Don't select duplicates based on a column in a union query - sql

I have a query which does an UNION :
select deal_id, codptf from acc_deals where run_id = 1
union
select deal_id, codptf from acc_deals where run_id = 2
However, I find myself with this result :
AAAA;1234
AAAA;3456
BBBB;4569
There is a duplicate row, aka the rows 1 and 2 (same deal_id).
How can I exclude one of the duplicate rows ?

it looks that you need full outer join here
create table test(id varchar2(10), val int, run_id int);
insert into test values('AAAA', 1234, 1);
insert into test values('BBBB', 4569, 1);
insert into test values('AAAA', 3456, 2);
insert into test values('CCCC', 1111, 2);
select
nvl(t1.id, t2.id) as id, nvl(t1.val, t2.val) as val
from
(select * from test where run_id = 1) t1
full join
(select * from test where run_id = 2) t2
on t1.id = t2.id
ID VAL
1 AAAA 1234
2 CCCC 1111
3 BBBB 4569

As an alternative to #are's solution, you could do a group by instead:
create table test(id varchar2(10), val int, run_id int);
insert into test values('AAAA', 1234, 1);
insert into test values('BBBB', 4569, 1);
insert into test values('AAAA', 3456, 2);
insert into test values('CCCC', 1111, 2);
insert into test values('DDDD', 8958, 1);
insert into test values('DDDD', 2345, 2);
commit;
select id,
max(val) max_val,
min(val) min_val,
max(val) keep (dense_rank first order by run_id) val_from_first_run_id,
max(val) keep (dense_rank last order by run_id) val_from_last_run_id
from test
where run_id in (1, 2)
group by id;
ID MAX_VAL MIN_VAL VAL_FROM_FIRST_RUN_ID VAL_FROM_LAST_RUN_ID
---------- ---------- ---------- --------------------- --------------------
AAAA 3456 1234 1234 3456
BBBB 4569 4569 4569 4569
CCCC 1111 1111 1111 1111
DDDD 8958 2345 8958 2345
You'll note that there are several columns there - I've simply listed out a few of the options depending on which value you wanted to keep. It's up to you to decide which of the columns you're going to use, since you didn't specify which value you wanted displayed in the event of duplicates.

Related

SELECT query based on mentioned result table - Oracle SQL

I have 3 tables.
TABLE1 with one column ROLL_NO
CREATE TABLE TABLE1 (ROLL_NO NUMBER(3) NOT NULL);
TABLE2 with 3 columns ROLL_NO, CLASS, SEC
CREATE TABLE TABLE2 (ROLL_NO NUMBER(3) NOT NULL, CLASS NUMBER(3) NOT NULL, SEC NUMBER(3) NOT NULL);
TABLE3 with 3 columns ROLL_NO, CODE, AMT
CREATE TABLE TABLE3 (ROLL_NO NUMBER(3) NOT NULL, CODE VARCHAR2(3) NOT NULL, AMT NUMBER(3) NOT NULL);
INSERT INTO TABLE1 VALUES (101);
INSERT INTO TABLE1 VALUES (102);
INSERT INTO TABLE1 VALUES (103);
INSERT INTO TABLE1 VALUES (104);
----------------------------------
INSERT INTO TABLE2 VALUES (101,1, 12);
INSERT INTO TABLE2 VALUES (102,1, 12);
INSERT INTO TABLE2 VALUES (103,1, 12);
INSERT INTO TABLE2 VALUES (104,1, 12);
--------------------------------------
INSERT INTO TABLE3 VALUES (101, 'A2', 100);
INSERT INTO TABLE3 VALUES (101, '10', 100);
INSERT INTO TABLE3 VALUES (102, 'B3', 200);
INSERT INTO TABLE3 VALUES (102, '10', 200);
INSERT INTO TABLE3 VALUES (103, '04', 300);
The SQL query which mentioned below:
SELECT T1.ROLL_NO,
T2.CLASS,
T2.SEC,
NVL(T3.CODE,0) AS CODE,
NVL(T3.AMT, 0) AS AMT
FROM TABLE1 T1
JOIN TABLE2 T2 ON T1.ROLL_NO = T2.ROLL_NO
LEFT JOIN TABLE3 T3 ON T1.ROLL_NO = T3.ROLL_NO
WHERE T1.ROLL_NO IN (101,102,103,104);
If we don't find any record i.e. CODE and AMT for particular ROLL_NO, by default we are assigning as 0.
The result for above query:
ROLL_NO CLASS SEC CODE AMT
-------------------------------------
101 1 12 A2 100
101 1 12 10 100
102 1 12 B3 200
102 1 12 10 200
103 1 12 4 300
104 1 12 0 0
I am looking for a query in such a way that
a) if particular ROLL_NO has CODE 10 and also additional CODE values other than 10 then get that row which has CODE as 10 in the result table.
b) if particular ROLL_NO don't have CODE 10 but has other additional CODE values then get that row in the result table.
In previous table, ROLL_NO 101 and 102 comes under case 'a' and 103, 104 comes under case 'b'
Final result should be
ROLL_NO CLASS SEC CODE AMT
-------------------------------------
101 1 12 10 100
102 1 12 10 200
103 1 12 4 300
104 1 12 0 0
I am looking for a query to get the above result but I am not able to get it. Till now, I tried using RANK function by partitioning on ROLL_NO and order by CODE in descending and select 1st row in each partition but it doesn't work if particular ROLL_NO have additional code greater than 10.
You can use analytic function ROW_NUMBER() and it should work:
select roll_no, class, sec, code, amt
from (SELECT T1.ROLL_NO,
T2.CLASS,
T2.SEC,
NVL(T3.CODE,0) AS CODE,
NVL(T3.AMT, 0) AS AMT,
--analytic function
row_number() over (partition by t1.roll_no order by NVL(T3.CODE,0) desc) as row_num
FROM TABLE1 T1
JOIN TABLE2 T2 ON T1.ROLL_NO = T2.ROLL_NO
LEFT JOIN TABLE3 T3 ON T1.ROLL_NO = T3.ROLL_NO
WHERE T1.ROLL_NO IN (101,102,103,104)) t
where t.row_num = 1;
You simply need a ROW_NUMBER() function to achieve your desired result -
SELECT T1.ROLL_NO,
T2.CLASS,
T2.SEC,
NVL(T3.CODE,0) AS CODE,
NVL(T3.AMT, 0) AS AMT
FROM TABLE1 T1
JOIN TABLE2 T2 ON T1.ROLL_NO = T2.ROLL_NO
LEFT JOIN (SELECT ROLL_NO, CODE, AMT,
ROW_NUMBER() OVER(PARTITION BY ROLL_NO ORDER BY CODE DESC) RN
-- If you have code greater than 10 also you have to use a CASE statement instead of simple order by clause
FROM TABLE3) T3 ON T1.ROLL_NO = T3.ROLL_NO
AND RN = 1
WHERE T1.ROLL_NO IN (101,102,103,104);

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

Find number of non-unique column values within a group where a second column value is all the same

I have two tables.
Table 1:
id_a,
id_b,
id_t
Table 2:
id_t,
name
If Table 2 name starts with a, I need to find out of anything
with matching id_ts also have matching id_as.
If Table 2 name starts with b, I need to find out of any row
with matching id_ts also have matching id_bs.
I need to know how many times these matches occur.
Table 1
id_a
id_b
id_t
1
0
123
1
0
123
2
0
123
0
4
456
0
4
456
0
5
456
0
5
456
0
5
456
0
6
456
0
7
456
Table 2
id_t
name
123
aaq
456
bws
So in this example, I want to see a result like
id_t
name
num_non_unique
123
aaq
1
456
bws
2
My current code is this:
SELECT
t2.id_t, t2.name, count(t1.*) AS num_non_unique
FROM
Table 2 AS t2
JOIN Table 1 as t1 ON t2.id_t = t1.id_t
WHERE
(t2.name like 'a%' and t1.id_a in (SELECT id_a FROM t1 GROUP BY id_a, id_t HAVING count(*) > 1))
OR (t2.name like 'b%' AND t1.id_b IN (SELECT id_b FROM t1 GROUP BY id_b, id_t HAVING count(*) > 1))
GROUP BY t1.name, t1.id_t
This doesn't currently give me the results I want.
With this code I seem to get the count of all available rows for id_b, and 1 + non_uniques for id_a (so with one non-unique, the value is 2, otherwise the column has a 1).
Any help is appreciated!
Schema and insert statements:
create table table_1(id_a int, id_b int, id_t int);
insert into table_1 values(1, 0, 123);
insert into table_1 values(1, 0, 123);
insert into table_1 values(2, 0, 123);
insert into table_1 values(0, 4, 456);
insert into table_1 values(0, 4, 456);
insert into table_1 values(0, 5, 456);
insert into table_1 values(0, 5, 456);
insert into table_1 values(0, 5, 456);
insert into table_1 values(0, 6, 456);
insert into table_1 values(0, 7, 456);
create table table_2 (id_t int, name varchar(50));
insert into table_2 values(123, 'aaq');
insert into table_2 values(456, 'bws');
Query:
with case1 as
(
select id_t, id_a
from table_1
group by id_t, id_a
having count(*)=1
),
case2 as
(
select id_t,id_b
from table_1
group by id_t,id_b
having count(*)=1
)
select id_t,name,
(case when t2.name like 'a%' then (select count(*) from case1 where case1.id_t=t2.id_t) when t2.name like 'b%' then (select count(*) from case2 where case2.id_t=t2.id_t) end)num_non_unique
from table_2 as t2
Output:
id_t
name
num_non_unique
123
aaq
1
456
bws
2
db<fiddle here
If I understand correctly, you want the count -- for each row in table_2 -- of the corresponding rows in table_1 where id_b only appears once.
One method is:
select t2.*, coalesce(cnt, 0)
from table_2 t2 left join
(select id_a, count(*) as cnt
from (select id_a, id_b
from table_1
group by id_a, id_b
having count(*) = 1
) t1
group by id_a
) t1
on t1.id_a = t2.id_a;
Try this Code:
select name,id_ts,sum(count_) "num_non_unique" from
(
select t1.id_a,t1.id_b,t1.id_ts,t2.name, count(*) "count_"
from tab1 t1
inner join tab2 t2 on t1.id_ts=t2.id_t
group by 1,2,3,4
having count(*)=1
)tab
group by 1,2
Explanation:
As you have mentioned in your question that one column value will always be same based of name starting character. So no need to check the starting of the name in your query.
Simply we will check the count of unique row and filter it by using condition having count(*)=1.
After getting the unique row simply group it to get the count of rows group by name and id_ts
DEMO

auto increment logic in DML

I have two tables test2 and test_hist. i want to load data into test_hist from test2 but it is failing due to unique constraint .
CREATE TABLE TEST2 (ID NUMBER , TEXT VARCHAR2(10));
create table test_hist (id number , text varchar2(10) , constraint t_pk PRIMARY key (id , text));
INSERT INTO TEST2 VALUES(100 , '20180909-I');
INSERT INTO TEST2 VALUES(101 , '20180909-I');
INSERT INTO TEST2 VALUES(102 , '20180809-I');
INSERT INTO TEST2 VALUES(100 , '20180909-I');
COMMIT;
INSERT INTO test_hist SELECT ID , TEXT FROM TEST2;
I want to append the TEXT field with auto increment number whenever there is a duplicate like below.
expected OUTPUT
ID TEXT
100 20180909-I
101 20180909-I
102 20180809-I
100 20180909-I-1
100 20180909-I-2
102 20180809-I-1
Could any one help me to achieve this.
Thanks in Advance
if i execute the insert statements multiple times it should inserted into the test_hist with autoincrement text .
e.g
insert into test_hist
select id,
text || case when row_number() over (partition by id, text order by null) > 1
then (1 - row_number() over (partition by id, text order by null)) end
from test2;
9 rows inserted.
select *
from test_hist
order by id, text;
ID TEXT
---------- ------------
100 20180909-I
100 20180909-I-1
100 20180909-I-2
100 20180909-I-3
101 20180909-I
101 20180909-I-1
102 20180809-I
102 20180809-I-1
102 20180809-I-2
Same basic idea as #Barbaros, but arranged slightly differently, and with the second table's column size increased so it can hold the amended value:
create table test2 (
id number, text varchar2(10)
);
create table test_hist (id number,
text varchar2(12), -- increased size to 12; may need to be larger
constraint t_pk primary key (id , text)
);
insert into test2 values(100 , '20180909-I');
insert into test2 values(101 , '20180909-I');
insert into test2 values(102 , '20180809-I');
insert into test2 values(100 , '20180909-I');
insert into test2 values(100 , '20180909-I');
insert into test2 values(102 , '20180809-I');
Then the same analytic function, in one level if you don't mind repeating it, and including all the PK columns in the partition-by clause:
insert into test_hist
select id,
text || case when row_number() over (partition by id, text order by null) > 1
then (1 - row_number() over (partition by id, text order by null)) end
from test2;
6 rows inserted.
select *
from test_hist
order by id, text;
ID TEXT
---------- ------------
100 20180909-I
100 20180909-I-1
100 20180909-I-2
101 20180909-I
102 20180809-I
102 20180809-I-1
If you actually have more columns in your real scenario and there is another non-PK column in the original table which you want to influence the order the history rows are incremented, you can just use that in the function's order by instead of null, which is just a dummy placeholder really.
If it needs to keep doing the same thing on multiple inserts (which suggests a data model issue even more than the original requirement did) then you'll need to count existing matches in the history table too.
Starting from the same original data as before and an empty historytable:
insert into test_hist
select id,
text || case when appearance > 1 then (1 - appearance) end
from (
select t.id,
t.text,
row_number() over (partition by t.id, t.text order by null) + (
select count(*) from test_hist th
where th.id = t.id
and th.text like t.text || '%'
) as appearance
from test2 t
);
6 rows inserted.
select *
from test_hist
order by id, text;
ID TEXT
---------- ------------
100 20180909-I
100 20180909-I-1
100 20180909-I-2
101 20180909-I
102 20180809-I
102 20180809-I-1
6 rows selected.
and running the same statement a second time:
insert into test_hist
select id,
text || case when appearance > 1 then (1 - appearance) end
from (
select t.id,
t.text,
row_number() over (partition by t.id, t.text order by null) + (
select count(*) from test_hist th
where th.id = t.id
and th.text like t.text || '%'
) as appearance
from test2 t
);
6 rows inserted.
select *
from test_hist
order by id, text;
ID TEXT
---------- ------------
100 20180909-I
100 20180909-I-1
100 20180909-I-2
100 20180909-I-3
100 20180909-I-4
100 20180909-I-5
101 20180909-I
101 20180909-I-1
102 20180809-I
102 20180809-I-1
102 20180809-I-2
102 20180809-I-3
12 rows selected.
There are probably ways to optimise it so you don't have to hit the history table so often, but this may give you a starting point to work from.
The use of like really means this only works if the text is always the same length, or at least you can't have values that are extensions of other values; otherwise you'll get more matches than you want. From your sample data, at least, that doesn't look like an issue. You could probably work around that by switching to regexp_like if necessary, depending on your actual data.
You may try the following :
select ID, text||decode(rn-1,0,null,'-'||(rn-1)) as text
from
(
with test2(rnk,ID,text) as
(
select 1, 100 , '20180909-I' from dual union all
select 2, 101 , '20180909-I' from dual union all
select 3, 102 , '20180809-I' from dual union all
select 4, 100 , '20180909-I' from dual union all
select 5, 100 , '20180909-I' from dual union all
select 6, 102 , '20180909-I' from dual
)
select t.ID, t.rnk,
t.text, row_number() over (partition by ID order by Text,ID) as rn
from test2 t
)
order by rn, rnk
Rextester Demo

merging rows into 1 column

I have a table with 2 columns
Input
Col 1 ---- Col 2
1 ---- aaaa
1 ---- bbbb
1 ---- cccc
2 ---- dddd
2 ---- eeee
2 ---- ffff
2 ---- gggg
Output
Col 1 ---- Col 2
1 ---- aaaabbbbcccc
2 ---- ddddeeeeffffgggg
I was thinking of doing several self joins, but doesnt seem efficient. Any ideas on how the sql has to be written?
Ok, I'll bite. Instead of stragg, try listagg (in 11.2):
create table tst1
(
pid number,
val varchar2(10)
);
insert into tst1 values(1, 'Rec1');
insert into tst1 values(1, 'Rec2');
insert into tst1 values(1, 'Rec3');
insert into tst1 values(2, 'Rec1');
insert into tst1 values(2, 'Rec2');
commit;
select pid, listagg(val, ':') within group(order by val) as "The List"
from tst1
group by pid;
And you get:
pid The List
1 Rec1:Rec2:Rec3
2 Rec1:Rec2
If you change the order by to "order by val desc" you'd get
pid The List
1 Rec3:Rec2:Rec1
2 Rec2:Rec1
This is a version that will work in Oracle 9i and up.
create table foo (
key_column number,
val_column varchar2(4)
);
insert into foo values (1, 'aaaa');
insert into foo values (1, 'bbbb');
insert into foo values (1, 'cccc');
insert into foo values (2, 'dddd');
insert into foo values (2, 'eeee');
insert into foo values (2, 'ffff');
insert into foo values (2, 'gggg');
select key_column
, replace(max(sys_connect_by_path(val_column, ',')), ',') combined
from (select key_column
, val_column
, row_number() over (partition by key_column order by val_column) cur
, row_number() over (partition by key_column order by val_column) - 1 prev
from foo) foo
group by key_column
connect by prior cur = prev and prior key_column = key_column
start with cur = 1;
key_column | val_column
--------------------------
1 | aaaabbbbcccc
2 | ddddeeeeffffgggg