Join within same table to retrieve hierarchy (Parent-Child Hierarchy) - sql

I have two tables - Master and Child. Child table contains hierarchal data for MainId stored in Master table.
Master.Name is Unique
Master.MainId = Child.ChildId
Child.ParentId = Child.ChildId
I have to retrieve all data from Child table for Master.RelDate and Master.Name. I thought it was a straight forward query but somehow not able to pull desired output. I'm ending up with just one Child table record no matter how I write query.
Is it to do with table structure or my query?
Note: None of the ParentIds and ChildIds are in sequence. That data comes from another system, I just put those values for simplicity.
IF OBJECT_ID('tempdb..#Master') IS NOT NULL BEGIN DROP TABLE #Master END
GO
IF OBJECT_ID('tempdb..#Child') IS NOT NULL BEGIN DROP TABLE #Child END
GO
CREATE TABLE #Master
(
Id BIGINT IDENTITY(1,1),
RelDate DATE NOT NULL,
MainId BIGINT NOT NULL,
Name VARCHAR(512) NOT NULL
)
GO
CREATE TABLE #Child
(
Id BIGINT IDENTITY(1,1),
RelDate DATE NOT NULL,
ChildId BIGINT NOT NULL,
ParentId BIGINT NULL,
Label VARCHAR(20) NULL,
Value VARCHAR(1024) NULL
)
GO
INSERT INTO #Master(RelDate,MainId,Name) VALUES ('2019-01-01',1,'Name1')
INSERT INTO #Master(RelDate,MainId,Name) VALUES ('2019-01-01',11,'Name11')
GO
INSERT INTO #Child(RelDate,ChildId,ParentId,Label,Value) VALUES
('2019-01-01',1,2,'Level10',NULL)
,('2019-01-01',2,3,'Level09',NULL)
,('2019-01-01',3,4,'Level08',NULL)
,('2019-01-01',4,5,'Level07',NULL)
,('2019-01-01',5,6,'Level06','This is level 6')
,('2019-01-01',6,7,'Level05',NULL)
,('2019-01-01',7,8,'Level04',NULL)
,('2019-01-01',8,9,'Level03','This is level 3')
,('2019-01-01',9,10,'Level02',NULL)
,('2019-01-01',10,11,'Level01',NULL) -- Always same
,('2019-01-01',11,NULL,'Root',NULL) -- Always same
INSERT INTO #Child(RelDate,ChildId,ParentId,Label,Value) VALUES
('2019-01-01',11,12,'Level10',NULL)
,('2019-01-01',12,13,'Level09',NULL)
,('2019-01-01',13,14,'Level08',NULL)
,('2019-01-01',14,15,'Level07',NULL)
,('2019-01-01',15,16,'Level06','This is level 6')
,('2019-01-01',16,17,'Level05',NULL)
,('2019-01-01',17,18,'Level04',NULL)
,('2019-01-01',18,19,'Level03','This is level 3')
,('2019-01-01',19,10,'Level02',NULL)
,('2019-01-01',10,11,'Level01',NULL) -- Always same
,('2019-01-01',11,NULL,'Root',NULL) -- Always same
GO
SELECT * FROM #Master
SELECT * FROM #Child
Desired output for RelDate = 2019-01-01 and Name = Name1
SELECT chld.*
FROM #Master mst (NOLOCK)
INNER JOIN #Child chld (NOLOCK) ON (mst.MainId = chld.ChildId)
LEFT JOIN #Child chld1 (NOLOCK) ON (chld.ParentId = chld.ChildId)
WHERE mst.RelDate = '2019-01-01' -- Input 1
AND mst.Name = 'Name1' -- Input 2
All the data from Child table
Id RelDate ChildId ParentId Label Value
1 2019-01-01 1 2 Level10 NULL
2 2019-01-01 2 3 Level09 NULL
3 2019-01-01 3 4 Level08 NULL
4 2019-01-01 4 5 Level07 NULL
5 2019-01-01 5 6 Level06 This is level 6
6 2019-01-01 6 7 Level05 NULL
7 2019-01-01 7 8 Level04 NULL
8 2019-01-01 8 9 Level03 This is level 3
9 2019-01-01 9 10 Level02 NULL
10 2019-01-01 10 11 Level01 NULL
11 2019-01-01 11 NULL Root NULL
Thanks in advance

select c.* from #Child c inner join #Master M
on M.MainId = C.ChildId
Id RelDate ChildId ParentId Label Value
1 2019-01-01 1 2 Level10 NULL
11 2019-01-01 11 NULL Root NULL
If this isn't the desired output, please provide your desired output.

It seams that data in the tables is kinda non logical. Especially with the 'Name11'. I assume that with every new rows you will lose one id from Childid column .Now we lose 'lvl2' at row 19.
I tried to figure out some solution, now my position here.
It works for 'Name1', but result for 'Name11' a bit different.
SELECT t2.*
FROM #Master mst
join #Child t1 ON mst.MainId = t1.ChildId
join #Child t2 ON t2.parentid >= t1.ChildId
WHERE mst.RelDate = '2019-01-01' -- Input 1
AND mst.Name = 'Name1' -- Input 2
order by t2.id
offset 0 rows
fetch first 10 rows only
Maybe it helps you a little. I will try to find a tricky solution.

Related

Listing all parents hierarchy in delimited string excluding the top most node

Im having a trouble with an already answered question in stackoverflow itself (Question before). So Im just repeating the question with some changes and a trouble in it because of a root element
I have an SQL table like this
ID Name ParentID
------------------------------
0 Users NULL
1 Alex 0
2 John 0
3 Don 1
4 Philip 2
5 Shiva 2
6 San 3
7 Antony 6
8 Mathew 2
9 Cyril 8
10 Johan 9
-------------------------
Am looking for an out put like this
if I pass the ID 7,10,1
The out put table will be
ID Name Relation
------------------------------------
7 Antony Alex->Don->San->Antony
10 Johan John->Mathew->Cyril->Johan
1 Alex -
From the above answer what I was trying to emphasis is it should not consider the top most node Users whose ID is 0 and parentid is null. So for ID 1, it returned just an empty string for relation or just hyphen (-)
How can I achieve that using CTE
Based on prev answer:
DECLARE #t table (ID int not null, Name varchar(19) not null, ParentID int null)
insert into #t(ID,Name,ParentID) values
(1 ,'Alex',null),
(2 ,'John',null),
(3 ,'Don',1),
(4 ,'Philip',2),
(5 ,'Shiva',2),
(6 ,'San',3),
(7 ,'Antony',6),
(8 ,'Mathew',2),
(9 ,'Cyril',8),
(10,'Johan',9)
declare #search table (ID int not null)
insert into #search (ID) values (7),(10), (1);
;With Paths as (
select s.ID as RootID,t.ID,t.ParentID,t.Name
, CASE WHEN t.ParentId IS NULL THEN '-'
ELSE CONVERT(varchar(max),t.Name) END as Path
from #search s
join #t t
on s.ID = t.ID
union all
select p.RootID,t.ID,t.ParentID,p.Name, t.Name + '->' + p.Path
from Paths p
join #t t
on p.ParentID = t.ID
)
select *
from Paths
where ParentID is null;
Rextester Demo
Based on your schema and data, this query:
with P as
(
select *, cast(null as nvarchar) as chain from People where parentID is null
union all
select C.*, cast(coalesce(P.chain, P.name) + '->' + C.name as nvarchar)
from People C inner join P on C.parentID = P.id
)
select id, name, coalesce(chain, '-') relation from P where id in (7, 10, 1)
Yields:
id name relation
----- ------ ------------------------------
1 Alex -
10 Johan John->Mathew->Cyril->Johan
7 Antony Alex->Don->San->Antony
Rextester Demo.

SQL - Multi level self join

i have a table which has parent child relationship like this - Isfinal column suggests that it is the final element of that level
ID name ParentId Isfinal
1 abc 0 No
2 acd 1 No
3 ads 1 No
4 xyz 2 No
5 xxy 2 Yes
6 plm 3 No
7 ytr 4 Yes
8 lks 6 Yes
I am trying to write a dynamic query which will give the child element of that ID.
E.G.
If I select 2 then it should give the result as -
ID name ParentId Isfinal
4 xyz 2 No
5 xxy 2 Yes
7 ytr 4 Yes
Is it possible with self join?
Using a recursive CTE , you can solve this.
DECLARE #TABLE TABLE
( ID int
,name nvarchar(200)
,ParentId int
,Isfinal nvarchar(20)
)
INSERT INTO #TABLE
VALUES (1,'abc',0,'No'),(2,'acd',1,'No'),(3,'ads',1,'No'),
(4,'xyz',2,'No'),(5,'xxy',2,'Yes'),(6,'plm',3,'No'),
(7,'ytr',4,'Yes'),(8,'lks',6,'Yes')
DECLARE #ID INT = 2
;WITH CTE
AS
(
SELECT ID,name,ParentId,Isfinal
FROM #TABLE
WHERE ParentId = #ID
UNION ALL
SELECT T.ID,T.name,T.ParentId,T.Isfinal
FROM #TABLE T
INNER JOIN CTE C ON C.ID = T.ParentId
)
SELECT * FROM CTE

SQL - Join tables where first table has a value that must fall within the range specified in the second

I've got a resultset that looks something like:
[DateField ][Hour][Value]
2014-10-01 1 200
2014-10-01 2 600
I need to add a couple of additional columns from another table like:
[DateField ][Hour][Value][T2Value1][T2Value2]
2014-10-01 1 200 Off 5
2014-10-01 2 600 Off 7
I need the T2Value1 and T2Value2 from Table 2 below if the Value field from Table 1 falls between the RangeValue1 and RangeValue2 of Table 2.
[Id][T2Value1][T2Value2][RangeValue1][RangeValue2]
1 Off 5 1 500
2 Off 7 501 1000
I've started to query
select datefield, hour, value, t2value1, t2value2 from
(
-- inner query that returns datefield, hour, value
) Table1, Table2
but don't know where to take it from here. Would appreciate any help, thanks!
See this simple example:
declare #Values as table
(
Name varchar(10) not null
,Value int not null
)
declare #Ranges as table
(
Grade char(1) not null
,v0 int not null
,v1 int not null
)
insert into #Values
values
('Albert', 33)
,('Bob', 133)
,('Carl', 233)
insert into #Ranges
values
('A',0,100)
,('B',100,200)
,('C',200,300)
select * from #Values v
join #Ranges r on r.v0 <= v.Value and v.Value < r.v1
As you can see it's ok to put some more complex condition on the join clause, in this case a range condition
it results in:
Name Value Grade v0 v1
---------- ----------- ----- ----------- -----------
Albert 33 A 0 100
Bob 133 B 100 200
Carl 233 C 200 300
A simple join should get you what you need. The on condition is a little more complicated than normal, but not much.
select t1.DateField, t1.Hour, t2.t2value1, t2.t2value2
from table1 t1
inner join table2 t2 on t2.RangeValue1 <= t1.Value and t1.Value <= t2.RangeValue2

How to find all 'related' parents from a table containing set of (parent, child)?

I have a SQL Server 2005 table as follows:
parent child
1 a
2 a
2 b
3 b
3 c
4 c
5 d
6 d
7 d
8 e
9 e
9 f
10 f
Each parent can have one or more child, each child can also have one or more parents.
How can I find (or group) all parents that are related?
In above case, I want to group:
parent 1, 2, 3, 4 into group 1
parent 5, 6, 7 into group 2
parent 8, 9, 10 into group 3
A result from the query would look like: (Doesn't matter which parent use as group as long as it is from one of those parent in the group, min ID, or max ID would do)
parent child parent_group
1 a 1
2 a 1
2 b 1
3 b 1
3 c 1
4 c 1
5 d 5
6 d 5
7 d 5
8 e 8
9 e 8
9 f 8
10 f 8
This is similar to those standard boss - subordinate recursive SQL questions except the subordinate may have more than 1 boss.
Is it possible to use SQL to generate result as above? If so how?
Appreciate any help.
There is no way to do this in a single query. I found a blog post (with some ruby code) describing how you can calculate connected components in SQL (MySQL flavor).
Based on the article, I wrote the following SQL (SQL Server flavor):
-- Step 1: Create temporary tables
CREATE TABLE #items (
id int PRIMARY KEY,
component_id int
);
CREATE TABLE #links (
first int,
second int,
PRIMARY KEY (first, second)
);
CREATE TABLE #components_to_merge (
component1 int,
component2 int
PRIMARY KEY (component1, component2)
);
-- Step 2: Populate tables
INSERT INTO #items (id, component_id)
SELECT DISTINCT parent, parent
FROM children;
INSERT INTO #links (first, second)
SELECT DISTINCT c1.parent, c2.parent
FROM children c1
INNER JOIN children c2 ON c1.child = c2.child
WHERE c1.parent <> c2.parent;
-- Step 3: Merge components
WHILE 1 = 1 BEGIN
-- Step 3.1: Update #components_to_merge
TRUNCATE TABLE #components_to_merge;
INSERT INTO #components_to_merge (component1, component2)
SELECT DISTINCT t1.component_id, t2.component_id
FROM #links l
INNER JOIN #items t1 ON t1.id = l.first
INNER JOIN #items t2 ON t2.id = l.second
WHERE t1.component_id <> t2.component_id;
INSERT INTO #components_to_merge (component1, component2)
SELECT component2, component1
FROM #components_to_merge m1
WHERE component2 NOT IN (
SELECT m2.component2
FROM #components_to_merge m2
WHERE m2.component1 = m1.component1
);
IF (SELECT COUNT(*) FROM #components_to_merge) = 0
BREAK;
-- Step 3.2: Update #items
UPDATE i
SET component_id = target
FROM #items i
INNER JOIN (
SELECT
component1 AS source,
MIN(component2) AS target
FROM #components_to_merge
GROUP BY component1
) new_components
ON source = component_id
WHERE target < component_id;
END;
-- Step 4: Generate result
SELECT parent, child, component_id
FROM children
INNER JOIN #items ON id = parent;
You could wrap this up in a stored procedure:
CREATE PROCEDURE CalculateComponents AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION;
-- SQL code from above
ROLLBACK TRANSACTION;
END;
And then call it with
EXEC CalculateComponents;
Output:
parent child component_id
1 a 1
2 a 1
2 b 1
3 b 1
3 c 1
4 c 1
5 d 5
6 d 5
7 d 5
8 e 8
9 e 8
9 f 8
10 f 8

SQL Always return all rows from LUT for each ID

I am wondering if this query can be modified to achieve what I want:
SELECT
cv.[ID]
,cv.[CustomValue]
,cf.[SpecialInformationId]
FROM #CustomFields cf
FULL OUTER JOIN #CustomValues cv ON cf.SpecialInformationId = cv.id
This returns all cv.Id's. It also returns any unmatched cf.SpecialInformationId's with a NULL for the cv information. What I actually want is for each instance of cv, I want every cf to show. cf is a lookup table. In this instance there are 12 values, but that varies everytime the query runs. Here is an example:
What this query currently returns:
cv.id cv.customvalue cf.specialinformationid
1 003 1
1 abc 2
2 004 1
2 1/1/2010 4
2 abc 2
3 009 1
4 003 1
4 acb 2
4 1/2/2010 4
What I want it to return:
cv.id cv.customvalue cf.specialinformationid
1 003 1
1 abc 2
1 NULL 3
1 NULL 4
1 NULL 5
2 004 1
2 abc 2
2 NULL 3
2 1/1/2010 4
2 NULL 5
3 009 1
3 NULL 2
3 NULL 3
3 NULL 4
3 NULL 5
4 003 1
4 acb 2
4 NULL 3
4 1/2/2010 4
4 NULL 5
A Left join cannot be used because there are only 12 rows in the lookup table so if a left join is used the same result will be achieved as the full outer join.
This is a spinoff of my other question:
SQL 2 tables always return all rows from 1 table match existing on other
Thanks
I believe a CROSS JOIN will achieve the results you're looking for.
The problems are arising because your Table2 is not really a 'vehicle' table. Because the VehicleId does not uniquely identify a record in that table. This is where all of the confusion is coming from. So to solve that and get your problem to work I did a select distinct on table2 against the values in table 1 (I also did a select distinct for clarity, but it was not necessary.) Hope this helps.
CREATE TABLE #Table1 (Id INT)
CREATE TABLE #Table2 (VehicleID INT, Value VARCHAR(50), Table1ID INT)
INSERT INTO #Table1 VALUES (1),(2),(3),(4),(5)
INSERT INTO #Table2 VALUES (1, 't', 1),(1, 'q', 2),(3, 'w', 3),(3, 'e', 4),(4, 't', 1),(5, 'e', 1),(5, 'f', 2),(5, 'g', 4)
SELECT * FROM #Table1
SELECT * FROM #Table2
SELECT t2.VehicleID, t2.Value
FROM ( SELECT t2.VehicleId, t1.Id
FROM ( SELECT DISTINCT
VehicleId
FROM #Table2 ) t2
CROSS JOIN ( SELECT Id
FROM #Table1 ) t1 ) Base
LEFT JOIN #Table2 t2
ON Base.VehicleId = t2.VehicleID
AND Base.Id = t2.Table1ID
WHERE (Base.VehicleId BETWEEN 1 AND 3)
DROP TABLE #Table1
DROP TABLE #Table2