SQL - Get one of each in a join - sql

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

Related

Group select statements together by common column in sql-server

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;

Optional on condition in SQL Server

I want to update a table based on another table.
I want to update table b using table A. One Id can have multiple serial number in table B. If I want to update all the serial number for a ID then I will pass null in table A, If I want to update only particular serial number then I will pass that serial number. So Serial number is like optional. How to achieve this?
This covers all cases:
SELECT B.ID, B.SerialNumber
FROM TableA A
JOIN TableB B ON A.ID=B.ID AND (A.SerialNumber=B.SerialNumber OR A.SerialNumber IS NULL)
Demo:
WITH TableA AS
(
SELECT * FROM (VALUES
(1,'AB'),
(1,'BC'),
(2,NULL),
(3,'AB')
)T(ID,SerialNumber)
), TableB AS
(
SELECT * FROM (VALUES
(1,'AB'),
(1,'BC'),
(2,'AB'),
(2,'BC'),
(3,'AB'),
(3,'BC'),
(3,'DE')
)T(ID,SerialNumber)
)
SELECT B.ID, B.SerialNumber
FROM TableA A
JOIN TableB B ON A.ID=B.ID AND (A.SerialNumber=B.SerialNumber OR A.SerialNumber IS NULL)
Result
ID SerialNumber
----------- ------------
1 AB
1 BC
2 AB
2 BC
3 AB
You can join both tables :
SELECT [A].[ID]
,[B].[SerialNumber]
,[A].[Values]
FROM [TableA] A
LEFT JOIN [TableB] B ON ([B].[ID] = [A].[ID] AND ([B].[SerialNumber] = [A].[SerialNumber] OR [A].[SerialNumber] IS NULL))

CTE to recurse to root in Table A, and join results from Table B at each iteration

I'm new to CTE's in T-SQL but am loving them. However, I cannot get the logic right for this particular stored procedure I'm writing.
Given Table A with the columns:
Id Name Inherits ...
Where the column Inherits stores an int that is an id to another row in this same table.
And Table B with the columns:
Id Name AId ...
Where AId is a foreign key to a row in Table A.
How could one use a CTE to start from an arbitrary row (x) in A, collect all rows in B where AId = x.Id, and then recurse upwards in A by setting x to the row pointed to by x.Inherits. This should proceed until x.Inherits IS NULL.
So the overall effect is I want to return the related B rows for the starting A Id, and then all inherited B rows that we discover by examining the Inherits column of A recursively.
My thinking is to set up the CTE to recurse to the 'root' of A from an arbitrary row, and inside each recursive call have a JOIN to B. This is as far as I've got:
WITH c
AS
(
SELECT A.Inherits,A.Id, B.Name, 1 AS Depth
FROM tbl_A A
INNER JOIN tbl_B B ON B.AId = A.Id
WHERE A.Id = #ArbitraryStartingAId
UNION ALL
SELECT T.Inherits,T.Id, c.Name, c.Depth + 1 AS 'Level'
FROM tbl_A T
INNER JOIN c ON T.Id = c.Inherits
)
SELECT *
FROM c
Which produces:
1 4 b_val 1
11 1 b_val 2
NULL 11 b_val 3
Where b_val is taken from a Table B row (y) where #ArbitraryStartingAId = y.AId. The recursion on A is working, but not pulling in the correct corresponding B data upon each iteration.
If someone could help rewrite this to yield the results I need then that would be great.
Many thanks
Supposing we have
CREATE TABLE A ( Id int, Name nvarchar(10), Inherits int );
CREATE TABLE B ( Id int, Name nvarchar(10), AId int );
And some data:
INSERT A VALUES ( 1, 'one', 2 );
INSERT A VALUES ( 2, 'two', 3 );
INSERT A VALUES ( 3, 'three', null );
INSERT A VALUES ( 4, 'four', 3 );
INSERT B VALUES ( 1, 'B one', 1 );
INSERT B VALUES ( 2, 'B two', 2 );
INSERT B VALUES ( 3, 'B three', 3 );
INSERT B VALUES ( 4, 'B four', 4 );
We can build a CTE on A, and then join to B:
WITH cteA AS
(
-- Anchor
SELECT Id, Name, Inherits FROM A
WHERE Id = 1
UNION
-- Recursive
SELECT A.Id, A.Name, A.Inherits FROM A
INNER JOIN cteA ON A.Id = cteA.Inherits
)
SELECT * FROM cteA INNER JOIN B ON cteA.Id = B.AId;
to get (with the anchor at 1):
Id Name Inherits Id Name AId
----------- ---------- ----------- ----------- ---------- -----------
1 one 2 1 B one 1
2 two 3 2 B two 2
3 three NULL 3 B three 3
Here's where I'm at now and it's one possible answer as the correct data is returned:
WITH c
AS
(
SELECT A.Inherits,A.Id, B.Name, 1 AS Depth
FROM tbl_A A
INNER JOIN tbl_B B ON B.AId = A.Id
WHERE A.Id =#ArbitraryStartingAId
UNION ALL
SELECT T.Inherits,T.Id, T.Name, B.Name, c.Depth + 1 AS 'Level'
FROM tbl_A T
INNER JOIN tbl_B B ON B.AId = T.Id
INNER JOIN c ON T.Id = c.Inherits
)
SELECT DISTINCT *
FROM c ORDER BY Depth ASC
It feels a bit dirty though as it returns the root level B rows twice, hence the DISTINCT operator to filter these out...??

select a value where it doesn't exist in another table

I have two tables
Table A:
ID
1
2
3
4
Table B:
ID
1
2
3
I have two requests:
I want to select all rows in table A that table B doesn't have, which in this case is row 4.
I want to delete all rows that table B doesn't have.
I am using SQL Server 2000.
You could use NOT IN:
SELECT A.* FROM A WHERE ID NOT IN(SELECT ID FROM B)
However, meanwhile i prefer NOT EXISTS:
SELECT A.* FROM A WHERE NOT EXISTS(SELECT 1 FROM B WHERE B.ID=A.ID)
There are other options as well, this article explains all advantages and disadvantages very well:
Should I use NOT IN, OUTER APPLY, LEFT OUTER JOIN, EXCEPT, or NOT EXISTS?
For your first question there are at least three common methods to choose from:
NOT EXISTS
NOT IN
LEFT JOIN
The SQL looks like this:
SELECT * FROM TableA WHERE NOT EXISTS (
SELECT NULL
FROM TableB
WHERE TableB.ID = TableA.ID
)
SELECT * FROM TableA WHERE ID NOT IN (
SELECT ID FROM TableB
)
SELECT TableA.* FROM TableA
LEFT JOIN TableB
ON TableA.ID = TableB.ID
WHERE TableB.ID IS NULL
Depending on which database you are using, the performance of each can vary. For SQL Server (not nullable columns):
NOT EXISTS and NOT IN predicates are the best way to search for missing values, as long as both columns in question are NOT NULL.
select ID from A where ID not in (select ID from B);
or
select ID from A except select ID from B;
Your second question:
delete from A where ID not in (select ID from B);
SELECT ID
FROM A
WHERE NOT EXISTS( SELECT 1
FROM B
WHERE B.ID = A.ID
)
This would select 4 in your case
SELECT ID FROM TableA WHERE ID NOT IN (SELECT ID FROM TableB)
This would delete them
DELETE FROM TableA WHERE ID NOT IN (SELECT ID FROM TableB)
SELECT ID
FROM A
WHERE ID NOT IN (
SELECT ID
FROM B);
SELECT ID
FROM A a
WHERE NOT EXISTS (
SELECT 1
FROM B b
WHERE b.ID = a.ID)
SELECT a.ID
FROM A a
LEFT OUTER JOIN B b
ON a.ID = b.ID
WHERE b.ID IS NULL
DELETE
FROM A
WHERE ID NOT IN (
SELECT ID
FROM B)

Problem combining result of two different queries into one

I have two tables (TableA and TableB).
create table TableA
(A int null)
create table TableB
(B int null)
insert into TableA
(A) values (1)
insert into TableB
(B) values (2)
I cant join them together but still I would like to show the result from them as one row.
Now I can make select like this:
select
(select A from tableA) as A
, B from TableB
Result:
A B
1 2
But if I now delete from tableB:
delete tableB
Now when I run the same query as before:
select
(select A from tableA) as A
, B from TableB
I see this:
A B
But I was expecting seeing value from tableA
like this:
Expected Result:
A B
1
Why is this happening and how can I still see the value from TableA although selectB is returning 0 rows?
I am using MS SQL Server 2005.
Use a LEFT JOIN (although it's more of a cross join in your case).
If your db supports it:
SELECT a.a, b.b
FROM a
CROSS JOIN b
If not, do something like:
SELECT a.a, b.b
FROM a
LEFT JOIN b ON ( 1=1 )
However, once you have more rows in a or b, this will return the cartesian product:
1 1
1 2
2 1
2 2
This will actually give you what you're looking for, but if you only have one row per table:
select
(select A from tableA) as A
, (select B from TableB) as B
give this a try:
DECLARE #TableA table (A int null)
DECLARE #TableB table (B int null)
insert into #TableA (A) values (1)
insert into #TableB (B) values (2)
--this assumes that you don't have a Numbers table, and generates one on the fly with up to 500 rows, you can increase or decrease as necessary, or just join in your Numbers table instead
;WITH Digits AS
(
SELECT 0 AS nbr
UNION SELECT 1 UNION SELECT 2 UNION SELECT 3
UNION SELECT 4 UNION SELECT 5 UNION SELECT 6
UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
)
, AllNumbers AS
(
SELECT u3.nbr * 100 + u2.nbr * 10 + u1.nbr + 1 AS Number
FROM Digits u1, Digits u2, Digits u3
WHERE u3.nbr * 100 + u2.nbr * 10 + u1.nbr + 1 <= 500
)
, AllRowsA AS
(
SELECT
A, ROW_NUMBER() OVER (ORDER BY A) AS RowNumber
FROM #TableA
)
, AllRowsB AS
(
SELECT
B, ROW_NUMBER() OVER (ORDER BY B) AS RowNumber
FROM #TableB
)
SELECT
a.A,b.B
FROM AllNumbers n
LEFT OUTER JOIN AllRowsA a on n.Number=a.RowNumber
LEFT OUTER JOIN AllRowsB b on n.Number=b.RowNumber
WHERE a.A IS NOT NULL OR b.B IS NOT NULL
OUTPUT:
A B
----------- -----------
1 2
(1 row(s) affected)
if you DELETE #TableB, the output is:
A B
----------- -----------
1 NULL
(1 row(s) affected)
try this:
select a, (select b from b) from a
union
select b, (select a from a) from b
should retrieve you all the existing data.
you can filter it more by surrounding it with another select