Change SELECT statement with CASE statement - sql

I have a query that returns data like this:
Data
|Nbr|Type|TelNbr|PNbr|Date|
|12345|AA|001122|432|15-DEC-14|
|23456|AA|009933|567|21-SEP-01|
|99999|AA|885523|000|17-JUN-98|
|99999|BB|885523|000|21-FEB-94|
|65432|AA|112233|NULL|01-JAN-01|
|NULL|AA|333333|555|09-JUL-20|
|65432|BB|112233|888|06-MAY-08|
|01010|CC|333333|555|04-MAR-99|
Query
SELECT t1.Nbr
,t1.Type
,MAX(FUNCTION(t1.TelNbr)) TelNbr
,t2.PNbr
,MIN(t1.Date)
FROM table1 t1
LEFT JOIN table2 t2 ON t1.id = t2.id
GROUP BY t1.Nbr, t1.Type, t2.PNbr
I want expand upon this query to select the oldest row (MIN(t1.Date)) for each TelNbr instance where type equals AA. However, if Nbr or PNbr is NULL, then I want to select the oldest row regardless of the type for each TelNbr instance.
I attempted to do something like this, but I'm not great with CASE statements:
SELECT CASE WHEN t1.Nbr IS NULL OR t2.PNbr IS NULL
THEN SELECT t1.Nbr
, t1.Type
, MAX(FUNCTION(TelNbr) TelNbr
, t2.PNbr
, MIN(t1.Date)
FROM table1 t1
LEFT JOIN table2 t2 ON t1.id = t2.id
GROUP BY t1.Nbr, t1.Type, t1.TelNbr, t2.PNbr
ELSE SELECT t1.Nbr
, t1.Type
, MAX(FUNCTION(TelNbr) TelNbr
, t2.PNbr
, MIN(t1.Date)
FROM table1 t1
LEFT JOIN table2 t2 ON t1.id = t2.id
WHERE t1.Type = 'AA'
GROUP BY t1.Nbr, t1.Type, t1.TelNbr, t2.PNbr
END
Whenever I run the query, Oracle gives me the following error:
ORA-00936: missing expression
Is my syntax off? I’ve tried to find the answer online, but I’m not having any luck. Any suggestions are greatly appreciated!

Your syntax is invalid. You can use conditional aggregation in an analytic COUNT function and then filter the rows in an outer query:
SELECT *
FROM (
SELECT t.*,
COUNT(CASE WHEN nbr IS NULL OR pnbr IS NULL THEN 1 END) OVER ()
AS null_count
FROM table_name t
ORDER BY "DATE" asc
)
WHERE ( null_count > 0 OR type = 'AA' )
AND ROWNUM = 1;

It is not quite clear (no expected result) what do you want but, as I understand the question, it seems like you want 2 rows as a result - one for type 'AA' with oldest date and the other (if there are Nulls in NBR or PNBR) with absolute oldest date.
Below is the code that results like described and giving you some options to get different results.
SAMPLE DATA:
tbl (NBR, A_TYPE, TELNBR, PNBR, A_DATE) AS
(
Select '12345', 'AA', '001122', '432', DATE '2014-12-15' From Dual Union All
Select '23456', 'AA', '009933', '567', DATE '2001-09-21' From Dual Union All
Select '99999', 'AA', '885523', '000', DATE '1998-06-17' From Dual Union All
Select '99999', 'BB', '885523', '000', DATE '1994-02-21' From Dual Union All
Select '65432', 'AA', '112233', Null, DATE '2001-01-01' From Dual Union All
Select Null, 'AA', '333333', '555', DATE '2020-07-09' From Dual Union All
Select '65432', 'BB', '112233', '888', DATE '2008-05-06' From Dual Union All
Select '01010', 'CC', '333333', '555', DATE '1999-03-04' From Dual
)
SQL 1
SELECT *
FROM
(
SELECT t.A_TYPE "A_TYPE",
t.NBR, t.TELNBR, t.PNBR,
CASE WHEN t.NBR Is Null OR t.PNBR Is Null THEN MIN(t.A_DATE) OVER() ELSE dt.MIN_DATE END "MIN_DATE" ,
MIN(t.A_DATE) OVER() "ABS_MIN_DATE"
FROM tbl t
INNER JOIN
( Select CASE WHEN NBR Is Null OR PNBR Is Null THEN 'AA' ELSE A_TYPE END "A_TYPE",
MIN(A_DATE) "MIN_DATE"
From tbl
Group By CASE WHEN NBR Is Null OR PNBR Is Null THEN 'AA' ELSE A_TYPE END
) dt ON(dt.MIN_DATE = t.A_DATE And dt.A_TYPE = CASE WHEN t.NBR Is Null OR t.PNBR Is Null THEN 'AA' ELSE t.A_TYPE END )
ORDER BY t.A_TYPE
)
WHERE A_TYPE = 'AA' OR MIN_DATE = ABS_MIN_DATE
AA
A_TYPE NBR TELNBR PNBR MIN_DATE ABS_MIN_DATE
------ ----- ------ ---- --------- ------------
AA 99999 885523 000 17-JUN-98 21-FEB-94
BB 99999 885523 000 21-FEB-94 21-FEB-94
You can exclude the last column from the selection - I left it here intentionaly so you could compare it with next SQL where just the values of 'AA' within the case expressions are changed into 'XX'.
SQL 2
SELECT *
FROM
(
SELECT t.A_TYPE "A_TYPE",
t.NBR, t.TELNBR, t.PNBR,
CASE WHEN t.NBR Is Null OR t.PNBR Is Null THEN MIN(t.A_DATE) OVER() ELSE dt.MIN_DATE END "MIN_DATE" ,
MIN(t.A_DATE) OVER() "ABS_MIN_DATE"
FROM tbl t
INNER JOIN
( Select CASE WHEN NBR Is Null OR PNBR Is Null THEN 'XX' ELSE A_TYPE END "A_TYPE",
MIN(A_DATE) "MIN_DATE"
From tbl
Group By CASE WHEN NBR Is Null OR PNBR Is Null THEN 'XX' ELSE A_TYPE END
) dt ON(dt.MIN_DATE = t.A_DATE And dt.A_TYPE = CASE WHEN t.NBR Is Null OR t.PNBR Is Null THEN 'XX' ELSE t.A_TYPE END )
ORDER BY t.A_TYPE
)
WHERE A_TYPE = 'AA' OR MIN_DATE = ABS_MIN_DATE
XX
A_TYPE NBR TELNBR PNBR MIN_DATE ABS_MIN_DATE
------ ----- ------ ---- --------- ------------
AA 99999 885523 000 17-JUN-98 21-FEB-94
AA 65432 112233 21-FEB-94 21-FEB-94
BB 99999 885523 000 21-FEB-94 21-FEB-94
SQL 3 - Min date per TELNBR for type 'AA'
Select A_TYPE,
TELNBR,
MIN(A_DATE) "MIN_DATE"
From tbl
WHERE A_TYPE = 'AA'
Group By A_TYPE, TELNBR
A_TYPE
TELNBR
MIN_DATE
AA
112233
01-JAN-01
AA
885523
17-JUN-98
AA
009933
21-SEP-01
AA
333333
09-JUL-20
AA
001122
15-DEC-14
SQL 4 - same as 3 but with all columns
SELECT m.A_TYPE, m.TELNBR, m.MIN_DATE, t.NBR, t.PNBR
FROM ( Select A_TYPE, TELNBR, MIN(A_DATE) "MIN_DATE"
From tbl
WHERE A_TYPE = 'AA'
Group By A_TYPE, TELNBR
) m
LEFT JOIN tbl t ON(t.A_TYPE = m.A_TYPE And t.TELNBR = m.TELNBR And t.A_DATE = m.MIN_DATE)
A_TYPE
TELNBR
MIN_DATE
NBR
PNBR
AA
001122
15-DEC-14
12345
432
AA
009933
21-SEP-01
23456
567
AA
885523
17-JUN-98
99999
000
AA
112233
01-JAN-01
65432
AA
333333
09-JUL-20
555

CASE statement is not used for switching of SELECT data group as you are trying to achieve. In your case, you could use UNION (or UNION ALL) for selecting desired output from 2 data groups.
-- Group 1: data which has Nbr or PNbr is null.
SELECT Nbr, Type, TelNbr, PNbr, MIN(DateTime)
FROM data
WHERE (Nbr IS NULL OR PNbr IS NULL)
GROUP BY Nbr, Type, TelNbr, PNbr
-- Group 2: data which has Type = 'AA' and Nbr, PNbr is not null.
UNION ALL
SELECT Nbr, Type, TelNbr, PNbr, MIN(DateTime)
FROM data
WHERE Type = 'AA' AND Nbr IS NOT NULL AND PNbr IS NOT NULL
GROUP BY Nbr, Type, TelNbr, PNbr
Demo: http://sqlfiddle.com/#!4/b519d/240

Related

SQL query to get both common and and non common data from 2 tables

Hi im looking for a query which will give me both common and non-common data in one query.
Table 2
ID
Assay
1
124
Result
required_missing
required_present
125
124
Based on req_ind column from table 1 , if req_ind is 1 and the same assay is present in table 2 i want to list it as above.
required missing column can have multiple column.
With the data given this gives requested result:
WITH table1 as (
select 1 as ID, 123 as Assay, 0 as req_ind from dual
union all
select 2,124,1 from dual
union all
select 3,125,1 from dual
),
table2 as (
select 1 as ID, 124 as Assay from dual
),
required_missing as (
select
row_number() over (order by table1.Assay) as R,
table1.Assay as required_missing
from table1
left join table2 on table2.Assay = table1.Assay
where table1.req_ind=1 and table2.id is null
),
requires_present as (
select
row_number() over (order by table1.Assay) as R,
table1.Assay as required_present
from table1
left join table2 on table2.Assay = table1.Assay
where table1.req_ind=1 and table2.id is not null
),
results as (
select row_number() over (order by (id)) as r
from table1
)
select rm.required_missing, rp.required_present
from results
left join required_missing rm on rm.R = results.R
left join requires_present rp on rp.R = results.R
where rm.R is not null or rp.R is not null;
output:
REQUIRED_MISSING
REQUIRED_PRESENT
125
124
If you want to have a comma separated list for missing and for present then you can use:
SELECT LISTAGG(CASE WHEN t2.assay IS NULL THEN t1.assay END, ',')
WITHIN GROUP (ORDER BY t1.assay) AS required_missing,
LISTAGG(t2.assay, ',')
WITHIN GROUP (ORDER BY t1.assay) AS required_present
FROM table1 t1
LEFT OUTER JOIN table2 t2
ON (t1.assay = t2.assay)
WHERE t1.req_ind = 1
Which, for the sample data:
CREATE TABLE table1 (id, assay, req_ind) AS
SELECT 1, 123, 0 FROM DUAL UNION ALL
SELECT 2, 124, 1 FROM DUAL UNION ALL
SELECT 3, 125, 1 FROM DUAL UNION ALL
SELECT 4, 126, 1 FROM DUAL UNION ALL
SELECT 5, 127, 1 FROM DUAL;
CREATE TABLE table2 (id, assay) AS
SELECT 1, 124 FROM DUAL UNION ALL
SELECT 2, 127 FROM DUAL;
Outputs:
REQUIRED_MISSING
REQUIRED_PRESENT
125,126
124,127
If you want the output in multiple rows then:
SELECT required_missing,
required_present
FROM (
SELECT NVL2(t2.assay, 'P', 'M') AS status,
ROW_NUMBER() OVER (
PARTITION BY NVL2(t2.assay, 'P', 'M')
ORDER BY t1.assay
) AS rn,
t1.assay
FROM table1 t1
LEFT OUTER JOIN table2 t2
ON (t1.assay = t2.assay)
WHERE t1.req_ind = 1
)
PIVOT (
MAX(assay)
FOR status IN (
'M' AS required_missing,
'P' AS required_present
)
)
Which outputs:
REQUIRED_MISSING
REQUIRED_PRESENT
125
124
126
127
db<>fiddle here

Duplicate handling using case statement

I have a table tab1. Case 1:if no dups then display col1 data. Case 2: If I find duplicate in col1,then max of sr_no should be considered. While considering this, I need to consider only data='xyz' others should be ignored.
Tab1 structure(not exactly) Col1 Sr Data
Could you please help me with the query. Tried with case condition but not getting desired output.
For example
Col1. Sr. Data.
1234. 1. ABC
1234. 2. MNO
1234. 3. XYZ
1234. 4. ABC
2345. 1. ABC
OUTPUT
Col1. Sr. Data
1234. 3. XYZ (as it is duplicated, select max of sr and data=XYZ)
2345. 1. ABC (As it is unique no checks for max and data=XYZ)
I think you want row_number() with a priority for XYZ:
select t.*
from (select t.*,
row_number() over (partition by col1 order by (case when data = 'XYZ' then 1 else 2 end), sr desc) as seqnum
from t
) t
where seqnum = 1;
Your logic appears to be:
SELECT Col1, Sr, Data
FROM (
SELECT t.*,
CASE max_cnt
WHEN 1
THEN 1
ELSE ROW_NUMBER() OVER ( PARTITION BY Col1 ORDER BY Sr DESC )
END AS rn
FROM (
SELECT t.*,
MAX( cnt ) OVER ( PARTITION BY Col1 ) AS max_cnt
FROM (
SELECT t.*,
COUNT(*) OVER ( PARTITION BY Col1, Data ) AS cnt
FROM table_name t
) t
) t
WHERE max_cnt = 1
OR data = 'XYZ'
)
WHERE rn = 1;
Which, for the sample data:
CREATE TABLE table_name ( Col1, Sr, Data ) AS
SELECT 1234, 1, 'ABC' FROM DUAL UNION ALL
SELECT 1234, 2, 'MNO' FROM DUAL UNION ALL
SELECT 1234, 3, 'XYZ' FROM DUAL UNION ALL
SELECT 1234, 4, 'ABC' FROM DUAL UNION ALL
SELECT 2345, 1, 'ABC' FROM DUAL;
Outputs:
COL1
SR
DATA
1234
3
XYZ
2345
1
ABC
db<>fiddle here

How to to give preference to null value during select

I have a table with
Id value
1000 null
1000 En
1000 Fr
1000 Es
1001 En
1001 Fr
1001 Es
Output of the select query should be as follows. (Since 1000 has a null value only, select the row with null value)
Id value
1000 null
1001 En
1001 Fr
1001 Es
You can use NOT EXISTS and a correlated subquery to check for the non-existence of a NULL for an ID. Include these rows and also rows where value is NULL.
SELECT t1.id,
t1.value
FROM elbat t1
WHERE NOT EXISTS (SELECT *
FROM elbat t2
WHERE t2.id = t1.id
AND t2.value IS NULL)
OR t1.value IS NULL;
with
t (id, value) as (
select 1000, null from dual union all
select 1000, 'En' from dual union all
select 1000, 'Fr' from dual union all
select 1000, 'Es' from dual union all
select 1001, 'En' from dual union all
select 1001, 'Fr' from dual union all
select 1001, 'Es' from dual
)
select id, value
from (
select t.*,
dense_rank() over (partition by id order by nvl2(value, 1, 0)) rnk
from t
)
where rnk = 1
;
ID VA
---------- --
1000
1001 En
1001 Fr
1001 Es
Functions used in this query:
NVL2() https://docs.oracle.com/database/121/SQLRF/functions132.htm#SQLRF00685
DENSE_RANK() https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions043.htm
In the most recent versions of Oracle, you can actually do this without a subquery:
select t.*
from t
order by rank() over (partition by id order by (case when value is null then 1 else 2 end))
fetch first 1 row with ties;
Here is a db<>fiddle.
You can use analytical function as following:
Select id , value from
(Select t.*,
Coalesce(Sum(case when value is null then 1 end) over (partition by id), 0) as cnt
From your_table)
Where (cnt = 1 and value is null)
or cnt = 0
Cheers!!

SQL query to return data only if ALL necessary columns are present and not NULL

ID | Type | total
1 Purchase 12
1 Return 2
1 Exchange 5
2 Purchase null
2 Return 5
2 Exchange 1
3 Purchase 34
3 Return 4
3 Exchange 2
4 Purchase 12
4 Exchange 2
Above is sample data. What I want to return is:
ID | Type | total
1 Purchase 12
1 Return 2
1 Exchange 5
3 Purchase 34
3 Return 4
3 Exchange 2
So if a field is null in total or the values of Purchase, Return and Exchange are not all present for that ID, ignore that ID completely. How can I go about doing this?
You can use exists. I think you intend:
select t.*
from t
where exists (select 1
from t t2
where t2.id = t.id and t2.type = 'Purchase' and t2.total is not null
) and
exists (select 1
from t t2
where t2.id = t.id and t2.type = 'Exchange' and t2.total is not null
) and
exists (select 1
from t t2
where t2.id = t.id and t2.type = 'Return' and t2.total is not null
);
There are ways to "simplify" this:
select t.*
from t
where 3 = (select count(distinct t2.type)
from t t2
where t2.id = t.id and
t2.type in ('Purchase', 'Exchange', 'Return') and
t2.total is not null
);
I would write this as a join, without subqueries:
SELECT pur.id, pur.total AS Purchase, exc.total AS Exchange, ret.total AS Return
FROM MyTable as pur
INNER JOIN MyTable AS exc ON exc.id=pur.id AND exc.type='Exchange'
INNER JOIN MyTable AS ret ON ret.id=pur.id AND ret.type='Return'
WHERE pur.type='Purchase'
The inner join means that if any of the three rows with different values are not found for a given id, then no row is included in the result.
Analytic functions are a good way to solve this kind of problems. The base table is read just once, and no joins (explicit or implicit, as in EXISTS conditions or correlated subqueries) are needed.
In the solution below, we count distinct values of 'Purchase', 'Exchange' and 'Return' for each id while ignoring other values (assuming that is indeed the requirement), and separately count total nulls in the total column for each id. Then it becomes a trivial matter to select just the "desired" rows in an outer query.
with
test_data ( id, type, total ) as (
select 1, 'Purchase', 12 from dual union all
select 1, 'Return' , 2 from dual union all
select 1, 'Exchange', 5 from dual union all
select 2, 'Purchase', null from dual union all
select 2, 'Return' , 5 from dual union all
select 2, 'Exchange', 1 from dual union all
select 3, 'Purchase', 34 from dual union all
select 3, 'Return' , 4 from dual union all
select 3, 'Exchange', 2 from dual union all
select 4, 'Purchase', 12 from dual union all
select 4, 'Exchange', 2 from dual
)
-- end of test data; actual solution (SQL query) begins below this line
select id, type, total
from ( select id, type, total,
count( distinct case when type in ('Purchase', 'Return', 'Exchange')
then type end
) over (partition by id) as ct_type,
count( case when total is null then 1 end
) over (partition by id) as ct_total
from test_data
)
where ct_type = 3 and ct_total = 0
;
Output:
ID TYPE TOTAL
-- -------- -----
1 Exchange 5
1 Purchase 12
1 Return 2
3 Exchange 2
3 Purchase 34
3 Return 4
This also should work fine even if new values are added to type column
select * from t where
ID not in(select ID from t where
t.total is null or t.[Type] is null)

How to join a table after another join?

Hallo, I was trying to join the table after the another join table. My expected output is to have all the code in CODE1, CODE2, and CODE3 are shown just like the table below:
channel_division_group staff_id the_code total update_dt
---------------------- -------- -------- ----- ---------
CH3 101 CODE1 1 03-Mar-11
CH3 101 CODE1 1 03-Mar-11
CH3 101 CODE2 1 03-Mar-11
CH3 101 CODE3 1 03-Mar-11
But the actual result is the row with CODE3 was missing:
channel_division_group staff_id the_code total update_dt
---------------------- -------- -------- ----- ---------
CH3 101 CODE1 1 03-Mar-11
CH3 101 CODE1 1 03-Mar-11
CH3 101 CODE2 1 03-Mar-11
Here is source code for your reference:
select channel_division_group, staff_id, the_code, total, update_dt
from (
select 'CODE1' the_code from dual
union all
select 'CODE2' the_code from dual
union all
select 'CODE3' the_code from dual
)
left outer join (
select a.staff_id, a.code, update_dt,
case when m.update_dt is null then 0
else count(*)
end total,
case a.channel_division_group
when 'CH1' then 'CH1'
when 'CH2' then 'CH2'
else 'CH3'
end tableC
from (
select code, staff.staff_id, staff.channel_division_group
from (
select 'CODE1' code, '1' seq from dual
union all
select 'CODE2' code, '2' seq from dual
union all
select 'CODE3' code, '3' seq from dual
), code_staff staff
) a
left outer join tableM m
on a.code = m.decision and to_char(a.staff_id)=m.approval_id
group by a.staff_id, a.code, update_dt, a.channel_division_group
order by a.channel_division_group, a.staff_id
) app
on the_code=app.code and staff_id=app.staff_id
where update_dt between trunc(to_date('13-MAR-11'), 'MONTH') and trunc(to_date('13-MAR-11'))
group by channel_division_group, staff_id, the_code, total, update_dt
order by staff_id;
If I remove the where clause statement, the CODE3 will be shown but this doesn't filter within the date. Is this could be done when a join after another join together with the where clause?
THanks #!
you going complex join better to use "with statement" for sql join like below.
with
temp_t1 as (
select dummy c1 from dual
)
,temp_t2 as (
select dummy c1 from dual
)
select *
from temp_t1 a
,temp_t2 b
Its help you.