SELECT NOT IN with multiple columns in subquery - sql

Regarding the statement below, sltrxid can exist as both ardoccrid and ardocdbid. I'm wanting to know how to include both in the NOT IN subquery.
SELECT *
FROM glsltransaction A
INNER JOIN cocustomer B ON A.acctid = B.customerid
WHERE sltrxstate = 4
AND araccttype = 1
AND sltrxid NOT IN(
SELECT ardoccrid,ardocdbid
FROM arapplyitem)

I would recommend not exists:
SELECT *
FROM glsltransaction t
INNER JOIN cocustomer c ON c.customerid = t.acctid
WHERE
??.sltrxstate = 4
AND ??.araccttype = 1
AND NOT EXISTS (
SELECT 1
FROM arapplyitem a
WHERE ??.sltrxid IN (a.ardoccrid, a.ardocdbid)
)
Note that I changed the table aliases to things that are more meaningful. I would strongly recommend prefixing the column names with the table they belong to, so the query is unambiguous - in absence of any indication, I represented this as ?? in the query.
IN sometimes optimize poorly. There are situations where two subqueries are more efficient:
SELECT *
FROM glsltransaction t
INNER JOIN cocustomer c ON c.customerid = t.acctid
WHERE
??.sltrxstate = 4
AND ??.araccttype = 1
AND NOT EXISTS (
SELECT 1
FROM arapplyitem a
WHERE ??.sltrxid = a.ardoccrid
)
AND NOT EXISTS (
SELECT 1
FROM arapplyitem a
WHERE ??.sltrxid = a.ardocdbid
)

Related

SQL split repeating rows caused by UNION

I am writing a query to look through and get two seperate averages based on where conditions.
I tried two select statetments but ended up with lots of duplicates.
Now I have a union which works pretty well, although I have my two fields in alternating rows instead of seperate columns.
Can anyone suggest a fix, sorry for the dodgy code!
SELECT
tblSkillName.skillName,
tblTestScores.skillUID,
AVG(tblTestScores.percentage) AS `cohortPercentage`
FROM
(
(
(
tblTestScores
INNER JOIN tblUsers ON tblUsers.email = tblTestScores.email
)
INNER JOIN tblTestDetails ON tblTestScores.testDetailsID = tblTestDetails.testDetailsID
)
INNER JOIN tblSkillName ON tblSkillName.skillUID = tblTestScores.skillUID
)
WHERE
teacherGroup = '9JS2/Cp'
AND tblTestScores.testDetailsID = 1
GROUP BY
skillName
UNION ALL
SELECT
tblSkillName.skillName,
tblTestScores.skillUID,
AVG(tblTestScores.percentage) AS `groupPercentage`
FROM
(
(
(
tblTestScores
INNER JOIN tblUsers ON tblUsers.email = tblTestScores.email
)
INNER JOIN tblTestDetails ON tblTestScores.testDetailsID = tblTestDetails.testDetailsID
)
INNER JOIN tblSkillName ON tblSkillName.skillUID = tblTestScores.skillUID
)
WHERE
tblTestScores.testDetailsID = 1
GROUP BY
skillName
ORDER BY
skillUID ASC

Oracle SQL XOR condition with > 14 tables

I have a question on sql desgin.
Context:
I have a table called t_master and 13 other tables (lets call them a,b,c... for simplicity) where it needs to compared.
Logic:
t_master will be compared to table 'a' where t_master.gen_val =
a.value.
If record exist in t_master, retrieve t_master record, else retrieve 'a' record.
I do not need to retrieve the records if it exists in both tables (t_master and a) - XOR condition
Repeat this comparison with the remaining 12 tables.
I have some idea on doing this, using WITH to subquery the non-master tables (a,b,c...) first with their respective WHERE clause.
Then use XOR statement to retrieve the records.
Something like
WITH a AS (SELECT ...),
b AS (SELECT ...)
SELECT field1,field2...
FROM t_master FULL OUTER JOIN a FULL OUTER JOIN b FULL OUTER JOIN c...
ON t_master.gen_value = a.value
WHERE ((field1 = x OR field2 = y ) AND NOT (field1 = x AND field2 = y))
AND ....
.
.
.
.
Seeing that I have 13 tables that I need to full outer join, is there a better way/design to handle this?
Otherwise I would have at least 2*13 lines of WHERE clause which I'm not sure if that will have impact on the performance as t_master is sort of a log table.
**Assume I cant change any schema.
Currently I'm not sure if this SQL will working correctly yet, so I'm hoping someone can guide me in the right direction regarding this.
update from used_by_already's suggestion:
This is what I'm trying to do (comparison between 2 tables first, before I add more, but I am unable to get values from ATP_R.TBL_HI_HDR HI_HDR as it is in the NOT EXISTS subquery.
How do i overcome this?
SELECT LOG_REPO.UNIQ_ID,
LOG_REPO.REQUEST_PAYLOAD,
LOG_REPO.GEN_VAL,
LOG_REPO.CREATED_BY,
TO_CHAR(LOG_REPO.CREATED_DT,'DD/MM/YYYY') AS CREATED_DT,
HI_HDR.HI_NO R_VALUE,
HI_HDR.CREATED_BY R_CREATED_BY,
TO_CHAR(HI_HDR.CREATED_DT,'DD/MM/YYYY') AS R_CREATED_DT
FROM ATP_COMMON.VW_CMN_LOG_GEN_REPO LOG_REPO JOIN ATP_R.TBL_HI_HDR HI_HDR ON LOG_REPO.GEN_VAL = HI_HDR.HI_NO
WHERE NOT EXISTS
(SELECT NULL
FROM ATP_R.TBL_HI_HDR HI_HDR
WHERE LOG_REPO.GEN_VAL = HI_HDR.HI_NO
)
UNION ALL
SELECT LOG_REPO.UNIQ_ID,
LOG_REPO.REQUEST_PAYLOAD,
LOG_REPO.GEN_VAL,
LOG_REPO.CREATED_BY,
TO_CHAR(LOG_REPO.CREATED_DT,'DD/MM/YYYY') AS CREATED_DT,
HI_HDR.HI_NO R_VALUE,
HI_HDR.CREATED_BY R_CREATED_BY,
TO_CHAR(HI_HDR.CREATED_DT,'DD/MM/YYYY') AS R_CREATED_DT
FROM ATP_R.TBL_HI_HDR HI_HDR JOIN ATP_COMMON.VW_CMN_LOG_GEN_REPO LOG_REPO ON HI_HDR.HI_NO = LOG_REPO.GEN_VAL
WHERE NOT EXISTS
(SELECT NULL
FROM ATP_COMMON.VW_CMN_LOG_GEN_REPO LOG_REPO
WHERE HI_HDR.HI_NO = LOG_REPO.GEN_VAL
)
Full outer joins used to exclude all matching rows can be an expensive query. You don't supply much detail, but perhaps using NOT EXISTS would be simpler and maybe it will produce a better explain plan. Something along these lines.
select
cola,colb,colc
from t_master m
where not exists (
select null from a where m.keycol = a.fk_to_m
)
and not exists (
select null from b where m.keycol = b.fk_to_m
)
and not exists (
select null from c where m.keycol = c.fk_to_m
)
union all
select
cola,colb,colc from a
where not exists (
select null from t_master m where a.fk_to_m = m.keycol
)
union all
select
cola,colb,colc from b
where not exists (
select null from t_master m where b.fk_to_m = m.keycol
)
union all
select
cola,colb,colc from c
where not exists (
select null from t_master m where c.fk_to_m = m.keycol
)
You could union the 13 a,b,c ... tables to simplify the coding, but that may not perform so well.

how to set expression variable in query select oracle sql

I have Oracle SQL like these :
SELECT
z."date", z.id_outlet as idOutlet, z.name as outletName, z.matClass, z.targetBulanan, z.targetBulanan/totalVisit as targetAwal,
z.actual,rownumber = tartot + rownumber as targetTotal
FROM (SELECT
b.visit_date as "date", a.id_outlet, max(o.name) as name, max(a.target_sales) as targetBulanan, a.id_material_class as matClass,
max(x.totalVisit) as totalVisit, NVL(SUM(d.billing_value),0) as actual
FROM (
select * from target_bulanan
where deleted = 0 and enabled = 1 and id_salesman = :id_salesman AND id_material_class like :id_material_class AND id_outlet like :id_outlet AND month = TO_NUMBER(TO_CHAR(current_date,'mm')) and year = to_number(TO_CHAR(current_date,'YYYY'))
) a
INNER JOIN outlet o ON o.id_outlet = a.id_outlet
LEFT JOIN visit_plan b ON b.deleted = 0 and a.id_salesman = b.id_salesman AND a.month = TO_NUMBER(TO_CHAR(b.visit_date,'mm')) AND a.year = to_number(TO_CHAR(b.visit_date,'yyyy')) AND a.id_outlet = b.id_outlet
LEFT JOIN so_header c ON SUBSTR(c.id_to,'0',1) = 'TO' AND a.id_salesman = c.id_salesman AND a.id_outlet = c.id_outlet
LEFT JOIN assign_billing d ON c.no_so_sap = d.no_so_sap AND d.billing_date = b.visit_date AND a.id_material_class = (SELECT id_material_class FROM material WHERE id = d.id_material)
LEFt JOIN (SELECT id_salesman, to_char(visit_date,'mm') as month, to_char(visit_date,'yyyy') as year, id_outlet, COUNT(*) as totalVisit FROM visit_plan
WHERE deleted = 0
group by id_salesman, id_outlet,to_char(visit_date,'mm'), to_char(visit_date,'yyyy')) x on
x.id_salesman = a.id_salesman AND x.month = a.month AND x.year = a.year AND x.id_outlet = a.id_outlet
GROUP BY b.visit_date, a.id_outlet, a.id_material_class) z
CROSS JOIN (SELECT 0 as rownumber FROM DUAL ) r
CROSS JOIN (SELECT 0 as tartot FROM DUAL ) t
CROSS JOIN (SELECT '' as mat FROM DUAL ) m
CROSS JOIN (SELECT '' as outlet FROM DUAL ) o
ORDER by outletName, z.matClass, z."date"
I want value of rownumber is formula in my select query but the result is error with this message
ORA-00923: FROM keyword not found where expected
00923. 00000 - "FROM keyword not found where expected"
Anyone can help me ? thanks
Just for enumeration -
replace the line
rownumber = rownumber + 1 AS row_number
with this
rownum AS row_number
rownum is an Oracle inbuilt function that enumerates each record of the result set and with auto increments
As mentioned by Gordon Linoff in his answer, there are further problems in your query.
At the first look (without executing it), I could list the problematic lines -
AND month = TO_NUMBER(TO_CHAR(current_date,'mm'))
AND year = to_number(TO_CHAR(current_date,'YYYY'))
Instead of current_date use sysdate
LEFT JOIN so_header c ON SUBSTR(c.id_to,'0',1) = 'TO'
I guess, you meant to do this -
LEFT JOIN so_header c ON SUBSTR(c.id_to,0,2) = 'TO'
i.e. substring from index 0 upto 2 characters
Plus, no need of those cross joins
THIS ADDRESSES THE ORIGINAL QUESTION.
You may have multiple problems in your query. After all, the best way to debug and to write queries is to start simple and gradually add complexity.
But, you do have an obvious error. In your outermost select:
SELECT z."date", z.id_outlet as idOutlet, z.name as outletName,
z.matClass, z.targetBulanan, z.targetBulanan/totalVisit as targetAwal,
z.actual,
rownumber = rownumber + 1 as row_number
The = is not Oracle syntax -- it looks like a SQL Server extension for naming a column or a MySQL use of variables.
I suspect that you want to enumerate the rows. If so, one syntax is row_number():
SELECT z."date", z.id_outlet as idOutlet, z.name as outletName,
z.matClass, z.targetBulanan, z.targetBulanan/totalVisit as targetAwal,
z.actual,
row_number() over (order by outletName, z.matClass, z."date") as row_number
In Oracle, you could also do:
rownum as row_number

sql function case returns more than one row

Going to use this query as a subquery, the problem is it returns many rows of duplicates. Tried to use COUNT() instead of exists, but it still returns a multiple answer.
Every table can only contain one record of superRef.
The below query I`ll use in SELECT col_a, [the CASE] From MyTable
SELECT CASE
WHEN
EXISTS (SELECT 1 FROM A WHERE
A_superRef = myTable.sysno AND A_specAttr = 'value')
THEN 3
WHEN EXISTS (SELECT 1 FROM B
INNER JOIN С ON С_ReferenceForB = B_sysNo WHERE C_superRef = myTable.sysno AND b_type = 2)
THEN 2
ELSE (SELECT C_intType FROM C
WHERE C_superRef = myTable.sysno)
END
FROM A, B, C
result:
3
3
3
3
3
3...
What if you did this? Because Im guessing you are getting an implicit full outer join A X B X C then running the case statement for each row in that result set.
SELECT CASE
WHEN
EXISTS (SELECT 1 FROM A WHERE
A_superRef = 1000001838012)
THEN 3
WHEN EXISTS (SELECT 1 FROM B
INNER JOIN С ON С_ReferenceForB = B_sysNo AND C_superRef = 1000001838012 )
THEN 2
ELSE (SELECT C_type FROM C
WHERE C_superRef = 1000001838012)
END
FROM ( SELECT COUNT(*) FROM A ) --This is a hack but should work in ANSI sql.
--Your milage my vary with different RDBMS flavors.
DUAL is what I needed, thanks to Thorsten Kettner
SELECT CASE
WHEN
EXISTS (SELECT 1 FROM A WHERE
A_superRef = 1000001838012)
THEN 3
WHEN EXISTS (SELECT 1 FROM B
INNER JOIN С ON С_ReferenceForB = B_sysNo AND C_superRef = 1000001838012 )
THEN 2
ELSE (SELECT C_type FROM C
WHERE C_superRef = 1000001838012)
END
FROM DUAL

Errors usually but within EXISTS sub-query it runs

Is the following problematic?
DELETE a
FROM WHAnalysis.dbo.tb_r12027dxi_CalculatedData a
WHERE EXISTS
(
SELECT *
FROM WHAnalysis.dbo.tb_r12027dxi_CalculatedData b
WHERE
b.[Past28Days] = 1 AND
a.[Index] = b.[Index]
HAVING SUM(b.Amount) = 0
)
Reason I'm slightly uneasy about using the above script is that if I run the following it errors:
SELECT *
FROM WHAnalysis.dbo.tb_r12027dxi_CalculatedData b
WHERE
b.[Past28Days] = 1
HAVING SUM(b.Amount) = 0
I understand why this script errors => the select is not grouped on anything therefore the processor does not like the aggregation in the HAVING clause.
But as a sub-query this error does not occur - why? Is this a problematic approach?
EDIT
Ended up using the following:
DELETE a
FROM WHAnalysis.dbo.tb_r12027dxi_CalculatedData a
WHERE a.[Index] IN
(
SELECT [Index]
FROM WHAnalysis.dbo.tb_r12027dxi_CalculatedData
WHERE [Past28Days] = 1
GROUP BY [Index]
HAVING SUM(Amount) = 0
)
But as suggested in answer the following is more readable by simply adding the GROUP BY into the sub-query:
DELETE a
FROM WHAnalysis.dbo.tb_r12027dxi_CalculatedData a
WHERE EXISTS
(
SELECT *
FROM WHAnalysis.dbo.tb_r12027dxi_CalculatedData b
WHERE
b.[Past28Days] = 1 AND
a.[Index] = b.[Index]
GROUP BY b.[Index]
HAVING SUM(b.Amount) = 0
)
It is legal to omit group by and still perform aggregations, therefore having is still a way of limiting results:
select sum(x)
from
(
select 1 x union all select 2
) a
having sum(x) = 3
Exists() work because everything in select list is ignored. Exists() looks for rows only, and terminates as soon as one is found. You might add group by b.Index to make intent clear to anyone reviewing the code later, or rewrite it using inner join and derived table.
DELETE a
FROM WHAnalysis.dbo.tb_r12027dxi_CalculatedData a
INNER JOIN
(
SELECT b.[Index]
FROM WHAnalysis.dbo.tb_r12027dxi_CalculatedData b
WHERE
b.[Past28Days] = 1
GROUP BY b.[Index]
HAVING SUM(b.Amount) = 0
) b1
ON a.[Index] = b1.[Index]