Oracle's SDO_CONTAINS not using spatial index on unioned tables? - oracle

I'm trying to use Oracle's sdo_contains spatial operator, but it seems, that it's not really working, when you use it on unioned tables.
The below code runs in 2 mins, but you have to duplicate the spatial operator for every source table:
SELECT -- works
x.code,
count(x.my_id) cnt
FROM (select
c.code,
t.my_id
from my_poi_table_1 t,my_shape c
WHERE SDO_contains(c.shape,
sdo_geometry(2001,null,SDO_POINT_type(t.latitude, t.longitude,null),null,null)
) = 'TRUE'
union all
select
c.code,
t.my_id
from my_poi_table_2 t,my_shape c
where SDO_contains(c.shape,
sdo_geometry(2001,null,SDO_POINT_type(t.lat, t.lng,null),null,null)
) = 'TRUE'
) x
group by x.code
I wanted to make it simple, so I tried to first create the points, and then just once use the sdo_contains on it, but it's running for more then 25 mins, because it's not using the spatial index:
SELECT -- does not work
c.code,
count(x.my_id) cnt
FROM my_shape c,
(select
my_id,
sdo_geometry(2001,null,SDO_POINT_type(latitude, longitude,null),null,null) point
from my_poi_table_1 t
union all
select
my_id2,
sdo_geometry(2001,null,SDO_POINT_type(lat, lng,null),null,null) point
from my_poi_table_2 t
) x
WHERE SDO_contains(c.shape,
x.point
) = 'TRUE'
group by c.code
Is there a way to use the sdo_contains on the results of multiple tables without having to include it in the select several times?
Oracle: 12.1.0.2

It seems, that sdo_contains cannot (efficiently) read from a subselect: if I put one of the poi tables into a subselect, then Oracle will not use spatial index for that part:
SELECT -- does not work
x.code,
count(x.my_id) cnt
FROM (select --+ ordered index(c,INDEX_NAME)
c.code,
t.my_id
from my_shape c,(select t.*,rownum rn from my_poi_table_1 t) t
WHERE SDO_contains(c.shape,
sdo_geometry(2001,null,SDO_POINT_type(t.latitude, t.longitude,null),null,null)
) = 'TRUE'
union all
select
c.code,
t.my_id
from my_poi_table_2 t,my_shape c
where SDO_contains(c.shape,
sdo_geometry(2001,null,SDO_POINT_type(t.lat, t.lng,null),null,null)
) = 'TRUE'
) x
group by x.code

Related

I am getting "missing keyword" error in co-related sub query in Oracle

EXPLAIN SELECT S.ITEMID, X.STATUS
FROM
(select itemid,itemdescription from A WHERE parent_itemid IN(SELECT ITEMID FROM A WHERE itemtype='CT') AND itemtype='SK' AND id='02') S,
(select s1.itemid from s1,s2 where s1.itemid=s2.itemid and status='RC' ) X
where S.itemid=X.itemid
It is not correlated subquery that makes problems, but syntax. Not just explain, but explain plan for.
I don't have your tables so I used Scott's (properly joined):
SQL> explain plan for select s.dname, x.ename
2 from (select d.deptno,
3 d.dname
4 from dept d
5 where d.deptno in (select b.deptno
6 from dept b
7 where b.deptno = 10
8 )
9 ) s join
10 (select e.deptno,
11 e.ename
12 from emp e
13 where e.deptno = 10
14 ) x
15 on s.deptno = x.deptno;
Explained.
SQL>
In your case, presuming that everything else is correct,
explain plan for select s.itemid, x.status
from (select a.itemid,
a.itemdescription
from a
where a.parent_itemid in (select b.itemid
from a b
where b.itemtype = 'CT'
)
and a.itemtype = 'SK'
and a.id = '02'
) s join
(select s1.itemid
from s1 join s2 on s1.itemid = s2.itemid
where s1.status = 'RC' --> which table does STATUS belong to?
) x
on s.itemid = x.itemid;
I'd also suggest you to always use table aliases because it is not clear what belongs to which table. For example, status = 'RC' - without alias, it is impossible to know which table has it. If both, you'd have ambiguity.
Finally, write formatted queries. Mess you posted is difficult to read and understand. Most modern GUI tools have built-in formatters; I suggest you use it. Or, use one of online formatters. Or, format it manually. Just try not to do what you're doing now.
The correct syntax for EXPLAIN is:
EXPLAIN PLAN FOR
SELECT...
Also in the 2nd subquery you must select the column status because you want to select it in the outer select:
EXPLAIN PLAN FOR
SELECT S.ITEMID, X.STATUS
FROM
(select itemid,itemdescription from A WHERE parent_itemid IN(SELECT ITEMID FROM A WHERE itemtype='CT') AND itemtype='SK' AND id='02') S,
(select s1.itemid, status from s1,s2 where s1.itemid=s2.itemid and status='RC' ) X
where S.itemid=X.itemid
Also status should be qualified by the table's name just to avoid any ambiguities.

Adding filters in subquery from CTE quadruples run time

I am working on an existing query for SSRS report that focuses on aggregated financial aid data split out into 10 aggregations. User wants to be able to select students included in that aggregated data based on new vs. returning and 'selected for verification.' For the new/returning status, I added a CTE to return the earliest admit date for a student. 2 of the 10 data fields are created by a subquery. I have been trying for 3 days to get the subquery to use the CTE fields for a filter, but they won't work. Either they're ignored or I get a 'not a group by expression' error. If I put the join to the CTE within the subquery, the query time jumps from 45 second to 400 seconds. This shouldn't be that complicated! What am I missing? I have added some of the code... 3 of the chunks work - paid_something doesn't.
with stuStatus as
(select
person_uid, min(year_admitted) admit_year
from academic_study
where aid_year between :AidYearStartParameter and :AidYearEndParameter
group by person_uid)
--- above code added to get student information not originally in qry
select
finaid_applicant_status.aid_year
, count(1) as fafsa_cnt --works
, sum( --works
case
when (
package_complete_date is not null
and admit.status is not null
)
then 1
else 0
end
) as admit_and_package
, (select count(*) --does't work
from (
select distinct award_by_aid_year.person_uid
from
award_by_aid_year
where
award_by_aid_year.aid_year = finaid_applicant_status.aid_year
and award_by_aid_year.total_paid_amount > 0 )dta
where
(
(:StudentStatusParameter = 'N' and stuStatus.admit_year = finaid_applicant_status.aid_year)
OR
(:StudentStatusParameter = 'R' and stuStatus.admit_year <> finaid_applicant_status.aid_year)
OR :StudentStatusParameter = '%'
)
)
as paid_something
, sum( --works
case
when exists (
select
1
from
award_by_person abp
where
abp.person_uid = fafsa.person_uid
and abp.aid_year = fafsa.aid_year
and abp.award_paid_amount > 0
) and fafsa.requirement is not null
then 1
else 0
end
) as paid_something_fafsa
from
finaid_applicant_status
join finaid_tracking_requirement fafsa
on finaid_applicant_status.person_uid = fafsa.person_uid
and finaid_applicant_status.aid_year = fafsa.aid_year
and fafsa.requirement = 'FAFSA'
left join finaid_tracking_requirement admit
on finaid_applicant_status.person_uid = admit.person_uid
and finaid_applicant_status.aid_year = admit.aid_year
and admit.requirement = 'ADMIT'
and admit.status in ('M', 'P')
left outer join stuStatus
on finaid_applicant_status.person_uid = stuStatus.person_uid
where
finaid_applicant_status.aid_year between :AidYearStartParameter and :AidYearEndParameter
and (
(:VerifiedParameter = '%') OR
(:VerifiedParameter <> '%' AND finaid_applicant_status.verification_required_ind = :VerifiedParameter)
)
and
(
(:StudentStatusParameter = 'N' and (stuStatus.admit_year IS NULL OR stuStatus.admit_year = finaid_applicant_status.aid_year ))
OR
(:StudentStatusParameter = 'R' and stuStatus.admit_year <> finaid_applicant_status.aid_year)
OR :StudentStatusParameter = '%'
)
group by
finaid_applicant_status.aid_year
order by
finaid_applicant_status.aid_year
Not sure if this helps, but you have something like this:
select aid_year, count(1) c1,
(select count(1)
from (select distinct person_uid
from award_by_aid_year a
where a.aid_year = fas.aid_year))
from finaid_applicant_status fas
group by aid_year;
This query throws ORA-00904 FAS.AID_YEAR invalid identifier. It is because fas.aid_year is nested too deep in subquery.
If you are able to modify your subquery from select count(1) from (select distinct sth from ... where year = fas.year) to select count(distinct sth) from ... where year = fas.year then it has the chance to work.
select aid_year, count(1) c1,
(select count(distinct person_uid)
from award_by_aid_year a
where a.aid_year = fas.aid_year) c2
from finaid_applicant_status fas
group by aid_year
Here is simplified demo showing non-working and working queries. Of course your query is much more complicated, but this is something what you could check.
Also maybe you can use dbfiddle or sqlfiddle to set up some test case? Or show us sample (anonimized) data and required output for them?

select statement should return count as zero if no row return using group by clause

I have a table student_info, it has column "status", status can be P (present), A (absent), S (ill), T ( transfer), L (left).
I am looking for expected output as below.
status count(*)
P 12
S 1
A 2
T 0
L 0
But output is coming like as below:
Status Count(*)
P 12
S 1
A 2
we need rows against status T and L as well with count zero though no record exist in DB.
#mkuligowski's approach is close, but you need an outer join between the CTE providing all of the possible status values, and then you need to count the entries that actually match:
-- CTE to generate all possible status values
with stored_statuses (status) as (
select 'A' from dual
union all select 'L' from dual
union all select 'P' from dual
union all select 'S' from dual
union all select 'T' from dual
)
select ss.status, count(si.status)
from stored_statuses ss
left join student_info si on si.status = ss.status
group by ss.status;
STATUS COUNT(SI.STATUS)
------ ----------------
P 12
A 2
T 0
S 1
L 0
The CTE acts as a dummy table holding the five statuses you want to count. That is then outer joined to your real table - the outer join means the rows from the CTE are still included even if there is no match - and then the rows that are matched in your table are counted. That allows the zero counts to be included.
You could also do this with a collection:
select ss.status, count(si.status)
from (
select column_value as status from table(sys.odcivarchar2list('A','L','P','S','T'))
) ss
left join student_info si on si.status = ss.status
group by ss.status;
It would be preferable to have a physical table which holds those values (and their descriptions); you could also then have a primary/foreign key relationship to enforce the allowed values in your existing table.
If all the status values actually appear in your table, but you have a filter which happens to exclude all rows for some of them, then you could get the list of all (used) values from the table itself instead of hard-coding it.
If your initial query was something like this, with a completely made-up filter:
select si.status, count(*)
from student_info si
where si.some_condition = 'true'
group by si.status;
then you could use a subquery to get all the distinct values from the unfiltered table, outer join from that to the same table, and apply the filter as part of the outer join condition:
select ss.status, count(si.status)
from (
select distinct status from student_info
) ss
left join student_info si on si.status = ss.status
and si.some_condition = 'true'
group by ss.status;
It can't stay as a where clause (at least here, where it's applying to the right-hand-side of the outer join) because that would override the outer join and effectively turn it back into an inner join.
You should store somewhere your statuses (pherhaps in another table). Otherwise, you list them using subquery:
with stored_statuses as (
select 'P' code, 'present' description from dual
union all
select 'A' code, 'absent' description from dual
union all
select 'S' code, 'ill' description from dual
union all
select 'T' code, 'transfer' description from dual
union all
select 'L' code, 'left' description from dual
)
select ss.code, count(*) from student_info si
left join stored_statuses ss on ss.code = si.status
group by ss.code

Where do I start to optimize this SQL Query?

I have this sql query delivered from a customer which we need to performance optimize.
Is there anyone that can point me in the right direction to where to start looking for optimizing the following query?
The query on my local machine takes about 6-7 seconds, but on the users it´s about 30 seconds, It executes on a mssql 2008r2
Thanks!
var query = #"
DECLARE #SearchString nvarchar(250)
set #SearchString = '1811820001'
;with BaseSelectorCTE (ID) as
(
SELECT ID FROM BaseCases b
where
b.CPR like #SearchString
OR (b.FirstName + ' ' + b.LastName) like #SearchString
OR b.CustomerInfo_InstitutionName like #SearchString
UNION
Select ID from FlexjobCase
where Kommune like #SearchString
UNION
Select ID from DisabledAssistantCase
where Kommune like #SearchString
UNION
Select ID from AdultStudentCase
where Kommune like #SearchString
UNION
Select ID from DiseaseCase
where Kommune like #SearchString
UNION
Select ID from MaternityCase
where Kommune like #SearchString
UNION
Select ID from MiscellaneousCase
where Kommune like #SearchString
UNION
Select ID from WageSubsidyCase
where Kommune like #SearchString
UNION
Select w.ID from WageSubsidyCase w inner join JobCenters j on
w.JobcenterID = j.ID
where
j.Name like #SearchString
UNION
Select a.ID from AdultStudentCase a inner join JobCenters j on
a.JobcenterID = j.ID
where
j.Name like #SearchString
)
--
-- Select BaseCases mapped to result type
--
,ResultSelectorCTE AS
(
select
bc.Id as CaseID,
bc.ChildCaseName,
bc.CPR,
bc.FirstName,
bc.LastName,
bc.CustomerInfo_CustomerInfoID as CustomerInfoID,
bc.CustomerInfo_InstitutionName as InstitutionName,
bc.CaseDeadline,
bc.StatusID,
cs.Name as [StatusName],
cs.Owner as [StatusOwner],
bc.MetaData_Updated as [LastChange],
bc.LastActionDay,
,CASE bc.StatusID WHEN 9 THEN 1 ELSE 0 END as SidstePeriodeSoegt
from BaseCases bc
inner join CaseStatus cs ON
bc.StatusID = cs.ID
inner join BaseSelectorCTE bsCTE ON
bc.ID = bsCTE.ID
)
select * from (Select *, ROW_NUMBER() Over(Order By ##version , CASE WHEN StatusID = 9 then 2 ELSE 1 END, CaseDeadline ASC,
SidstePeriodeSoegt)
As rownum from ResultSelectorCTE where 1=1 AND StatusOwner <> 2 AND StatusOwner <> 3
AND SUBSTRING(CPR, 0, 3) BETWEEN 26-08-2014 AND 26-08-2015) As Result
where rownum Between ((1 - 1) * 100 + 1) AND (1 * 100);
Yes, the query execution plan:
The SQL Server Database Engine can display how it navigates tables and uses indexes to access or process the data for a query or other DML statement, such as an update. This is a display of an execution plan. To analyze a slow-running query, it is useful to examine the query execution plan to determine what is causing the problem.
Without knowing anything, start by losing the wildcards (*) sign. It's bad almost always as you are just saying, send everything and forgetting to actually review it.
Then, format your code correctly, CTE's are great, simplifies code, but it beats the purpose if your selects look like spaghetti, this has nothing to do with performance though.
Also, I had many times when UNION ALL outperformed UNION and I din't really think if duplicates were a problem or not, so you might want to look into that.
You didn't say if you are running it from Management Studio, are you on a local or remote server, how do the CTE's perform individually, etc. Context is king on this.
Hope this helps.

Oracle Optimizer Unexpected Results

I have a co worker who wrote the following query. The first one works and the second one does not. Also if you remove the aggregate function from the subquery, it works. The oracle optimizer is doing something weird. Any thoughts? Running in SQL Developer 3.1 against 11.1.0.6.0 64 bit.
This works:
SELECT
a.fd_customer_key
, b.fd_customer_key
, b.counter
FROM FETCH_CUSTOMER a
, (select fd_customer_key, count(*) as counter from fetch_customer_order group by fd_customer_key) b
where a.fd_customer_key = b.fd_customer_key (+)
and b.counter is null
This doesn’t:
SELECT
a.fd_customer_key
, b.fd_customer_key
, b.counter
FROM FETCH_CUSTOMER a
, (select fd_customer_key, count(*) as counter from fetch_customer_order group by fd_customer_key) b
where a.fd_customer_key = b.fd_customer_key (+)
and b.fd_customer_key is null
Actually yes, both of the queries you provided are supposed to wrok the same way, but if i understand your need well, you are trying to select the fd_customer_key which has no Order?
I suggest the following query for your need, its more simple and less consuming :
SELECT a.fd_customer_key
FROM FETCH_CUSTOMER a
WHERE NOT EXISTS (SELECT 1
FROM fetch_customer_order b
WHERE a.fd_customer_key = b.fd_customer_key)
It seems like you are trying to make an anti-join (find the rows from FETCH_CUSTOMER that have no corresponding rows in FETCH_CUSTOMER_ORDER).
With Oracle you do not have to use this clever OUTER JOIN trick to write an anti-join, you could use a NOT IN or NOT EXISTS operator and let the optimizer find the best plan. This will be just as efficient and easier to read.
Anyway, I can't reproduce your findings, here's my setup:
CREATE TABLE a (ID NUMBER PRIMARY KEY);
CREATE TABLE b (a_id NUMBER NOT NULL, DATA VARCHAR2(30));
INSERT INTO a (SELECT object_id FROM all_objects);
INSERT INTO b (SELECT object_id, object_name
FROM all_objects WHERE object_type = 'VIEW');
SELECT a.id, b.a_id, b.cnt
FROM a, (SELECT a_id, COUNT(*) cnt FROM b GROUP BY a_id) b
WHERE a.id = b.a_id (+)
AND b.cnt IS NULL;
SELECT a.id, b.a_id, b.cnt
FROM a, (SELECT a_id, COUNT(*) cnt FROM b GROUP BY a_id) b
WHERE a.id = b.a_id (+)
AND b.a_id IS NULL;
You will find that both queries return rows. What is your DB version?

Resources