Inner join then full join among the matched records - sql-server

I have two tables:
declare #Table1 as table (id int, value CHAR(1))
declare #Table2 as table (id int, value CHAR(1))
INSERT #Table1
VALUES (1, 'A'),
(1, 'B'),
(3, 'A')
INSERT #Table2
VALUES(1, 'A'),
(1, 'C'),
(2, 'A')
I want to join these two tables so that at the end I should be able to produce this result:
id value id value
1 A 1 A
1 B NULL NULL
NULL NULL 1 C
I'm sorry for inadequate explanation (I mean no explanation at all). What I am trying to do here is (something like) to make inner join for the id columns (I mean take the records which are common on both sets over the "id" column) then look at the value columns and compare them inside the boundaries of this common set.
I hope I could describe what I was trying to do.

Hope this will work
SELECT distinct t1_id as id, t1_value as value , t2_id as id , t2_value as value
FROM (SELECT t1.id as t1_id, t1.value as t1_value from #Table1 t1 INNER JOIN #Table2 t2 on t1.id = t2.id) as A
FULL OUTER JOIN
(SELECT t2.id as t2_id, t2.value as t2_value from #Table1 t1 INNER JOIN #Table2 t2 on t1.id = t2.id) as B
on A.t1_value = B.t2_value
ORDER BY t1_id desc
Basically, What I am doing is outer joining the inner set (which is inner join on id column) on value column of inner set.

What do I win?
SELECT c.id,c.value,d.id,d.value
FROM
#Table1 c
full join
#Table2 d
on c.id = d.id and c.value = d.value
WHERE exists
(
SELECT a.id
FROM
#Table1 a
INNER JOIN
#Table2 b
ON a.id = b.id and a.id = c.id or d.id = a.id
)

You can do this with a full outer join and by pre-filtering the tables:
select a.id as a_id, a.value as a_value,
b.id as b_id, b.value as b_value
from (select *
from tablea a
where a.id = 1
) a full outer join
(select *
from tableb b
where b.id = 1
) b
on a.id = b.id and a.value = b.value;

Related

How find rows in one table that share common ancestor in second table with a third table

Sorry for the convoluted title, hopefully what follows is clear. Suppose I have the following tables:
T1
ID
1001
1002
T2
ID|T1ID
20|1001
T3
ID|T1ID
30|1002
T4
T1ID|T5ID
1001|2003
1002|2004
T5
ID|ParentID
2001|2000
2002|2001
2003|2001
2004|2002
T2 and T3 have a connection such that, via T1 and T4, if they share a common ancestor in T5, they are connected. In the example above, 20 in T2 has a connection to 30 in T3 because in T5 (via T1 and T4), they have a common ParentID, i.e. ancestor (2001). How can I construct a query that returns all rows in T3 that has a connection to a given T2? That is, given ID 20 for T2, it returns
T3
30|1002
T3 30? I know how to use CTE to solve something much simpler like "given T5 2004, find all ancestors" (2002,2001,2000), but I don't know how to extend that to solve this more complex problem. Thanks.
[Edit]
Using Paul Fleming's answer here as a starting point, I think I've been able to construct a query that works. But I'm wondering if there's a simpler solution. Working example for sample data above:
CREATE TABLE #T1 (id INT);
CREATE TABLE #T2 (id INT, t1Id INT);
CREATE TABLE #T3 (id INT, t1Id INT);
CREATE TABLE #T4 (t1Id INT, t5Id INT);
CREATE TABLE #T5 (id INT, parentId INT);
-- insert some test data
INSERT INTO #T1 (id)
SELECT 1001 UNION ALL SELECT 1002
INSERT INTO #T2 (id, t1Id)
SELECT 20,1001
INSERT INTO #T3 (id, t1Id)
SELECT 30,1002
INSERT INTO #T4 (t1Id, t5Id)
SELECT 1001,2003 UNION ALL SELECT 1002,2004
INSERT INTO #T5 (id, parentid)
SELECT 2001,2000 UNION ALL SELECT 2002,2001 UNION ALL SELECT 2003,2001
UNION ALL SELECT 2004,2002
DECLARE #t5Id INT;
SELECT #t5Id = (SELECT t5.id from #T5 t5
inner join #T4 t4 on t4.t5Id=t5.id
inner join #T2 t2 on t2.t1Id=t4.t1Id
inner join #T1 t1 on t1.id=t2.t1Id
where t2.id=20);
-- build the CTE
WITH #results AS
(
SELECT id,
parentid
FROM #T5
WHERE id = #t5Id
UNION ALL
SELECT t.id,
t.parentid
FROM #T5 t
INNER JOIN #results r ON r.parentid = t.id
)
SELECT t3.* from #T3 t3
inner join
(SELECT t4.* from #T4 t4
inner join
(SELECT t5b.* FROM #t5 t5b
inner join
(SELECT t5.* FROM #T5 t5
inner join
(SELECT * FROM #results) as R1 on R1.parentId=t5.parentId) as R2 on t5b.parentId=R2.id) as R3 on R3.id=t4.t5Id) as R4 on t3.t1Id=R4.t1Id
Using your tables, this is clearer I think:
DECLARE #T2Id int = 20;
-- Tree returns all #T5 IDs with each of their ancestors
WITH Tree AS (
SELECT Id, ParentId
FROM #T5
UNION ALL
SELECT #T5.id, t.parentid
FROM Tree t
INNER JOIN #T5 ON #T5.parentId = t.id
)
SELECT *
FROM #T3
-- We don't mind if there are multiple common ancestors, we just want to find an example
WHERE EXISTS (
SELECT NULL
FROM #T4 AS t4a
INNER JOIN #T5 AS t5a ON t5a.id = t4a.t5Id
INNER JOIN Tree tr ON tr.id = t5a.id
INNER JOIN #T5 AS t5b ON t5b.parentId = tr.parentId
INNER JOIN #T4 AS t4b ON t4b.t5Id = t5b.id
INNER JOIN #T2 ON #t2.t1Id = t4b.t1Id
WHERE t4a.t1Id = #t3.t1Id
AND #t2.id = #T2Id
);

Counting from 2 tables

I have 2 tables.
A
Id Name
1 ab
2 cd
3 eg
4 fg
B
FkID
1
2
2
1
1
2
4
Since 3 is not there in B in the FKID column. I need to find the count in table A which has value in table B which have a value in FKID also.
So th total count should be 3. In my query, I am getting 7 after the left join.
You don't need a join, you can do it with EXISTS:
select count(*) from a
where exists (
select 1 from b
where b.fkid = a.id
)
I believe EXISTS is more efficient than a join, but if you need a join then it has to be an INNER JOIN like this:
select count(distinct a.id)
from a inner join b
on b.fkid = a.id
Using COUNT(DISTINCT B.FkId) the result can be achiveable. The COUNT is not considering the NULL values, so the following query will work.
SELECT COUNT(DISTINCT B.FkId) AS Occurence
FROM TableA A
LEFT JOIN TableB B ON B.FkId = A.Id;
or it can be achiveable with INNER JOIN too
SELECT COUNT(DISTINCT B.FkId) AS Occurence
FROM TableA A
INNER JOIN TableB B ON B.FkId = A.Id;
Demo with sample data:
DECLARE #TableA TABLE (Id INT, [Name] VARCHAR (2));
INSERT INTO #TableA (Id, [Name]) VALUES
(1, 'ab'),
(2, 'cd'),
(3, 'eg'),
(4, 'fg');
DECLARE #TableB TABLE (FkId INT);
INSERT INTO #TableB (FkId) VALUES
(1),
(2),
(2),
(1),
(1),
(2),
(4);
SELECT COUNT(DISTINCT B.FkId) AS Occurence
FROM #TableA A
LEFT JOIN #TableB B ON B.FkId = A.Id

Is CTE the correct way to handle recursion with two tables?

I have a query setup below, and I'm having trouble with the recursion piece. I start with a contract(s), Abc and Xyz from Table1. I take Table1.Id, groupNo and look them up in Table2, to get the contracts(s) there, then look those contracts back up in Table1, repeating the process until it eventually returns null, and capturing the last iteration. Is CTE the way to handle this? If so, could someone help with the last iteration. I tried nesting, and haven't got it to work.
Table Structure
create table Table1 (id int, groupNo int, contract varchar(3))
insert into Table1 values(33,2,'Abc')
insert into Table1 values(34,8,'Xyz')
insert into Table1 values(88,11,'123')
insert into Table1 values(89,11,'456')
create table Table2 (id int, groupNo int, contract varchar(3))
insert into Table2 values(34,8,'123')
insert into Table2 values(34,8,'456')
insert into Table2 values(89,11,'789')
Query
with myCTE (id,groupNo,contract) as
(
select
t1.id
,t1.groupNo
,t2.contract
from Table1 t1
inner join Table2 t2 on t1.id = t2.id and t1.groupNo = t2.groupNo
union all
select
t1.id
,t1.groupNo
,c2.contract
from myCTE c2
inner join Table1 t1 on c2.contract = t1.contract
)
select top 10 id, groupNo, contract
from myCTE
SQL FIDDLE
This is one way of doing it.
Basically, I record the level of each recursion and only keep the highest level. See SQL Fiddle and query below:
declare #Table1 table(id int, groupNo int, contract varchar(3));
insert into #Table1 values(33,2,'Abc');
insert into #Table1 values(34,8,'Xyz');
insert into #Table1 values(88,11,'123');
insert into #Table1 values(89,11,'456');
declare #Table2 table(id int, groupNo int, contract varchar(3));
insert into #Table2 values(34,8,'123');
insert into #Table2 values(34,8,'456');
insert into #Table2 values(89,11,'789');
with myCTE (level, id, groupNo, contract, subcontract) as
(
select 0, t1.id,t1.groupNo, t1.contract
,t2.contract
from #Table1 t1
inner join #Table2 t2 on t1.id = t2.id and t1.groupNo = t2.groupNo
union all
select level+1, c2.id, c2.groupNo, c2.contract
,t2.contract
from myCTE c2
inner join #Table1 t1 on c2.subcontract = t1.contract
inner join #Table2 t2 on t1.id = t2.id and t1.groupNo = t2.groupNo
)
Select c.* From myCTE as c
Inner join (select id, groupNo, contract, level = max(level) From myCTE Group by id, groupNo, contract) as m
On m.level = c.level and m.id = c.id and m.groupNo = c.groupNo and m.contract = c.contract
OPTION (MAXRECURSION 0);
I also added table2 to the second select. You want it to behave like the first one and it needs to get the subcontract name from table2.

Select record from table1 where value from table2

I have 2 tables here:
table1
id name idfrom idto
1 test 2 3
2 test3 1 9
table2
id branch status
2 a from
1 b from
9 c to
3 d to
How do I select branch from table2 and table1 based on status in table2?
I want the result to look like this:
id name branchfrom branchto
1 test a d
2 test3 b c
I answer it doesn't mean I like it.
SELECT id, name, bfrom.branch branchfrom, bto.branch branchto
FROM table1 t1
INNER JOIN (SELECT id, branch
FROM table2
WHERE status = 'from') bfrom
ON t1.idfrom = bfrom.id
INNER JOIN (SELECT id, branch
FROM table2
WHERE status = 'to') bto
ON t1.idto = bto.id;
I use INNER JOIN as sample only. You must adjust with your requirement (which you didn't clearly specify).
Something like the following should do (assuming you're wanting to join on id in both tables):
select t1.id, t1.name, f.branch as branchfrom, t.branch as branchto
from table1 as t1
join table2 as f
on f.id = t1.id
and f.status = 'from'
join table2 as t
on t.id = t1.id
and t.status = 'to'
This should work for you:
select t1.id, t1.name, f.branch as branchfrom, f1.branch as branchto
from table1 as t1
join table2 as f
on t1.idfrom = f.id
join table2 as f1
on t1.idto = f1.id
Please see here for demo: SQL Fiddle Demo
I don't know if this is better or worse than what the other two people have suggested but
select
t1.name,
(select
t2.branch
from
table2 t2
where
t1.idfrom = t2.id
) as branchfrom,
(select
t2.branch
from
table2 t2
where
t1.idto = t2.id
) as branchto
from
table1 t1
Here is a fiddle
Use this Code:
CREATE TABLE #table1 (
id int,
name varchar(10),
idfrom int,
idto int
)
CREATE TABLE #table2 (
id int,
branch char,
statuss varchar(10)
)
INSERT INTO #table1
VALUES (1, 'test', 2, 3)
INSERT INTO #table1
VALUES (2, 'test3', 1, 9)
INSERT INTO #table2
VALUES (2, 'a', 'From')
INSERT INTO #table2
VALUES (1, 'b', 'From')
INSERT INTO #table2
VALUES (9, 'c', 'to')
INSERT INTO #table2
VALUES (3, 'd', 'to')
SELECT
a.id,
a.name,
(SELECT
b.branch
FROM #table2 b
WHERE a.idfrom = b.id
AND b.statuss = 'FROM')
AS BranchFrom,
(SELECT
b.branch
FROM #table2 b
WHERE a.idto = b.id
AND b.statuss = 'to')
AS BranchTo
FROM #table1 a

Compare and merge data from 2 tables with same structure

Having 2 tables with the same structure, like this SQLFiddle, is it possible to build a SQL statement that compares the values of the columns of both tables (where id is the unique key), and return a list of the change columns in the format:
columnname, oldvalue, newvalue
Where oldvalue is the value in Table1 and newvalue is the value in Table2.
You can do something like this:
SELECT T1.Id
,'Name' AS ColumnName
,CAST(T1.name AS VARCHAR(MAX)) AS OldValue
,CAST(T2.name AS VARCHAR(MAX)) AS NewValue
FROM Table1 AS T1
FULL OUTER JOIN Table2 AS T2
ON T1.id = T2.id
UNION
SELECT T1.Id
,'Amount'
,CAST(T1.amount AS VARCHAR(MAX))
,CAST(T2.amount AS VARCHAR(MAX))
FROM Table1 AS T1
FULL OUTER JOIN Table2 AS T2
ON T1.id = T2.id
You need to use a MERGE statement , please note it is only available from SQL 2008 onwards
here is an example
MERGE Production.ProductInventory AS target
USING (SELECT ProductID, SUM(OrderQty) FROM Sales.SalesOrderDetail AS sod
JOIN Sales.SalesOrderHeader AS soh
ON sod.SalesOrderID = soh.SalesOrderID
AND soh.OrderDate = #OrderDate
GROUP BY ProductID) AS source (ProductID, OrderQty)
ON (target.ProductID = source.ProductID)
WHEN MATCHED AND target.Quantity - source.OrderQty <= 0
THEN DELETE
WHEN MATCHED
THEN UPDATE SET target.Quantity = target.Quantity - source.OrderQty,
target.ModifiedDate = GETDATE()
OUTPUT $action, Inserted.ProductID, Inserted.Quantity, Inserted.ModifiedDate, Deleted.ProductID,
Deleted.Quantity, Deleted.ModifiedDate;
GO
you can learn more about merge's here SQL merge
if you only want rows with differences:
SELECT COALESCE(T1.Id, T2.Id) Id
,'Name' AS ColumnName
,CAST(T1.name AS VARCHAR(MAX)) AS OldValue
,CAST(T2.name AS VARCHAR(MAX)) AS NewValue
FROM Table1 AS T1
FULL OUTER JOIN Table2 AS T2
ON T1.id = T2.id
WHERE COALESCE(T1.name,'**') != COALESCE(T2.name ,'**')
UNION ALL
SELECT COALESCE(T1.Id, T2.Id) Id
,'Amount' AS ColumnName
,CAST(T1.Amount AS VARCHAR(MAX)) AS OldValue
,CAST(T2.Amount AS VARCHAR(MAX)) AS NewValue
FROM Table1 AS T1
FULL OUTER JOIN Table2 AS T2
ON T1.id = T2.id
WHERE COALESCE(T1.Amount,0) != COALESCE(T2.Amount,0)

Resources