How to join two tables when there's no coincidence? - sql

I have two tables that I want to join. I've tried an usual left and right join but neither gives the result I want.
TABLE A
ID_A VALUE_A
-----------------
A 1
B 2
TABLE B
ID_B ID_A VALUE_B
-------------------------
90 A 1
90 C 1
90 E 1
91 A 1
91 B 1
92 B 1
92 E 1
92 F 1
I want to get this result:
ID_A VALUE_A ID_B ID_A VALUE_B
-------------------------------------------------
A 1 90 A 1
B 2 90 NULL NULL
A 1 91 A 1
B 2 91 B 1
A 1 92 NULL NULL
B 2 92 B 1

If I understand correctly, you want all combinations of id_a and value_a from the first table along with all distinct id_b from the second table. If so:
select iv.id_a, iv.value_a, ib.id_b, b.id_a, b.value_b
from (select distinct id_a, value_a from a) iv cross join
(select distinct id_b from b) ib left join
b
on b.id_b = ib.id_b and b.id_a = iv.id_a;
The cross join generates the rows. The left join brings in the additional columns.

I usually break things like this down into CTEs:
DDL
use tempdb
CREATE TABLE Table1
([ID_A] varchar(1), [VALUE_A] int)
;
INSERT INTO Table1
([ID_A], [VALUE_A])
VALUES
('A', 1),
('B', 2)
;
CREATE TABLE Table2
([ID_B] int, [ID_A] varchar(1), [VALUE_B] int)
;
INSERT INTO Table2
([ID_B], [ID_A], [VALUE_B])
VALUES
(90, 'A', 1),
(90, 'C', 1),
(90, 'E', 1),
(91, 'A', 1),
(91, 'B', 1),
(92, 'B', 1),
(92, 'E', 1),
(92, 'F', 1)
;
Answer
with a as (
select distinct id_b
from Table2
),
b as (
select id_a, value_a, id_b
from Table1 cross join a
)
select b.id_a, b.value_a, b.id_b, t2.id_a, t2.value_b
from b left join Table2 t2
on b.id_a = t2.id_a
and b.id_b = t2.id_b
Results
+------+---------+------+------+---------+
| id_a | value_a | id_b | id_a | value_b |
+------+---------+------+------+---------+
| A | 1 | 90 | A | 1 |
| B | 2 | 90 | NULL | NULL |
| A | 1 | 91 | A | 1 |
| B | 2 | 91 | B | 1 |
| A | 1 | 92 | NULL | NULL |
| B | 2 | 92 | B | 1 |
+------+---------+------+------+---------+

I couldn't resolve the exact logic and couldn't match the results exactly as desired , but presume you'd like to get something like :
SELECT a.ID_A, COALESCE(a.VALUE_A,b.VALUE_B) VALUE_A, b.ID_B, a.ID_A,
(CASE WHEN a.ID_A IS NULL THEN a.ID_A ELSE CAST(b.VALUE_B as VARCHAR(1)) END)
as VALUE_B
FROM TABLE_A a FULL OUTER JOIN TABLE_B b
ON ( a.ID_A = b.ID_A )
GROUP BY a.ID_A, a.VALUE_A, b.ID_B, a.ID_A, b.VALUE_B
ORDER BY 3, 2, 1;
SQL Fiddle Demo

Try this:
SELECT A.ID_A , A.VALUE_A , B.ID_B , B.ID_A , B.VALUE_B
FROM TABLE_A A
LEFT OUTER JOIN TABLE_B B
ON A.ID_A = B.ID_A ;
EDIT: Typos corrected following sticky bit note (thanks!!).

Related

Get Count of records - Oracle

I'm trying to get count of records from table by joining 2 or more table. Suppose I have 3 table like follows,
Table A
Column 1 Column 2
121 XX
123 XX
124 A0
125 A2
126 XX
Table B
Column 1
A0
A1
A2
A3
Table C
Column 1 Column 2
121 A0
122 A1
123 A0
124 A0
125 A2
126 A3
From these I need count result as follows,
Column 1 Column 2
XX,A0 2
XX,A1 0
XX,A2 0
XX,A3 1
A0,A0 1
A1,A1 0
A2,A2 1
A3,A3 0
Here I have 121 and 123 with XX in table A and same 121,123 in table C with A0, so count should be 2. Similarly 124 have A0 in table A and A0 in table C, so count should 1 and if no record matched with any column 2 it should have 0.
I tried with below query it is not returing as expected,
select b.column2 ||','|| c.column2 column , count(a.column1) count
from table A a
join table c c
on a.column1=c.column1
join table b b
on b.column1=c.column2
group by b.column2 ||','|| c.column2
order by b.column2 ||','|| c.column2
first you would want a cartesian product to get the set of all possible combinations betweeen tablea and tableb. I changed the column names to something more readable.
create table tablea(id int, name varchar2(10));
create table tableb(name varchar2(10));
create table tablec(id int, name varchar2(10));
insert into tablea
select 121,'XX' from dual union all
select 123,'XX' from dual union all
select 124,'A0' from dual union all
select 125,'A2' from dual union all
select 126,'XX' from dual
insert into tableb
select 'A0' from dual union all
select 'A1' from dual union all
select 'A2' from dual union all
select 'A3' from dual
insert into tablec
select 121,'A0' from dual union all
select 122,'A1' from dual union all
select 123,'A0' from dual union all
select 124,'A0' from dual union all
select 125,'A2' from dual union all
select 126,'A3' from dual
--Gets all possible combination of tablea and tableb
select distinct a.name as a_name,b.name as b_name
from tablea a
join tableb b
on 1=1
Then you would do a group by on these combination fields as follows
with data
as (select distinct a.name as a_name,b.name as b_name
from tablea a
join tableb b
on 1=1
)
,tablec_data
as (select c.name c_name,a.name a_name
from tablec c
join tablea a
on c.id=a.id
)
select max(m.a_name||','||m.b_name) as val_name,count(n.c_name) as cnt
from data m
left join tablec_data n
on m.a_name=n.a_name
and m.b_name=n.c_name
group by m.a_name,m.b_name
order by m.a_name desc,m.b_name
Here is a db fiddle link
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=9e573822ecc1087e5871c80b586d1116
+----------+-----+
| VAL_NAME | CNT |
+----------+-----+
| XX,A0 | 2 |
| XX,A1 | 0 |
| XX,A2 | 0 |
| XX,A3 | 1 |
| A2,A0 | 0 |
| A2,A1 | 0 |
| A2,A2 | 1 |
| A2,A3 | 0 |
| A0,A0 | 1 |
| A0,A1 | 0 |
| A0,A2 | 0 |
| A0,A3 | 0 |
+----------+-----+
Table A should be used instead of Table B for combining the values. Table B doesn't have column Colunn2.
Also, Remember alias name for a.column2 ||','|| c.column2 should not be column as this is reserved keyword in oracle. So you can use column2 or something else.
EDIT: Following will give 0 count for not matched records.
select t1.column2,
case when t2.count1 is not null then t2.count1 else t1.count1 end as count1
from (
(select distinct a.column2||','||b.column1 as column2,
0 as count1 from tableA a join tableB b on a.column2='XX'
union
select distinct c.column2||','||b.column1 as column2,
0 as count1 from tableC c join tableB b on c.column2=b.column1
)t1
left join
(select a.Column2 ||','|| c.Column2 as column2,
count(*) count1 from TableA a join TableC c on a.column1=c.column1
join TableB B on b.column1=c.column2 group by a.column2 ||','|| c.column2
)t2
on t1.column2=t2.column2
)
order by
t1.column2
Output:
A0,A0 1
A1,A1 0
A2,A2 1
A3,A3 0
XX,A0 2
XX,A1 0
XX,A2 0
XX,A3 1
At first prepare key columns, use simple union. Then use this keys to construct join. Left join is used because we need zeros too:
with keys as (select 'XX' ka, column1 kb from b union all select column1, column1 from b)
select ka, kb, sum(case when c.column2 is not null then 1 else 0 end) cnt
from keys
left join a on ka = a.column2
left join c on kb = c.column2 and a.column1 = c.column1
group by ka, kb
order by ka, kb
dbfiddle demo

count combination of columns in postgresql matrix

I have a table in postgres like below
I want an sql in postgres that count a combination of 2 columns that has YY
Expecting an output like
Combination Count
AB 2
AC 1
AD 2
AZ 1
BC 1
BD 3
BZ 2
CD 2
CZ 0
DZ 1
Can anyone help me?
WITH stacked AS (
SELECT id
, unnest(array['A', 'B', 'C', 'D', 'Z']) AS col_name
, unnest(array[a, b, c, d, z]) AS col_value
FROM test t
)
SELECT combo, sum(cnt) AS count
FROM (
SELECT t1.id, t1.col_name || t2.col_name AS combo
, (CASE WHEN t1.col_value = 'Y' AND t2.col_value = 'Y' THEN 1 ELSE 0 END) AS cnt
FROM stacked t1
INNER JOIN stacked t2
ON t1.id = t2.id
AND t1.col_name < t2.col_name) t3
GROUP BY combo
ORDER BY combo
yields
| combo | count |
|-------+-------|
| AB | 2 |
| AC | 1 |
| AD | 2 |
| AZ | 2 |
| BC | 1 |
| BD | 3 |
| BZ | 2 |
| CD | 2 |
| CZ | 0 |
| DZ | 1 |
The unnesting recipe for unpivoting the table comes from Stew's post, here.
To count occurrances of YYY among 3 columns you could use:
WITH stacked AS (
SELECT id
, unnest(array['A', 'B', 'C', 'D', 'Z']) AS col_name
, unnest(array[a, b, c, d, z]) AS col_value
FROM test t
)
SELECT combo, sum(cnt) AS count
FROM (
SELECT t1.id, t1.col_name || t2.col_name || t3.col_name AS combo
, (CASE WHEN t1.col_value = 'Y'
AND t2.col_value = 'Y'
AND t3.col_value = 'Y' THEN 1 ELSE 0 END) AS cnt
FROM stacked t1
INNER JOIN stacked t2
ON t1.id = t2.id
INNER JOIN stacked t3
ON t1.id = t3.id
AND t1.col_name < t2.col_name
And t2.col_name < t3.col_name
) t3
GROUP BY combo
ORDER BY combo
;
which yields
| combo | count |
|-------+-------|
| ABC | 0 |
| ABD | 1 |
| ABZ | 2 |
| ACD | 1 |
| ACZ | 0 |
| ADZ | 1 |
| BCD | 1 |
| BCZ | 0 |
| BDZ | 1 |
| CDZ | 0 |
Or, to handle combinations of N columns, you could use WITH RECURSIVE:
For example, for N = 3,
WITH RECURSIVE result AS (
WITH stacked AS (
SELECT id
, unnest(array['A', 'B', 'C', 'D', 'Z']) AS col_name
, unnest(array[a, b, c, d, z]) AS col_value
FROM test t)
SELECT id, array[col_name] AS path, array[col_value] AS path_val, col_name AS last_name
FROM stacked
UNION
SELECT r.id, path || s.col_name, path_val || s.col_value, s.col_name
FROM result r
INNER JOIN stacked s
ON r.id = s.id
AND s.col_name > r.last_name
WHERE array_length(r.path, 1) < 3) -- Change 3 to your value for N
SELECT combo, sum(cnt)
FROM (
SELECT id, array_to_string(path, '') AS combo, (CASE WHEN 'Y' = all(path_val) THEN 1 ELSE 0 END) AS cnt
FROM result
WHERE array_length(path, 1) = 3) t -- Change 3 to your value for N
GROUP BY combo
ORDER BY combo
Note that N = 3 is used in 2 places in the SQL above.
I would do this using a lateral join:
with vals as (
select v.*
from t cross join lateral
(values ('A', A), ('B', B), ('C', C), ('D', D), ('Z', Z)
) v(which, val)
)
select (v1.which || v2.which) as combo,
sum( (val = 'Y')::int ) as count
from vals v1 join
vals v2
on v1.which < v2.which
group by combo
order by combo;
I consider lateral joins to be a more direct way to unpivot the values. There is no need to convert the values to an array an unnest, much less unnest two arrays and align the values.

Query Count Rows Join Oracle

I've been struggling trying to do this query, this is what I have/need:
Two tables, Table A and Table B, for each row in table A there might be N rows in table B. But I need to find the rows from A that have exactly two rows in B where one of them the type( column in B) starts with 'PYT' and the other one has a column null, also I need the amount from table B to be from the latest row( DATEP in table B) I've been trying to do it, but I've found some issues, this is what I have so far:
SELECT
A.TYPE, A.NMRAD,A.ID, B.AMOUNT
FROM
TABLE_A A
JOIN TABLE_B B ON A.ID = B.ID_A AND A.NMRAD = B.NMRAD AND A.TYPE = B.TYPE
WHERE
(SELECT COUNT(1) FROM (SELECT 1 FROM TABLE_B WHERE ID_A = A.ID AND TYPE LIKE 'PYT%'
UNION
SELECT 1 FROM TABLE_B WHERE ID_A = A.ID AND B.TYPEPROCESS IS NOT NULL))=2
WHERE A.TYPE=?
For example:
Table A
ID | NMRAD | TYPE
1 | 2 | PYT1
2 | 14 | PYT2
5 | 11 | PYY2
TABLE B
ID_A | NMRAD | TYPE | TYPEPROCESS | AMOUNT | DATEP
1 | 2 | PTY1 | NULL | 50 | 18/10/2018
1 | 2 | PYY2 | 123 | 35 | 19/10/2018
2 | 14 | PTY2 | NULL | 50 | 18/09/2018
2 | 14 | PTY2 | NULL | 35 | 17/10/2018
2 | 14 | PTY3 | NULL | 77 | 11/07/2018
EXPECTED RESULT
TYPE | NMRAD | ID | AMOUNT
PTY1 | 2 | 1 | 35
I could not match your "exactly two rows" criteria, but here is SQL that matches your expected results:
WITH
aset AS
(SELECT a.*
FROM tablea a
WHERE EXISTS
(SELECT NULL
FROM tableb b
WHERE a.id = b.id
AND a.nmrad = b.nmrad
AND a.TYPE = b.TYPE
AND b.TYPE LIKE 'PTY%')
AND EXISTS
(SELECT NULL
FROM tableb b
WHERE a.id = b.id
AND a.nmrad = b.nmrad
AND a.TYPE = b.TYPE
AND b.typeprocess IS NULL))
SELECT a.*
, (SELECT amount
FROM tableb b
WHERE a.id = b.id
AND a.nmrad = b.nmrad
AND datep = (SELECT MAX( datep )
FROM tableb bb
WHERE b.id = bb.id AND b.nmrad = bb.nmrad)) amount
FROM aset a;
This resulted in:
ID NMRAD TYPE AMOUNT
1 2 PTY1 35
Make it easy for us to help you, next time include a setup:
CREATE TABLE tablea
(
id NUMBER( 3 )
, nmrad NUMBER( 2 )
, TYPE VARCHAR2( 4 )
);
CREATE TABLE tableb
(
id NUMBER( 3 )
, nmrad NUMBER( 2 )
, TYPE VARCHAR2( 4 )
, typeprocess NUMBER( 3 )
, amount NUMBER( 3 )
, datep DATE
);
begin
insert into tablea values (1, 2, 'PTY1');
insert into tablea values (2, 14, 'PTY1');
insert into tablea values (5, 11, 'PTY1');
insert into tableb values (1, 2, 'PTY1', NULL, 50, to_date('18/10/2018', 'DD/MM/YYYY'));
insert into tableb values (1, 2, 'PYY2', 123, 35, to_date('19/10/2018', 'DD/MM/YYYY'));
insert into tableb values (2, 14, 'PTY2', NULL, 50, to_date('18/09/2018', 'DD/MM/YYYY'));
insert into tableb values (2, 14, 'PTY2', NULL, 35, to_date('17/10/2018', 'DD/MM/YYYY'));
insert into tableb values (2, 14, 'PTY3', NULL, 77, to_date('11/07/2018', 'DD/MM/YYYY'));
commit;
end;

sql data where not exists in a table and not duplicate

its a bit tricky please focus on my requirments, I have 2 tables , I want to get data from first table where not exists in the second AND the data in first column are not duplicate for sub id and child id.
example: I have this table
tab1
id subid childid
1 11 77
2 22 55
3 33 66
4 11 77
7 22 55
8 33 60
9 99 98
10 33 60
11 97 98
tab2
id
1
4
7
10
the first thing I want is the id in tab1 doesnt exists in tab2 which will be
2,3,8,9,11 however some of those id have duplicate subid and chilid so i have to exclude them therefore I should have id 3, 9, 11
I tried this query but it returne me also 3 ,9 ,11, 8 , I dont want 8 how to fix the query ?
select *
from tab1 a
where not exists (select 1 from tab2 b where a.id = b.id)
and a.sub_id in (select c.sub_id
from tab1 c
group by c.sub_id,c.evt_id
having count(1) = 1)
For multiple database vendors I think the easiest solution would be a couple of not exists queries:
select *
from tab1 a
where not exists (
select 1
from tab2 b
where a.id = b.id
)
and not exists (
select 1
from tab1 c
where c.sub_id = a.sub_id
and c.evt_id = a.evt_id
and c.id <> a.id
)
i think below query will work
select a.*
from tab1 a
where not exists (select 1 from tab2 b where a.id = b.id)
and not exists (select 1 from tab1 c
where c.sub_id = a.sub_id
and c.childid = a.childid
group by c.sub_id,childid
having count(*)> = 2
)
Just to add an approach using a CTE, you can first determine the unique childid,subid pairs and then join that table with your main table:
DB Fiddle
Schema (PostgreSQL v9.6)
create table tab1 (
id integer primary key unique not null
, subid integer not null
, childid integer not null
);
insert into tab1 (id,subid,childid) values (1, 11, 77);
insert into tab1 (id,subid,childid) values (2, 22, 55);
insert into tab1 (id,subid,childid) values (3, 33, 66);
insert into tab1 (id,subid,childid) values (4, 11, 77);
insert into tab1 (id,subid,childid) values (7, 22, 55);
insert into tab1 (id,subid,childid) values (8, 33, 60);
insert into tab1 (id,subid,childid) values (9, 99, 98);
insert into tab1 (id,subid,childid) values (10, 33,60);
insert into tab1 (id,subid,childid) values (11, 97 ,98);
create table tab2 (
id integer primary key unique not null
);
insert into tab2 (id) values (1);
insert into tab2 (id) values (4);
insert into tab2 (id) values (7);
insert into tab2 (id) values (10);
Query #1
with tab1_unique as (
select subid, childid, count(*) as duplicate
from tab1
group by subid, childid
having count(*) = 1
)
select *
from tab1 a
join tab1_unique u on a.subid = u.subid and a.childid = u.childid
where not exists (select 1 from tab2 b where a.id = b.id);
| id | subid | childid | subid | childid | duplicate |
| --- | ----- | ------- | ----- | ------- | --------- |
| 11 | 97 | 98 | 97 | 98 | 1 |
| 9 | 99 | 98 | 99 | 98 | 1 |
| 3 | 33 | 66 | 33 | 66 | 1 |
not exists should do this:
select t1.*
from (select t1.*, count(*) over (partition by subid, childid) as cnt
from tab1 t1
) t1
where not exists (select 1 from tab2 t2 where t2.id = t1.id) and
cnt = 1;
You can use not exists as well for the subid/childid with the assumption that the rows are unique in the first table. Without this assumption, window functions are best solution -- and possibly the best solution anyway.

Eliminating duplicate rows except one column with condition

I am having trouble trying to find an appropriate query(SQL-SERVER) for selecting records with condition however, the table I will be using has more than 100,000 rows and more than 20 columns.
So I need a code that satisfies the following condition:
1.)If [policy] and [plan] column is unique between rows then I will select that record
2.)If [policy] and [plan] return 2 or more rows then I will select the record which 'code' column isn't 999
3.)In some cases the unwanted rows may not have '999' in [code] column but may be other specifics
In other words, I would like to get row number 1,2,4,5,7.
Here is an example of what the table looks like
row #|policy|plan|code
-----------------------
1 | a | aa |111
-----------------------
2 | b | bb |112
-----------------------
3 | b | bb |999
-----------------------
4 | c | cc |111
-----------------------
5 | c | cc |112
-----------------------
6 | c | cc |999
-----------------------
7 | d | dd |999
-----------------------
I'm expecting to see something like
row #|policy|plan|code
-----------------------
1 | a | aa |111
-----------------------
2 | b | bb |112
-----------------------
4 | c | cc |111
-----------------------
5 | c | cc |112
-----------------------
7 | d | dd |999
-----------------------
Thank you in advance
This sounds like a prioritization query. You an use row_number():
select t.*
from (select t.*,
row_number() over (partition by policy, plan
order by code
) as seqnum
from t
) t
where seqnum = 1;
The expected output makes this a bit clearer:
select t.*
from (select t.*,
rank() over (partition by policy, plan
order by (case when code = 999 then 1 else 2 end) desc
) as seqnum
from t
) t
where seqnum = 1;
The OP wants all codes that are not 999 unless the only codes are 999. So, another approach is:
select t.*
from t
where t.code <> 999
union all
select t.*
from t
where t.code = 999 and
not exists (select 1
from t t2
where t2.policy = t.policy and t2.plan = t.plan and
t2.code <> 999
);
May be you want this (eliminate the last row if more than one)?
select t.*
from (select t.*
, row_number() over (partition by policy, plan
order by code desc
) AS RN
, COUNT(*) over (partition by policy, plan) AS RC
from t
) t
where RN > 1 OR RN=RC;
Output:
row policy plan code RN RC
1 1 a aa 111 1 1
2 2 b bb 112 2 2
3 5 c cc 112 2 3
4 4 c cc 111 3 3
5 7 d dd 999 1 1
CREATE TABLE #Table2
([row] int, [policy] varchar(1), [plan] varchar(2), [code] int)
;
INSERT INTO #Table2
([row], [policy], [plan], [code])
VALUES
(1, 'a', 'aa', 111),
(2, 'b', 'bb', 112),
(3, 'b', 'bb', 999),
(4, 'c', 'cc', 111),
(5, 'c', 'cc', 112),
(6, 'c', 'cc', 999),
(7, 'd', 'dd', 999)
;
with cte
as
(
select *,
row_number() over (partition by policy, [plan]
order by code
) as seqnum
from #Table2
)
select [row], [policy], [plan], [code] from cte where seqnum=1