Duplicate handling using case statement - sql

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

Related

Create a duplicate row on top of Select statement

table TEST
id
Name
1
abc
2
xyz
In general i used to get records from below query
Select id,name from TEST.
id
Name
1
abc
2
xyz
but now i want to create a duplicate for each row on top my select query
expected output: please suggest how can i achieve result like below
id
Name
1
abc
1
abc
2
xyz
2
xyz
You may cross join your table with a sequence table containing how ever many copies you want. Here is an example using an inline sequence table:
SELECT t1.id, t1.Name
FROM yourTable t1
CROSS JOIN (
SELECT 1 AS seq FROM dual UNION ALL
SELECT 2 FROM dual UNION ALL
SELECT 3 FROM dual
) t2
WHERE t2.seq <= 2
ORDER BY t1.id;
To me, UNION (ALL) set operator seems to be quite simple.
Sample data:
SQL> select * from test;
ID NAME
---------- ----
1 abc
2 xyz
UNION ALL:
SQL> select * from test
2 union all
3 select * from test;
ID NAME
---------- ----
1 abc
2 xyz
1 abc
2 xyz
SQL>
CREATE table test(
id integer,
name VARCHAR2(4)
);
INSERT into test (id, name) VALUES (1,'ABC');
INSERT into test (id, name) VALUES (2,'XYZ');
with data as (select level l from dual connect by level <= 2)
select *
from test, data
order by id, l
/
One more option is LATERAL
SELECT t.*
FROM test
, LATERAL (
SELECT id, name FROM DUAL
union all
SELECT id, name FROM DUAL
) t
One option is using a self-join along with ROW_NUMBER analytic function such as
WITH t AS
(
SELECT t1.id, t1.name, ROW_NUMBER() OVER (PARTITION BY t1.id ORDER BY 0) AS rn
FROM test t1,
test t2
)
SELECT id, name
FROM t
WHERE rn <= 2
Demo

How to find out below output using oracle query

Input:
col1 col2
------------
1 A
2 1
3 B
4 2
5 C
6 Null
Output i want
Col1 Col2
__________
A 1
B 2
C Null
Oh, I see. Assuming there are no gaps in col1, you can use aggregation using arithmetic:
select max(case when mod(id, 2) = 0 then col2 end),
max(case when mod(id, 2) = 1 then col2 end)
from t
group by floor((id - 1) / 2);
Another method uses lead():
select col2, next_col2
from (select t.*, lead(col2) over (order by id) as next_col2
from t
) t
where mod(id, 2) = 1;
Use join to the next row of the same table and limit to every other row.
select t1.col2 col1, t2.col2
from tab t1
join tab t2 on t1.col1 = t2.col1 -1
where mod(t1.col1,2) = 1
C C
- -
A 1
B 2
C
Again, it assumes that your sequence in col1is without gaps.
This is an application of PIVOT, available since Oracle 11.1. (A much shorter solution is possible in Oracle 12.1 and higher, using MATCH_RECOGNIZE, but the question is explicitly tagged oracle11g.)
I use the analytic function ROW_NUMBER() to prepare the data, so that we don't need to assume anything about the ordering column - it may have gaps, and/or it doesn't even have to be a number column, it can be date or string or anything else that can be ordered.
Setup:
create table sample_data (col1, col2) as
select 1, 'A' from dual union all
select 2, '1' from dual union all
select 3, 'B' from dual union all
select 4, '2' from dual union all
select 5, 'C' from dual union all
select 6, null from dual
;
Query and output:
select col1, col2
from (
select ceil(row_number() over (order by col1) / 2) as r,
mod (row_number() over (order by col1) , 2) as c, col2
from sample_data
)
pivot (min(col2) for c in (1 as col1, 0 as col2))
order by r
;
COL1 COL2
---- ----
A 1
B 2
C
Just for fun, and for whoever may have this problem in Oracle 12 or higher, here is the match_recognize solution:
select col1, col2
from sample_data
match_recognize(
order by col1
measures x.col2 as col1, y.col2 as col2
pattern ( x y? )
define x as null is null
);

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!!

ORACLE get rows with condition value equals something but not equals to anything else

I have rows that look like .
OrderNo OrderStatus SomeOtherColumn
A 1
A 1
A 3
B 1 X
B 1 Y
C 2
C 3
D 2
I want to return all orders that have only one possible value of orderstatus. For e.g Here order B has only order status 1 SO result should be
B 1 X
B 1 Y
Notes:
Rows can be duplicated with same order status. For e.g. B here.
I am interested in the order having a very peculiar status for e.g. 1 here and not having any other status. So if B had a status of 3 at any point of time it is disqualified.
You can use not exists:
select t.*
from t
where not exists (select 1
from t t2
where t.orderno = t2.orderno and t.OrderStatus = t2.OrderStatus
);
If you just want the orders where this is true, you can use group by and having:
select orderno
from t
group by orderno
having min(OrderStatus) = max(OrderStatus);
If you only want a status of 1 then add max(OrderStatus) = 1 to the having clause.
Here is one way to do it. It does not handle the case where the status can be NULL; if that is possible, you will need to explain how you want it handled.
SQL> create table test_data ( orderno, status, othercol ) as (
2 select 'A', 1, null from dual union all
3 select 'A', 1, null from dual union all
4 select 'A', 3, null from dual union all
5 select 'B', 1, 'X' from dual union all
6 select 'B', 1, 'Y' from dual union all
7 select 'C', 2, null from dual union all
8 select 'C', 3, null from dual union all
9 select 'D', 2, null from dual
10 );
Table created.
SQL> variable input_status number
SQL> exec :input_status := 1
PL/SQL procedure successfully completed.
SQL> column orderno format a8
SQL> column othercol format a8
SQL> select orderno, status, othercol
2 from (
3 select t.*, count(distinct status) over (partition by orderno) as cnt
4 from test_data t
5 )
6 where status = :input_status
7 and cnt = 1
8 ;
ORDERNO STATUS OTHERCOL
-------- ---------- --------
B 1 X
B 1 Y
One way to handle NULL status (if that may happen), if in that case the orderno should be rejected (not included in the output), is to define the cnt differently:
count(case when status != :input_status or status is null then 1 end)
over (partition by orderno) as cnt
and in the outer query change the WHERE clause to a single condition,
where cnt = 0
Count distinct OrderStatus partitioned by OrderNo and show only rows where number equals one:
select OrderNo, OrderStatus, SomeOtherColumn
from ( select t.*, count(distinct orderstatus) over (partition by orderno) cnt
from t )
where cnt = 1
SQLFiddle demo
Just wanted to add something to Gordon's answer, using a stats function:
select orderno
from t
group by orderno
having variance(orderstatus) = 0;

Apply the distinct on 2 fields and also fetch the unique data for each columns

According to some weird requirement, i need to select the record where all the output values in both the columns should be unique.
Input looks like this:
col1 col2
1 x
1 y
2 x
2 y
3 x
3 y
3 z
Expected Output is:
col1 col2
1 x
2 y
3 z
or
col1 col2
1 y
2 x
3 z
I tried applying the distinct on 2 fields but that returns all the records as overall they are distinct on both the fields. What we want to do is that if any value is present in the col1, then it cannot be repeated in the col2.
Please let me know if this is even possible and if yes, how to go about it.
Great problem! Armunin has picked up on the deeper structural issue here, this is a recursive enumerable problem description and can only be resolved with a recursive solution - base relational operators (join/union/etc) are not going to get you there. As Armunin cited, one approach is to bring out the PL/SQL, and though I haven't checked it in detail, I'd assume the PL/SQL code will work just fine. However, Oracle is kind enough to support recursive SQL, through which we can build the solution in just SQL:
-- Note - this SQL will generate every solution - you will need to filter for SOLUTION_NUMBER=1 at the end
with t as (
select 1 col1, 'x' col2 from dual union all
select 1 col1, 'y' col2 from dual union all
select 2 col1, 'x' col2 from dual union all
select 2 col1, 'y' col2 from dual union all
select 3 col1, 'x' col2 from dual union all
select 3 col1, 'y' col2 from dual union all
select 3 col1, 'z' col2 from dual
),
t0 as
(select t.*,
row_number() over (order by col1) id,
dense_rank() over (order by col2) c2_rnk
from t),
-- recursive step...
t1 (c2_rnk,ids, str) as
(-- base row
select c2_rnk, '('||id||')' ids, '('||col1||')' str
from t0
where c2_rnk=1
union all
-- induction
select t0.c2_rnk, ids||'('||t0.id||')' ids, str||','||'('||t0.col1||')'
from t1, t0
where t0.c2_rnk = t1.c2_rnk+1
and instr(t1.str,'('||t0.col1||')') =0
),
t2 as
(select t1.*,
rownum solution_number
from t1
where c2_rnk = (select max(c2_rnk) from t1)
)
select solution_number, col1, col2
from t0, t2
where instr(t2.ids,'('||t0.id||')') <> 0
order by 1,2,3
SOLUTION_NUMBER COL1 COL2
1 1 x
1 2 y
1 3 z
2 1 y
2 2 x
2 3 z
You can use a full outer join to merge two numbered lists together:
SELECT col1, col2
FROM ( SELECT col1, ROW_NUMBER() OVER ( ORDER BY col1 ) col1_num
FROM your_table
GROUP BY col1 )
FULL JOIN
( SELECT col2, ROW_NUMBER() OVER ( ORDER BY col2 ) col2_num
FROM your_table
GROUP BY col2 )
ON col1_num = col2_num
Change ORDER BY if you require a different order and use ORDER BY NULL if you're happy to let Oracle decide.
What would be the result if another row of
col1 value as 1 and col2 value as xx ?
A single row is better in this case:
SELECT DISTINCT TO_CHAR(col1) FROM your_table
UNION ALL
SELECT DISTINCT col2 FROM your_table;
My suggestion is something like this:
begin
EXECUTE IMMEDIATE 'CREATE global TEMPORARY TABLE tmp(col1 NUMBER, col2 VARCHAR2(50))';
end;
/
DECLARE
cur_print sys_refcursor;
col1 NUMBER;
col2 VARCHAR(50);
CURSOR cur_dist
IS
SELECT DISTINCT
col1
FROM
ttable;
filtered sys_refcursor;
BEGIN
FOR rec IN cur_dist
LOOP
INSERT INTO tmp
SELECT
col1,
col2
FROM
ttable t1
WHERE
t1.col1 = rec.col1
AND t1.col2 NOT IN
(
SELECT
tmp.col2
FROM
tmp
)
AND t1.col1 NOT IN
(
SELECT
tmp.col1
FROM
tmp
)
AND ROWNUM = 1;
END LOOP;
FOR rec in (select col1, col2 from tmp) LOOP
DBMS_OUTPUT.PUT_LINE('col1: ' || rec.col1 || '|| col2: ' || rec.col2);
END LOOP;
EXECUTE IMMEDIATE 'DROP TABLE tmp';
END;
/
May still need some refining, I am especially not happy with the ROWNUM = 1 part.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE tbl ( col1, col2 ) AS
SELECT 1, 'x' FROM DUAL
UNION ALL SELECT 1, 'y' FROM DUAL
UNION ALL SELECT 2, 'x' FROM DUAL
UNION ALL SELECT 2, 'y' FROM DUAL
UNION ALL SELECT 3, 'x' FROM DUAL
UNION ALL SELECT 3, 'y' FROM DUAL
UNION ALL SELECT 4, 'z' FROM DUAL;
Query 1:
WITH c1 AS (
SELECT DISTINCT
col1,
DENSE_RANK() OVER (ORDER BY col1) AS rank
FROM tbl
),
c2 AS (
SELECT DISTINCT
col2,
DENSE_RANK() OVER (ORDER BY col2) AS rank
FROM tbl
)
SELECT c1.col1,
c2.col2
FROM c1
FULL OUTER JOIN c2
ON ( c1.rank = c2.rank)
ORDER BY COALESCE( c1.rank, c2.rank)
Results:
| COL1 | COL2 |
|------|--------|
| 1 | x |
| 2 | y |
| 3 | z |
| 4 | (null) |
And to address the additional requirement:
What we want to do is that if any value is present in the col1, then it cannot be repeated in the col2.
Query 2:
WITH c1 AS (
SELECT DISTINCT
col1,
DENSE_RANK() OVER (ORDER BY col1) AS rank
FROM tbl
),
c2 AS (
SELECT DISTINCT
col2,
DENSE_RANK() OVER (ORDER BY col2) AS rank
FROM tbl
WHERE col2 NOT IN ( SELECT TO_CHAR( col1 ) FROM c1 )
)
SELECT c1.col1,
c2.col2
FROM c1
FULL OUTER JOIN c2
ON ( c1.rank = c2.rank)
ORDER BY COALESCE( c1.rank, c2.rank)