Alternative to using subqueries in SQL statements? - sql

I have two tables:
TableA: (a temporary table)
ItemId (int)
TableB:
ItemId (int), ParentID (int)
I want to retrieve all items in Table A where the ParentID of any of the items in Table A doesn't exist as an ItemID. (i.e. I want to get the root of the items in TableA)
This query does what I want:
SELECT a.ItemID
FROM TableA a
INNER JOIN TableB b ON a.ItemId = b.ItemID
WHERE b.ParentID NOT IN ( SELECT * from TableA )
as does this one:
SELECT b.ItemID
FROM TableB b
WHERE b.ItemID IN ( SELECT * FROM TableA)
AND b.ParentID NOT IN ( SELECT * FROM TableA )
I am not satisfied with either of the queries, particularly because of the use of NOT IN/IN. Is there a way to do this without them? Perhaps a cleaner way that doesn't require subqueries?
Sample Data:
Table A
-------
2
3
5
6
Table B
--------
1 | NULL
2 | 1
3 | 1
4 | 3
5 | 3
6 | 3
Desired Result:
2
3
Thanks

Without subqueries:
SELECT ItemID
FROM TableA
INTERSECT
SELECT b.ItemID
FROM TableB AS b
LEFT OUTER JOIN TableA AS a
ON b.ParentID = a.ItemID
WHERE a.ItemID IS NULL;
...but is your fear of subqueries rational? :) I'd find this equivalent query easier to read and understand:
SELECT ItemID
FROM TableA
INTERSECT
SELECT ItemID
FROM TableB
WHERE NOT EXISTS (
SELECT *
FROM TableA AS a
WHERE a.ItemID = TableB.ParentID
);

Take a look at Select all rows from one table that don't exist in another table to see 5 different ways to do this kind of query by using
NOT IN
NOT EXISTS
LEFT and RIGHT JOIN
OUTER APPLY (2005+)
EXCEPT (2005+)
Here is a script that you can run
CREATE TABLE #TableA( ItemId int)
INSERT #TableA values(1)
INSERT #TableA values(2)
INSERT #TableA values(3)
INSERT #TableA values(4)
INSERT #TableA values(5)
INSERT #TableA values(6)
CREATE TABLE #TableB( ItemId int, ParentID int)
INSERT #TableB values(1,1)
INSERT #TableB values(2,2)
INSERT #TableB values(4,3)
INSERT #TableB values(5,4)
this will do it for parent
SELECT a.ItemID
FROM #TableA a
LEFT JOIN #TableB b ON a.ItemId = b.ParentID
WHERE b.ItemID IS NULL
SELECT a.ItemID
FROM #TableA a
WHERE NOT EXISTS (SELECT 1 FROM #TableB b WHERE a.ItemId = b.ParentID)
Output
ItemID
5
6

You can use outer joins. Something like this:
SELECT a.ItemID
FROM TableA a
INNER JOIN TableB b ON a.ItemId = b.ItemID
LEFT JOIN TableB parentB on a.ItemID = parentB.ParentID
WHERE parentB.ParentID IS NULL

Your Tables A and B seem to store a tree structure. I'd interpret table A as "Nodes" (storing elements of the tree) and Table B as "Edges" (linking a node to it's parent). The inner join variant is very elegant since it covers all cases of "no edge to parent", "edge to PrantID null" and "edge to non existant parent" at once.
cheers

Related

What join to get all instances of a key shared by both tables?

What is the most efficient way to get the result with these two starting tables using T-SQL?
Table1
id
value1
1
A
2
B
Table2
id
value2
1
E
3
F
Desired result:
id
value1
value2
1
A
E
2
B
null
3
null
F
drop table if exists dbo.test1;
create table dbo.test1
(id int,
value1 varchar(50)
);
drop table if exists dbo.test2;
create table dbo.test2
(id int,
value2 varchar(50)
);
insert into dbo.test1
values
(1,'A'),
(2,'B');
insert into dbo.test2
values
(1,'E'),
(3,'F');
this works but seems very inefficient
Union all keys first, then join in the values from the other 2 tables
select p.id, a.value1, b.value2
from
(select id from test1
union
select id from test2) p
left join dbo.test1 a
on p.id = a.id
left join dbo.test2 b
on p.id = b.id
You can use FULL JOIN and COALESCE(), for SQL Server.
Example:
SELECT
COALESCE(a.id,b.id) AS id
,a.value1
,b.value2
FROM test1 AS a
FULL JOIN test2 AS b ON a.id = b.id
Fiddle https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=d1f62122bff5ca91fa31c25818cc58f7

is there a syntactic shortcut similar to coalesce for union?

What I'm trying to do is:
select
A.Fuzz
,A.Fizz
,B.Whiz
from A
left outer join B
on A.Fuzzy B=Wuzzy
To replace:
select
A.Fuzz
,A.Fizz
,B.Whiz
from A
left outer join B
on A.Fuzzy B=Wuzzy
UNION ALL
select
B.wuzz
,A.Fizz
,B.Whiz
from A
left outer join B
on A.Fuzzy B=Wuzzy
May be this is something "near" what you think (done on MSSQL)?. Pls in every question post sample data, expected result, etc.
CREATE TABLE A (ID INT, DESC_A VARCHAR(10));
INSERT INTO A VALUES (1,'A');
INSERT INTO A VALUES (2,'B');
CREATE TABLE B (ID INT, DESC_B VARCHAR(10));
INSERT INTO B VALUES (2,'Z');
INSERT INTO B VALUES (3,'Y');
SELECT COALESCE(A.ID, B.ID) AS ID
, A.DESC_A
, B.DESC_B
FROM A
FULL JOIN B ON A.ID = B.ID
Output:
ID DESC_A DESC_B
1 A NULL
2 B Z
3 NULL Y

Is there a way of this query working with JOINs or must I use a UNION?

I am probably trying to use JOINs for purposes they were not intended here.
Here's my (simplified) table structure:
Table A
ID
Table C ID
IsStatic (bit)
Table B
ID
Table A ID (nullable)
Table C ID
Table C
ID
My goal is to get all of Table B rows joined to Table A rows where Table B's Table A ID column has a value and equals Table A's ID column value.
I also need all of Table B rows where Table B's Table A ID column has no value.
I also need all of Table A rows with there were no joined Table B rows and Table A's IsStatic column is true.
Table C must also be associated with Table A or Table B. If Table B does not have a value for TableAID then it's value for TableCID should equal TableC's ID value. Otherwise TableA's TableCID should equal TableC's ID value.
Here's some SQL to create some TABLE variables and populate with sample data:
DECLARE #TableA TABLE (TableAID int, TableCID int, IsStatic bit)
DECLARE #TableB TABLE (TableBID int, TableAID int, TableCID int)
DECLARE #TableC TABLE (TableCID int)
INSERT INTO #TableC (TableCID) VALUES (1)
INSERT INTO #TableC (TableCID) VALUES (2)
INSERT INTO #TableA (TableAID, TableCID, IsStatic) VALUES (1, 1, 0)
INSERT INTO #TableA (TableAID, TableCID, IsStatic) VALUES (2, 2, 1)
INSERT INTO #TableA (TableAID, TableCID, IsStatic) VALUES (3, 2, 1)
INSERT INTO #TableA (TableAID, TableCID, IsStatic) VALUES (4, 2, 0)
INSERT INTO #TableB (TableBID, TableAID, TableCID) VALUES (1, NULL, 1)
INSERT INTO #TableB (TableBID, TableAID, TableCID) VALUES (2, 1, 1)
INSERT INTO #TableB (TableBID, TableAID, TableCID) VALUES (3, 2, 2)
Here's my (simplified) query that didn't quite work:
SELECT
a.TableAID,
b.TableBID
FROM #TableC c
LEFT OUTER JOIN #TableB b ON
(b.TableAID IS NOT NULL OR (b.TableAID IS NULL AND b.TableCID = c.TableCID))
LEFT OUTER JOIN #TableA a ON
a.TableCID = c.TableCID
AND ((a.IsStatic = 1 AND b.TableBID IS NULL)
OR (b.TableBID IS NOT NULL AND b.TableAID = a.TableAID))
The result of this query using the sampel data is:
TableAID TableBID
-----------------
NULL 1
1 2
NULL 3 (not required)
NULL 2 (not required)
2 3
The required result is:
TableAID TableBID
-----------------
NULL 1
3 NULL (missing)
2 3
1 2
Problem with this query is that if TableB.TableAID has no value then the Table A rows where TableA.IsStatic is true without any matching TableB rows are never included. Also some TableB rows are being included and they shouldn't be.
The only other way I can see of doing this is with a union with a not exists but I was hoping to do this in a more efficient way.
Update: Adding a WHERE clause removes the "not required" rows but still omits the missing row.
WHERE (b.TableBID IS NULL OR b.TableAID IS NULL OR b.TableAID = a.TableAID)
The result of the same query with the where clause is:
TableAID TableBID
-----------------
NULL 1
1 2
2 3
select b.TableAID, b.TableBID
from #TableB b
left join #TableA a on a.TableAID = b.TableAID
inner join #TableC c on c.TableCID = case when a.TableAID IS NULL then b.TableCID else a.TableCID end
union all
select a.TableAID, NULL
from #TableA a
inner join #TableC c on c.TableCID = a.TableCID
left join #TableB b on b.TableAID = a.TableAID
where b.TableAID is NULL
and a.IsStatic = 1
What a mind twister. I think that this is another way to express it. You'll have to see if the performance is good or not:
select a.TableAID, b.TableBID
from (select a.*
from #TableA a
join #TableC c
on c.TableCID = a.TableCID) a
full outer join (select b.*
from #TableB b
join #TableC c
on c.TableCID = b.TableCID) b
on b.TableAID = a.TableAID
where b.TableBID is not null or a.IsStatic = 1
I should also mention that it's hard to know for sure if the above query really respects your requirements using the sample data you provided. To illustrate, if I use this simplified query below that simply ignores the #TableC table, I still get the right results with your sample data:
select a.TableAID, b.TableBID
from #TableA a
full outer join #TableB b
on b.TableAID = a.TableAID
where b.TableBID is not null or a.IsStatic = 1
EDIT: Funny discussion in the comments about the interpretation of OP' requirements... But if I had to address Anton's point:
select a.TableAID, b.TableBID
from (select a.*,
case when c.TableCID is not null then 1 end as has_c
from #TableA a
left join #TableC c
on c.TableCID = a.TableCID) a
full outer join (select b.*,
case when c.TableCID is not null then 1 end as has_c
from #TableB b
left join #TableC c
on c.TableCID = b.TableCID) b
on b.TableAID = a.TableAID
where (b.TableBID is not null or a.IsStatic = 1)
and (a.has_c = 1 or b.has_c = 1)

how to capture the data that is outside of the join scope into a tempTBL

Let's say we have the tables A and B, where A is the parent table of B
TableA:
ID | VAL
1 | "foo"
2 | "bar"
TableB:
ID | aID
1 | 2
OK?
Now lets have an join:
select *
from A
inner join B on a.Id = b.aID
Is there a way to use the INTO keyword to immediately store the failed join record into a temporary table. Something similar using the OUTPUT clause?
I know that it is a bit far fetched, but maybe there is a way I'm not aware of. Pays off to try.
CREATE TABLE ##tmp (
ID int,
VAL nvarchar(3),
IDD int,
aID int
)
CREATE TABLE ##tmp1 (
ID int,
VAL nvarchar(3)
)
;WITH TableA AS (
SELECT *
FROM (VALUES
(1, 'foo'),(2, 'bar')) as t(ID, VAL)
), TableB AS (
SELECT *
FROM (VALUES
(1, 2)) as t(ID, aID)
)
INSERT INTO ##tmp
select a.ID,
a.VAL,
b.ID AS IDD,
b.aID
from TableA a
FULL OUTER JOIN TableB B on a.Id = b.aID
DELETE FROM ##tmp
OUTPUT deleted.ID, deleted.VAL INTO ##tmp1
WHERE IDD IS NULL
Data in ##tmp:
ID VAL IDD aID
----------- ---- ----------- -----------
2 bar 1 2
(1 row(s) affected)
Data in ##tmp1:
ID VAL
----------- ----
1 foo
(1 row(s) affected)
Failed join record ? do you mean non matching records ?
select *
from A
left join B on a.Id = b.ID
where b.ID IS NULL
To store in temporary table , create table structure with required columns from rows retrived in join operation then do
INSERT INTO #temp
SELECT * from A
left join B on a.Id = b.ID
where b.ID IS NULL
or if you require all the columns then do select * into
SELECT * INTO #temp from A
left join B on a.Id = b.ID
where b.ID IS NULL

Parent Child Query without using SubQuery

Let say I have two tables,
Table A
ID Name
-- ----
1 A
2 B
Table B
AID Date
-- ----
1 1/1/2000
1 1/2/2000
2 1/1/2005
2 1/2/2005
Now I need this result without using sub query,
ID Name Date
-- ---- ----
1 A 1/2/2000
2 B 1/2/2005
I know how to do this using sub query but I want to avoid using sub query for some reason?
If I got your meaning right and you need the latest date from TableB, then the query below should do it:
select a.id,a.name,max(b.date)
from TableA a
join TableB b on b.aid = a.id
group by a.id,a.name
SELECT a.ID, a.Name, MAX(B.Date)
FROM TableA A
INNER JOIN TableB B
ON B.ID = A.ID
GROUP BY A.id, A.name
It's a simple aggregation. Looks like you want the highest date per id/name combo.
create table #t1 (id int, Name varchar(10))
create table #t2 (Aid int, Dt date)
insert #t1 values (1, 'A'), (2, 'B')
insert #t2 values (1, '1/1/2000'), (1, '1/2/2000'), (2, '1/1/2005'), (2, '1/2/2005')
;WITH cte (AId, MDt)
as
(
select Aid, MAX(Dt) from #t2 group by AiD
)
select #t1.Id, #t1.Name, cte.MDt
from #t1
join cte
on cte.AId = #t1.Id