Group select statements together by common column in sql-server - sql

I need to combine two tables together based on a common column. Normally I would just use an inner join on the specific column (lets call it parentID), but I need the results to be in seperate rows.
Table A:
ID, ...
Table B:
ID, ParentID, SomeColB, ...
Table C:
ID, ParentID, SomeColC, ...
ParentID points to the ID of table A. The result should look as follows:
ParentID ID_A ID_B SomeColB SomeColC
1 10 20 'VAL_B1' NULL
1 10 20 NULL 'VAL_C1'
2 11 21 'VAL_B2' NULL
2 11 21 NULL 'VAL_C2'
...
So I want to alternate between selecting values from Table B and C and leave the remaining columns on null. How would I do that?
I tried joining them together but this results in results being put into a single row.
EDIT: Both Table B and C have a 1-n relationship to table A (one entry in table a can be referenced from multiple entries in table B and C). Table B and C don't reference each other and are completely independent of eachother.

Would something like this work for you? I've used a UNION to get both sets of data per ParentID:
SELECT
*
FROM (
SELECT
ParentID,
ID_A,
ID_B,
SomeCol B,
NULL AS SomeColC
FROM
TableA
UNION
SELECT
ParentID,
ID_A,
ID_B,
NULL AS SomeColB,
SomeColC
FROM
TableB
)
ORDER BY
ParentID,
SomeColB,
SomeColC

You should use union operator
SELECT IDA, ParentIDA, SomeColA FROM first_table
UNION
SELECT IDB, ParentIDB, SomeColB FROM second_table
UNION will skip the duplicate recored
if you want to show the duplicate records you should use UNION ALL operator

Looks like what you really want is a LEFT OUTER JOIN.
A slimmed down version of your select with the pertinent fields would look like this...
select a.ID as ParentID, b.SomeCol as SomeColB, c.SomeCol as SomeColC
from tableA a
left outer join tableB b
on b.ID = a.ID
left outer join tableC c
on c.ID = a.ID
;
Left outer joins include non-matching rows from the left table in the join, providing NULL values for the fields coming from the unmatched records in the table to the right in the join.

Bit of a stab in the dark, but it gets you the result set you want based on the sample data:
WITH A AS(
SELECT ID
FROM (VALUES(1),(2)) V(ID)),
B AS(
SELECT V.ID,
V.ParentID,
V.ColB
FROM (VALUES(1, 10,'Val_B1'),
(2,11,'Val_B2'))V(ParentID,ID, ColB)),
C AS(
SELECT V.ID,
V.ParentID,
V.ColC
FROM (VALUES(1,20,'Val_C1'),
(2,21,'Val_C2'))V(ParentID,ID, ColC))
SELECT A.ID AS ParentID,
B.ID AS ID_A,
C.ID AS ID_B,
B.ColB,
C.ColC
FROM A
CROSS APPLY (VALUES('B'),('C'))V(T)
LEFT JOIN B ON A.ID = B.ParentID
AND V.T = 'B'
LEFT JOIN C ON A.ID = C.ParentID
AND V.T = 'C'
ORDER BY A.ID,
V.T;
DB<>fiddle

My guess it's a UNION of JOINs
SELECT A.ID AS ParentID, B.ID AS ID_B, null as ID_C, B.SomeColB, null as SomeColC --, ..
FROM A
JOIN B ON A.ID = B.ParentID
UNION
SELECT A.ID AS ParentID, null, c.ID as ID_C, null, C.SomeColC --, ..
FROM A
JOIN C ON A.ID = C.ParentID
ORDER BY ParentID, ID_B, ID_C;
To repeat ids wrap it with one more SELECT:
SELECT ParentID
, max(ID_B) OVER(PARTITION BY ParentID) AS ID_B
, max(ID_C) OVER(PARTITION BY ParentID) AS ID_C
, SomeColB, SomeColC --, --
FROM (
SELECT A.ID AS ParentID, B.ID AS ID_B, null as ID_C, B.SomeColB, null as SomeColC --, ..
FROM A
JOIN B ON A.ID = B.ParentID
UNION
SELECT A.ID AS ParentID, null, c.ID as ID_C, null, C.SomeColC --, ..
FROM A
JOIN C ON A.ID = C.ParentID) t
ORDER BY ParentID, ID_B, ID_C;

Related

Return matched-pair and the non-matched pair for each ID only once

Any help would be appreciated.
I have two sample tables here.
Table A:
ID |Name
123|REG
123|ERT
124|REG
124|ACR
Table B
ID |Name
123|REG
123|WWW
124|REG
124|ADR
Here is the simple join output and I will explain my question in the comments:
*Yes -- I want this row
*No -- I don't want this row
AID|Aname|BID|Bname
123|REG |123|REG --Yes-- Matched-pair for id '123'
123|ERT |123|REG --No--'REG' already had one match. 'ERT' should pair with 'WWW' for id '123'
123|REG |123|WWW --No--The same reason as above
123|ERT |123|WWW --Yes--non-matched pair for id '123'
124|REG |124|REG
124|ACR |124|REG
124|REG |124|ADR
124|ACR |124|ADR
My desired result:
AID|Aname|BID|Bname
123|ERT |123|WWW
123|REG |123|REG
124|REG |124|REG
124|ACR |124|ADR
SQL server 2017.
Thank you in advance.
My approach (Inspired by the post from #The Impaler)
;with CTEall as(
select A.id as AID, A.NAME as Aname, b.id as BID,b.NAME as Bname from A
inner join B on A.id = B.id),
match as (
select A.id as AID, A.NAME as Aname, b.id as BID,b.NAME as Bname
from A inner join B on A.id = B.id and A.NAME = B.NAME)
select *
from CTEall
where Aname not in (select Aname from match where AID = BID)
and Bname not in (select Aname from match where BID = AID)
union all
select * from match
order by 1
Often when you think about the logic you want in a different way, the answer (or at least AN answer) becomes obvious.
I am thinking of your logic this way:
JOIN Table A to Table B such that A.ID=B.ID (always) AND EITHER
A.Name=B.Name OR A.Name doesn't have a Match in B, and B.Name doesn't
have a match in A.
This logic is pretty easy to express in SQL
WHERE a.ID=b.ID
AND (
a.Name=b.Name OR (
NOT EXISTS(SELECT * FROM TableB b2 WHERE b2.ID=a.ID AND b2.Name=a.Name)
AND
NOT EXISTS(SELECT * FROM TableA a2 WHERE a2.ID=b.ID AND a2.Name=b.Name)
)
)
I would do:
with
m as ( -- matched rows
select a.id as aid, a.name as aname, b.id as bid, b.name as bname
from table_a a
join table_b b on a.id = b.id and a.name = b.name
),
l as ( -- unmatched "left rows"
select a.id, a.name,
row_number() over(partition by id order by name) as rn
from table_a a
left join table_b b on a.id = b.id and a.name = b.name
where b.id is null
),
r as ( -- unmatched "right rows"
select b.id, b.name,
row_number() over(partition by id order by name) as rn
from table_b b
left join table_a a on a.id = b.id and a.name = b.name
where a.id is null
)
select aid, aname, bid, bname from m
union all
select l.id, l.name, r.id, r.name
from l
join r on r.id = l.id and r.rn = l.rn
Note: This solution may be a little bit overkill, since matches all unmatched rows when there are multiple ones per ID... something that is not necessary. Per OP comments there always be a single unmatched row per ID.

efficently get nearest id based on counting a column in one-to-many table

I have a relational table (one-to-many) and I need to efficiently get the similarities between the ids giving they associated items. The table its something like this:
id item
1 A2231
1 A2134
2 A2134
2 B2313
...
What I need is to get how many rows are common between all the ids:
a_id b_id count_items
1 2 1
1 3 0
2 1 1
...
I have made a query, but Its o(n2), and it doesn't work because the spool space.
SELECT A.ID AS a_id, B.ID AS b_id, COUNT(B.item) AS count_items
FROM Tab AS A LEFT JOIN Tab AS B --same table
ON (A.item = B.item)
GROUP BY A.ID, B.ID
EDIT:
n_rows ~ 50MM
n_items ~ 100K
n_ids ~ 170K
combinations id/item are unique
It'a there a way to efficiently accomplish this?
Thanks in advance!
I would start by just using an inner join:
SELECT A.ID, B.ID, COUNT(*) AS count_items
FROM Tab A LEFT JOIN
Tab B --same table
ON A.item = B.item
GROUP BY A.ID, B.ID;
Next, if your table has duplicates, then this might work:
with t as (
select distinct id, item
from tab
)
select a.id, b.id, count(*)
from t a join
t b
on a.item = b.item
group by a.id, b.id;
And finally, if you want all pairs of items, then:
with t as (
select distinct id, item
from tab
)
select i1.id, i2.id, count(b.id)
from (select distinct id from tab) i1 cross join
(select distinct id from tab) i2 left join
t a
on t.id = i1.id left join
t b
on b.id = i2.id and a.item = b.item
group by i1.id, i2.id;

SQL - Get one of each in a join

I've got 2 tables. Table A and B.
Table A has an id and some data which isn't important for the question.
Table B has an id and an A_id. The last one is used to combine the 2 of them. There can be either multiple rows with the same A_id, only 1 or none at all.
I need a query which will do the following:
Get only 1 of each row from table A
Join table B into it
No duplicates from table A
I know it might sound complicated, so here is an example
Table A
id other info
1 ...
2 ...
3 ...
4 ...
Table B
id A_id
1 2
2 3
3 3
4 3
Output
A.id other info B.id A_id
1 ... NULL NULL
2 ... 1 2
3 ... 2 3
4 ... NULL NULL
So, even though there are multiple rows in table B of which A_id is 3, I only need the one of them. And even though there is no row in table B of which the A_id is 1 or 4, I still need both of them to show up.
This is as clear as I can possibly describe my question, please give feedback on how I can improve this question.
I think the simplest way is to use a correlated subquery:
select a.*,
(select max(b.id) from b where b.a_id = a.id)
from a;
I can't test it right now but it seems that you want something like this
SELECT * FROM A
LEFT JOIN B ON A.ID = B.A_ID
UPDATE:
WITH tmp AS (
SELECT MIN(ID) ID FROM B GROUP BY A_id
)
SELECT A.*, B.* FROM B
INNER JOIN tmp ON B.Id = tmp.ID
RIGHT JOIN A ON A.Id = B.A_Id
Assuming your database supports ANSI SQL and when there are multiple rows in B you want the last one based on ID:
with lastB (B_Id) as (
select max(id) from tableB group by A_id
),
BRows as (
select * from tableB
where Id in (select B_Id from lastB)
)
select a.field1, a.field2, a.fieldN,
b.field1, b.field2, b.fieldN
from tableA a
left join BRows b on a.Id = b.A_Id
EDIT: Oops. You edited your question and wnat the first one. Then simply make max(), min().
with lastB (B_Id) as (
select min(id) from tableB group by A_id
),
BRows as (
select * from tableB
where Id in (select B_Id from lastB)
)
select a.field1, a.field2, a.fieldN,
b.field1, b.field2, b.fieldN
from tableA a
left join BRows b on a.Id = b.A_Id
EDIT: Here is the MS SQL sample I promised for:
DECLARE #tableA TABLE ( id INT, other VARCHAR(10) );
DECLARE #tableB TABLE
(
id INT ,
A_Id INT ,
other VARCHAR(10)
);
INSERT #tableA
( id, other )
VALUES ( 1, 'v1' ),
( 2, 'v2' ),
( 3, 'v3' ),
( 4, 'v4' );
INSERT #tableB
( id, A_Id, other )
VALUES ( 1, 2, 'v21' ),
( 2, 3, 'v31' ),
( 3, 3, 'v32' ),
( 4, 3, 'v33' );
WITH fromB ( B_Id )
AS ( SELECT MIN(id)
FROM #tableB
GROUP BY A_Id
),
BRows
AS ( SELECT *
FROM #tableB
WHERE id IN ( SELECT B_Id
FROM fromB )
)
SELECT a.id AS A_Id ,
a.other AS A_Other ,
b.id AS B_Id ,
b.other AS B_Other
FROM #tableA a
LEFT JOIN BRows b ON a.id = b.A_Id;
Result:
A_Id A_Other B_Id B_Other
1 v1 NULL NULL
2 v2 1 v21
3 v3 2 v31
4 v4 NULL NULL
Big thank you for Gordon Linoff for pushing me in the right direction. The initial answer was:
select A.*,
(select count(B.A_id) from B where B.A_id = A.id)
from A
I'm sorry for not telling this in my question, but all I actually needed was to get every row from A and at the same time check if there was any row in table B which had the same value in A_id as the A.id had.
This query counts all rows which have the same value of A_id as A.id.
To be clear, the output will give:
A.id other info count
1 ... 0
2 ... 1
3 ... 3
4 ... 0

Need to compare data between two ORACLE tables with different table structure and return the discrepancies

I am trying to compare data between two ORACLE tables with different table structure, different column names. I need a ORACLE SQL query that compares the data and returns the unmatched data with their IDs. Both the tables have same ID column which can be used as the comparison link. Could anyone please guide me on this.
Example: TABLE A (ID_A,QTY_A,DATE_A)
Example: TABLE B (ID_B,QTY_B,DATE_B) where ID_A = ID_B
You should be able to do this with a FULL OUTER JOIN:
SELECT
A.id,
A.qty_a,
B.qty_b,
A.date_a,
B.date_b
FROM
Table_A A
FULL OUTER JOIN Table_B B ON B.id = A.id
WHERE
(
A.qty_a <> B.qty_b OR
(A.qty_a IS NULL AND B.qty_b IS NOT NULL) OR
(A.qty_a IS NOT NULL AND B.qty_b IS NULL)
) OR
(
A.date_a <> B.date_b
(A.date_a IS NULL AND B.date_b IS NOT NULL) OR
(A.date_a IS NOT NULL AND B.date_b IS NULL)
)
If I understand your question correctly, this should suffice:
Select *
From TableA A
Join TableB B On A.Id_A = B.Id_B
Where Not Exists
(
Select *
From TableB B2
Where B2.Id_B = B.Id_B
And (A.Qty_A = B.Qty_B Or Coalesce(A.Qty_A, B.Qty_B) Is Null)
And (A.Date_A = B.Date_B Or Coalesce(A.Date_A, B.Date_B) Is Null)
)
Edit to include NULL checking.
(
(select id_a as id, qty_a as qty, date_a as date from table_a)
minus
(select id_b as id, qty_b as qty, date_b as date from table_b)
)
union
(
(select id_b as id, qty_b as qty, date_b as date from table_b)
minus
(select id_a as id, qty_a as qty, date_a as date from table_a)
)

LEFT JOIN - How to join tables and include extra row even if you have right match

I have two tables
Table A
-------
ID
ProductName
Table B
-------
ID
ProductID
Size
I want to join these two tables
SELECT * FROM
(SELECT * FROM A)
LEFT JOIN
(SELECT * FROM B)
ON A.ID = B.ProductID
This is easy, I will get all rows from A multiplied by rows matched in B, and NULL fields if there is no match.
But here comes the tricky question, how can I get all rows from A with NULL fields for table B, even if there is a match, so I get an extra line with NULL values plus all the matches?
SELECT A.*
, B3.ID
, B3.ProductID
, B3.Size
FROM A
LEFT JOIN
(
SELECT ProductID as MatchID
, ID
, ProductID
, Size
FROM B
UNION ALL
SELECT ID
, null
, null
, null
FROM A A2
) B3
ON A.ID = B3.MatchID
Live example at SQL Fiddle.
Instead of using UNION ALL in a subquery as suggested by others, you could also (and I would) use UNION ALL at the outer level, which keeps the query simpler:
SELECT A.ID, A.ProductName, B.ID, B.Size
FROM A
INNER JOIN B
ON B.ProductID = A.ID
UNION ALL
SELECT A.ID, A.ProductName, NULL, NULL
FROM A
Since every join is going to be successful, we can switch to a full/inner join:
SELECT
*
FROM
A
INNER JOIN
(SELECT ID,ProductID,Size FROM B
UNION ALL
SELECT NULL,ID,NULL FROM A) B
ON
A.ID = B.ProductID
Now would be a very good time to switch to naming columns explicitly, rather than using SELECT *
Or, if, as per #Andomar's comment, you need all of the B columns to be NULL:
SELECT
A.ID,A.ProductName,
B.ID,B.ProductID,B.Size
FROM
A
INNER JOIN
(SELECT ID,ProductID,Size,ProductID as MatchID FROM B
UNION ALL
SELECT NULL,NULL,NULL,ID FROM A) B
ON
A.ID = B.MatchID