Change IN to EXISTS - oracle

I have this 2 query :
Query #1
SELECT A1.clrn_id ,A1.gpgroup ,A1.cl_id
FROM p_dtl A1
WHERE A1.PYMT_DT = TO_DATE(:1 ,'YYYY-MM-DD')
and A1.clrn_id in (
select clrn_id
from gp_cl_rn run
where run.clrn_id = a1.clrn_id
and run.finalized_ind = :2
)
AND cl_id IN
(
SELECT cl_id
FROM hm_sal
WHERE oprt_id ='004038'
AND runctrl_id = :3
)
Query #2
SELECT B.eid
FROM JBB B
WHERE B.eid IN
(
(
SELECT eid
FROM pbank A
WHERE (bankid,branchid) IN
(
SELECT bankid ,branchid
FROM pbranch
WHERE sourceid = :1
AND estat=:2
)
AND ESTAT = :2
AND acct_type = :3
AND acct_id = (
select max(D.acct_id)
from pbank D
where D.eid = A.eid
AND D.ESTAT = :2
AND D.acct_type = :3)
)
)
How can I change the clrn_id for the 1st query and B.eid for the 2nd query into EXISTS function? And are the bankid, branchid and acct_id also changeable into EXISTS function?
Thanks in advance!

You can change this to a where exists, but you probably want to join instead:
SELECT B.eid
FROM JBB B
JOIN ( SELECT eid, estat, acct_type, acct_id
, max(acct_id) over ( partition by eid ) as max_acct
FROM pbank
) A
ON b.eid = a.eid
JOIN pbranch C
ON a.bankid = c.bankid
AND a.branchid = c.brahchid
WHERE c.sourceid = :1
AND c.estat = :2
AND a.ESTAT = :3
AND a.acct_type = :4
AND a.acct_id = a.max_acct
By using the analytic function max() you remove the need for a sub-select; it's also a lot clearer, I think, to do things in this manner.
And, your newly added query becomes:
SELECT A1.clrn_id, A1.gpgroup, A1.cl_id
FROM p_dtl A1
JOIN gp_cl_rn run
ON A1.clrn_id = run.clrn_id
WHERE A1.PYMT_DT = TO_DATE(:1 ,'YYYY-MM-DD')
AND run.clrn_id = a1.clrn_id
AND run.finalized_ind = :2
I notice that you're explicitly using the to_date() function, which implies that you're storing a date as a string. This is bad practice and likely to cause you trouble in the longer run... avoid it if at all possible.
max(acct_id) over ( partition by eid )
is an analytic function; this does exactly the same as the aggregate function max(), except instead of requiring a GROUP BY, it returns the same result for every row in the result set.
This particular use returns the maximum acct_id for every eid. There's a whole host of analytic functions, but the best thing to do is to try it for yourself. There are also several examples available online.
Using a JOIN is not necessarily quicker than a where exists, it just depends what you're after. As with everything I would recommend trying both and seeing what suits your particular situation more.
Generally, they have different purposes; where exists is designed to stop "calculating" rows when a single row that fulfils the conditions is found. JOIN, does everything. In your case as you want everything there may be little to chose between them but I would always use JOIN; just ensure that your indexes are correct.

I hope, it will help you:
SELECT B.eid
FROM JBB B
where exists
(
SELECT 1
FROM pbank A
WHERE exists
(
SELECT 1
FROM pbranch PB
WHERE PB.sourceid = :1
AND PB.estat=:2
and PB.bankid = A.bankid
AND PB.branchid = A.branchid
)
AND ESTAT = :2
AND acct_type = :3
AND A.eid = B.eid
AND acct_id = (
select max(D.acct_id)
from pbank D
where D.eid = A.eid
AND D.eid = B.eid
AND D.ESTAT = :2
AND D.acct_type = :3)
)

SELECT B.eid
FROM JBB B
JOIN
(
(
SELECT eid
FROM pbank A
WHERE (bankid,branchid) IN
(
SELECT bankid ,branchid
FROM pbranch
WHERE sourceid = 'BNIATB'
AND estat='A'
)
AND ESTAT = 'A'
AND acct_type = 'S'
AND acct_id = (
select max(D.acct_id)
from pbank D
where D.eid = A.eid
AND D.ESTAT = 'A'
AND D.acct_type = 'S')
)
) C on B.eid = c.EID

Related

how to group oracle queries which has aggregate functions

it says "not a groupby function". when I add D.unitpricef to groupby it doesn't show any error,but in result it shows same itmcode multiple time. one item code should diplay only one time
SELECT
ItemCode,
case when H.InvoType = 1 then concat(ItemCode,' ( SLT Equipment )' ) else concat(ItemCode,' (
CONBES Eqipment )' ) end as EquName,
case when TRIM(SUM(QTY)) is null then '' else TRIM(TO_CHAR(SUM(QTY),'999999')) end as QTY,
CAST( (SUM(QTY) * D.unitpricef) AS NUMBER(38,2)) AS Amount
FROM Invoicedetails D
INNER JOIN
invoiceheader H ON D.InvoiceNo = H.InvoiceNo
INNER JOIN Equipment E ON E.EquCode = ItemCode
WHERE
H.CancelStat= 0
AND H.ReceiptStat = 1
AND H.BCCODE = 'xxx'
GROUP BY ItemCode,H.InvoType ORDER BY ItemCode ASC;
You need to group by using the same expressions associated to the aggregate function
SELECT
ItemCode,
case when H.InvoType = 1 then concat(ItemCode,' ( SLT Equipment )' ) else concat(ItemCode,' ( CONBES Eqipment )' ) end as EquName,
case when TRIM(SUM(QTY)) is null then '' else TRIM(TO_CHAR(SUM(QTY),'999999')) end as QTY,
CAST( (SUM(QTY) * D.unitpricef) AS NUMBER(38,2)) AS Amount
FROM Invoicedetails D
INNER JOIN
invoiceheader H ON D.InvoiceNo = H.InvoiceNo
INNER JOIN Equipment E ON E.EquCode = ItemCode
WHERE
H.CancelStat= 0
AND H.ReceiptStat = 1
AND H.BCCODE = 'xxx'
GROUP BY
ItemCode
case when H.InvoType = 1 then concat(ItemCode,' ( SLT Equipment )' ) else concat(ItemCode,' ( CONBES Eqipment )' ) end
ORDER BY ItemCode ASC;

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?

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

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

How can I join these two select queries into one query?

this is my first time posting, so I am sure I will get a number of things wrong. Do not hesitate to correct me and I will do everything I can to clarify.
In Oracle SQL Developer, I am trying to take two separate SELECT statements and combine them to get one row of results. Unfortunately, because this is sensitive data, I am unable to give any results from the statements individually, but instead, just the SQL statements themselves. I suspect I should be able to join these two on the field "emplid" but just cannot get there. Any help is greatly appreciated! Here is the code below, please mind the syntax :)
1st Select statement is giving me a list of people that were paid in 2017:
SELECT DISTINCT C.COMPANY,
C.EMPLID,
C.SSN
FROM PS_PAY_CHECK C
WHERE TO_CHAR(C.CHECK_DT,'YYYY') = '2017'
AND C.COMPANY IN ('001','054','076')
ORDER BY C.COMPANY, C.EMPLID
And 2nd Select statement would be a list of the deductions taken for the employees that were identified in the first statement:
SELECT G.EMPLID, G.DEDCD,
CASE
WHEN DC.DED_CLASS IN ('A','B','T')
THEN G.DED_ADDL_AMT
ELSE 0
END AS "EEAmt",
CASE
WHEN DC.DED_CLASS NOT IN ('A','B','T')
THEN G.DED_ADDL_AMT
ELSE 0
END AS "ERAmt",
DC.DED_CLASS,
G.DED_ADDL_AMT,
G.GOAL_AMT
FROM PS_GENL_DEDUCTION G,
PS_DED_CLASS_VW DC
WHERE G.EFFDT =
(SELECT MAX(G_ED.EFFDT)
FROM PS_GENL_DEDUCTION G_ED
WHERE G.EMPLID = G_ED.EMPLID
AND G.COMPANY = G_ED.COMPANY
AND G.DEDCD = G_ED.DEDCD
AND G_ED.EFFDT <= SYSDATE
)
AND ( G.DEDUCTION_END_DT IS NULL
OR G.DEDUCTION_END_DT > SYSDATE)
AND ( G.GOAL_AMT = 0.00
OR G.GOAL_AMT <> G.GOAL_BAL)
AND G.DED_ADDL_AMT > 0
AND DC.PLAN_TYPE = '00'
AND DC.DEDCD = G.DEDCD
AND DC.EFFDT =
(SELECT MAX(V1.EFFDT)
FROM PS_DED_CLASS_VW V1
WHERE V1.PLAN_TYPE = DC.PLAN_TYPE
AND V1.DEDCD = DC.DEDCD
)
AND G.EMPLID = 'XXXXXX'
Ideally, what I'd like to do is put in a value in place of 'XXXXXX' and get one row of data with the two combined statements.
Thanks everyone!
To do this, we stick each SELECT statement into a subquery and give that subquery an alias to refer to in the main queries select statement:
SELECT t1.*, t2.*
FROM
(
SELECT DISTINCT C.COMPANY,
C.EMPLID,
C.SSN
FROM PS_PAY_CHECK C
WHERE TO_CHAR(C.CHECK_DT,'YYYY') = '2017'
AND C.COMPANY IN ('001','054','076')
ORDER BY C.COMPANY, C.EMPLID
) t1
INNER JOIN
(
SELECT G.EMPLID, G.DEDCD,
CASE
WHEN DC.DED_CLASS IN ('A','B','T')
THEN G.DED_ADDL_AMT
ELSE 0
END AS "EEAmt",
CASE
WHEN DC.DED_CLASS NOT IN ('A','B','T')
THEN G.DED_ADDL_AMT
ELSE 0
END AS "ERAmt",
DC.DED_CLASS,
G.DED_ADDL_AMT,
G.GOAL_AMT
FROM PS_GENL_DEDUCTION G,
PS_DED_CLASS_VW DC
WHERE G.EFFDT =
(SELECT MAX(G_ED.EFFDT)
FROM PS_GENL_DEDUCTION G_ED
WHERE G.EMPLID = G_ED.EMPLID
AND G.COMPANY = G_ED.COMPANY
AND G.DEDCD = G_ED.DEDCD
AND G_ED.EFFDT <= SYSDATE
)
AND ( G.DEDUCTION_END_DT IS NULL
OR G.DEDUCTION_END_DT > SYSDATE)
AND ( G.GOAL_AMT = 0.00
OR G.GOAL_AMT <> G.GOAL_BAL)
AND G.DED_ADDL_AMT > 0
AND DC.PLAN_TYPE = '00'
AND DC.DEDCD = G.DEDCD
AND DC.EFFDT =
(SELECT MAX(V1.EFFDT)
FROM PS_DED_CLASS_VW V1
WHERE V1.PLAN_TYPE = DC.PLAN_TYPE
AND V1.DEDCD = DC.DEDCD
)
AND G.EMPLID = 'XXXXXX'
) t2 ON
t1.empid = t2.empid
We just treat each derived table/subquery as it's own table and join them on empid. You can tweak the SELECT statement at the top as needed.
This is similar to creating a view for both sql statements and then referencing the views in a third sql statement.
An alternative way of doing this is to use CTE (Common Table Expressions) to house the separate sql. There's no performance advantage here, but you might find it easier to read.
WITH t1 as
(
SELECT DISTINCT C.COMPANY,
C.EMPLID,
C.SSN
FROM PS_PAY_CHECK C
WHERE TO_CHAR(C.CHECK_DT,'YYYY') = '2017'
AND C.COMPANY IN ('001','054','076')
ORDER BY C.COMPANY, C.EMPLID
),
t2 AS
(
SELECT G.EMPLID, G.DEDCD,
CASE
WHEN DC.DED_CLASS IN ('A','B','T')
THEN G.DED_ADDL_AMT
ELSE 0
END AS "EEAmt",
CASE
WHEN DC.DED_CLASS NOT IN ('A','B','T')
THEN G.DED_ADDL_AMT
ELSE 0
END AS "ERAmt",
DC.DED_CLASS,
G.DED_ADDL_AMT,
G.GOAL_AMT
FROM PS_GENL_DEDUCTION G,
PS_DED_CLASS_VW DC
WHERE G.EFFDT =
(SELECT MAX(G_ED.EFFDT)
FROM PS_GENL_DEDUCTION G_ED
WHERE G.EMPLID = G_ED.EMPLID
AND G.COMPANY = G_ED.COMPANY
AND G.DEDCD = G_ED.DEDCD
AND G_ED.EFFDT <= SYSDATE
)
AND ( G.DEDUCTION_END_DT IS NULL
OR G.DEDUCTION_END_DT > SYSDATE)
AND ( G.GOAL_AMT = 0.00
OR G.GOAL_AMT <> G.GOAL_BAL)
AND G.DED_ADDL_AMT > 0
AND DC.PLAN_TYPE = '00'
AND DC.DEDCD = G.DEDCD
AND DC.EFFDT =
(SELECT MAX(V1.EFFDT)
FROM PS_DED_CLASS_VW V1
WHERE V1.PLAN_TYPE = DC.PLAN_TYPE
AND V1.DEDCD = DC.DEDCD
)
AND G.EMPLID = 'XXXXXX'
)
SELECT t1.*, t2.*
FROM t1 INNER JOIN t2 ON
t1.empid = t2.empid
Basically you do something like
select blah, blah.. (your second query )
AND G.EMPLID IN ( SELECT DISTINCT C.COMPANY,
C.EMPLID,
C.SSN
FROM PS_PAY_CHECK C
WHERE TO_CHAR(C.CHECK_DT,'YYYY') = '2017'
AND C.COMPANY IN ('001','054','076')
)
So basically I have used your first query in you second query. I removed the DISTINCT, as its not needed.
I hope that makes sense.

ORA-01427: single-row subquery returns more than one row while using update

Hi I have the following update query:
UPDATE buchung b
SET kst_id = (SELECT kst_id
FROM kostenstelle
WHERE b.Kostenstelle = kostenstelle)
I get the Error in the Topic and I know why.
My Question: Is it possible to update just the rows which are unique?
There are various ways to do this.
Add a WHERE clause that checks that for the specific b.kostenstelle value there is exactly one row in table kostenstelle:
update buchung b
set kst_id = (select kst_id
from kostenstelle
where b.Kostenstelle = kostenstelle)
where 1 = (select count(*)
from kostenstelle
where b.Kostenstelle = kostenstelle) ;
exactly as before, only using a different way to check for only one. It may be more efficient, with an index on kostenstelle (kostenstelle, kst_id):
update buchung b
set kst_id = (select kst_id
from kostenstelle
where b.Kostenstelle = kostenstelle)
where not exists
( select 0
from kostenstelle
where b.Kostenstelle = kostenstelle
having min(kst_id) < max(kst_id) -- the PK of kostenstelle
) ;
use the COUNT() window function to determine the count:
with upd as
( select b.kst_id,
k.kst_id as k_kst_id
count(distinct k.kst_id) over (partition by k.kostenstelle) as cnt
from buchung b
join kostenstelle
on b.Kostenstelle = k.kostenstelle
)
update upd
set kst_id = k_kst_id
where cnt = 1 ;
and a variation on the last one, where it may be more obvious what the code is doing:
with unq as
( select kostenstelle,
min(kst_id) as kst_id
from kostenstelle
group by kostenstelle
having min(kst_id) = max(kst_id)
) ,
upd as
( select b.kst_id,
k.kst_id as k_kst_id
from buchung b
join unq k
on b.Kostenstelle = k.kostenstelle
)
update upd
set kst_id = k_kst_id ;

Resources