Get parent of parent of Parent from self join table - sql

Pleae copy and run following script.
DECLARE #Locations TABLE
(
LocationId INT,
LocationName VARCHAR(50),
ParentId INT
)
INSERT INTO #Locations SELECT 1, 'Europe', NULL
INSERT INTO #Locations SELECT 2, 'UK', 1
INSERT INTO #Locations SELECT 3, 'England', 2
INSERT INTO #Locations SELECT 4, 'Scotland', 2
INSERT INTO #Locations SELECT 5, 'Wales', 2
INSERT INTO #Locations SELECT 6, 'Cambridgeshire', 3
INSERT INTO #Locations SELECT 7, 'Cambridge', 6
INSERT INTO #Locations SELECT 8, 'North Scotland', 4
INSERT INTO #Locations SELECT 9, 'Inverness', 8
INSERT INTO #Locations SELECT 10, 'Somerset', 3
INSERT INTO #Locations SELECT 11, 'Bath', 10
INSERT INTO #Locations SELECT 12, 'Poland', 1
INSERT INTO #Locations SELECT 13, 'Warsaw', 12
I need following kind of result
Thanks.

There's no way you can do this with the current set of data; how would you know that in the case of LocationId=11, you have a county/country/continent, while in the case of LocationId=13, there's no county - just a country/continent??
And how do you know to "skip" the entries for Somerset, North Scotland etc. from your output result??
You definitely need more information here....
With this recursive CTE (Common Table Expression) query, you can get the "ladder" up the hierarchy to the top, for any given location:
DECLARE #LocID INT = 13
;WITH LocationHierarchy AS
(
SELECT LocationId, LocationName, ParentId, 1 AS 'Level'
FROM #Locations
WHERE LocationId = #LocID
UNION ALL
SELECT l.LocationId, l.LocationName, l.ParentId, lh.Level + 1 AS 'Level'
FROM #Locations l
INNER JOIN LocationHierarchy lh ON lh.ParentId = l.LocationId
)
SELECT
LocationName,
LocationId,
Level
FROM LocationHierarchy
This CTE works on SQL Server 2005 and up - on SQL Server 2000, you're out of luck, unfortunately (time to upgrade!!).
This again allows you to walk up the hierarchy for a single entry - but it cannot possibly return that data set you're looking for - there's not enough information to determine this from the current data.
For #LocID=13 (Warsaw), you get this output:
LocationName LocationId Level
Warsaw 13 1
Poland 12 2
Europe 1 3
and for #LocID=7 (Cambridge), you get:
LocationName LocationId Level
Cambridge 7 1
Cambridgeshire 6 2
England 3 3
UK 2 4
Europe 1 5
From there on, you'd have to use some smarts in your app to get the exact output you're looking for.

Related

Custom hierarchy

I've a table source
idGeo GEO PARENTID
1 EMEA NULL
2 France 1
3 mIDCAPSfRANCE 2
4 Germany 1
5 France exl midcaps 2
6 Amercias NULL
7 US 6
The expected result of the hierarchy
I tried to do left join(self join) but I'm not able to get exactly as expected.
Here is a generic method regardless of the level of the hierarchy.
SQL
-- DDL and sample data population, start
DECLARE #tbl table (
idGeo INT IDENTITY PRIMARY KEY,
GEO VARCHAR(64),
PARENTID INT
);
insert into #tbl (GEO, PARENTID) values
( 'EMEA', NULL),
( 'France', 1),
( 'mIDCAPSfRANCE', 2),
( 'Germany', 1),
( 'France exl midcaps', 2),
( 'Amercias', NULL),
( 'US', 6);
-- DDL and sample data population, end
--SELECT * FROM #tbl;
WITH cte AS
(
-- Anchor query
SELECT idGEO, GEO, ParentID, 1 AS [Level]
, CAST('/' + GEO AS VARCHAR(1000)) AS XPath
FROM #tbl
WHERE ParentID IS NULL
UNION ALL
-- Recursive query
SELECT t.idGEO, t.GEO, t.ParentID, cte.[Level] + 1 AS [Level]
, CAST(cte.[XPath] + '/' + t.GEO AS VARCHAR(1000)) AS [XPath]
FROM #tbl AS t
INNER JOIN cte ON t.ParentID = cte.idGEO
WHERE t.ParentID IS NOT NULL
)
SELECT idGEO
, REPLICATE(' ',[Level]-1) + GEO AS GEOHierarchy
, GEO, ParentID, [Level], [XPath]
FROM cte
ORDER BY XPath;
Output
So if I understand you want to generate columns as the levels go on.
You cannot dynalically generate columns, SQL is a fixed column language.

Recursive CTE to find all records SQL Server

I am having below table structure. I want to write a recursive cte to get the bottom most table result.
Highly appreciate your help.
CREATE TABLE Jobcard (
jobcard_id INT NOT NULL PRIMARY KEY,
jobcard_name varchar(20) NOT NULL,
);
CREATE TABLE Vehicle (
vehicle_id INT NOT NULL PRIMARY KEY,
vehicle_name varchar(20) NOT NULL
);
CREATE TABLE Jobacard_vehicle (
jobcard_id INT NOT NULL,
vehicle_id INT NOT NULL
);
INSERT INTO Jobcard (jobcard_id, jobcard_name) VALUES
(1, 'Job1'),(2, 'Job2'),(3, 'Job3'),
(4, 'Job4'),(5, 'Job5'),(6, 'Job6'),
(7, 'Job7'),(8, 'Job8'),(9, 'Job9');
INSERT INTO Vehicle (vehicle_id, vehicle_name) VALUES
(1, 'Vehicle1'),(2, 'Vehicle2'),(3, 'Vehicle3'),
(4, 'Vehicle4'),(5, 'Vehicle5'),(6, 'Vehicle6');
INSERT INTO Jobacard_vehicle (jobcard_id, vehicle_id) VALUES
(3, 1),(4, 2),(5, 3),
(9, 6),(7, 2),(5, 4),
(8, 4),(6, 1),(3, 5);
jobcard_id, vehicle_id
--------------------------
3 1
4 2
5 3
9 6
7 2
5 4
8 4
6 1
3 5
I want to get this result from Jobacard_vehicle table when I pass the vehicle id as 3 as
vehicle id 3 is having jobcard 5
jobcard 5 is having vehicle 4
again vehicle 4 referred to jobcard 8
means all the jobcard refered by 3 or its counter part jobcards vehicles
jobcard_id, vehicle_id
--------------------------
5 3
5 4
8 4
Thank you.
Try to save full path and check it in the next recursion
DECLARE #startVehicleID int=3
;WITH vehCTE AS(
SELECT jobcard_id,vehicle_id,CAST(CONCAT('(',jobcard_id,',',vehicle_id,')') AS varchar(MAX)) [path]
FROM Jobacard_vehicle
WHERE vehicle_id=#startVehicleID
UNION ALL
SELECT v.jobcard_id,v.vehicle_id,c.[path]+CONCAT('(',v.jobcard_id,',',v.vehicle_id,')')
FROM Jobacard_vehicle v
JOIN vehCTE c ON (v.jobcard_id=c.jobcard_id OR v.vehicle_id=c.vehicle_id) AND CHARINDEX(CONCAT('(',v.jobcard_id,',',v.vehicle_id,')'),c.[path])=0
)
SELECT *
FROM vehCTE
ORDER BY [path]
If you need to check Job->Vehicle->Job->Vehicle->... then I think you can use the following
DECLARE #startVehicleID int=3
;WITH vehCTE AS(
SELECT
jobcard_id,
vehicle_id,
CAST(CONCAT('(',jobcard_id,',',vehicle_id,')') AS varchar(MAX)) [path],
1 NextIsJob
FROM Jobacard_vehicle
WHERE vehicle_id=#startVehicleID
UNION ALL
SELECT
v.jobcard_id,
v.vehicle_id,
c.[path]+CONCAT('(',v.jobcard_id,',',v.vehicle_id,')'),
IIF(c.NextIsJob=1,0,1)
FROM Jobacard_vehicle v
JOIN vehCTE c ON ((c.NextIsJob=1 AND v.jobcard_id=c.jobcard_id) OR (c.NextIsJob=0 AND v.vehicle_id=c.vehicle_id)) AND CHARINDEX(CONCAT('(',v.jobcard_id,',',v.vehicle_id,')'),c.[path])=0
)
SELECT *
FROM vehCTE
ORDER BY [path]

Common Table Expression to traverse down hierarchy

The Structure
I have 2 tables that link to each other. One is a set of values and a nullable foreign key that points to the Id of the other table, which contains 2 foreign keys back to the other table.
HierarchicalTable
Id LeftId RightId SomeValue
1 1 2 some value
2 3 4 top level in tree
3 5 6 incorrect hierarchy 1
4 7 8 incorrect result top level
IntermediateTable
Id SomeValue HierarchicalTableId
1 some value NULL
2 value NULL
3 NULL 1
4 value NULL
5 incorrect result 1 NULL
6 incorrect result 3 NULL
7 incorrect result 3 NULL
8 NULL 3
Each table points down the hierarchy. Here is this structure graphed out for the Hierarchical Table records 1 & 2 and their IntermediateTable values:
(H : HierarchicalTable, I : IntermediateTable)
H-2
/ \
I-3 I-4
/
H-1
/ \
I-1 I-2
The Problem
I need to be able to send in an Id for a given HierarchicalTable and get all the HierarchicalTable records below it. So, for the structure above, if I pass 1 into a query, I should just get H-1 (and from that, I can load the related IntermediateTable values). If I pass 2, I should get H-2 and H-1 (and, again, use those to load the relevant IntermediateTable values).
The Attempts
I've tried using a CTE, but there are a few main things that are different from the examples I've seen:
In my structure, the objects point down to their children, instead of up to their parent
I have the Id of the top object, not the Id of the bottom object.
My hierarchy is split across 2 tables. This shouldn't be a big issue once I understand the algorithm to find the results I need, but this could be causing additional confusion for me.
If I run this query:
declare #TargetId bigint = 2
;
with test as (
select h.*
from dbo.hierarchicaltable h
inner join dbo.intermediatetable i
on (h.leftid = i.id or h.rightid = i.id)
union all
select h.*
from dbo.hierarchicaltable h
where h.id = #TargetId
)
select distinct *
from test
I get all 4 records in the HierarchicalTable, instead of just records 1 & 2. I'm not sure if what I want is possible to do with a CTE.
Try this:
I'm build entire tree with both tables, then filter (only hierarchicaltable records).
DECLARE #HierarchicalTable TABLE(
Id INT,
LeftId INT,
RightId INT,
SomeValue VARCHAR(MAX)
)
INSERT INTO #HierarchicalTable
VALUES
(1, 1, 2, 'some value '),
(2, 3, 4, 'top level in tree '),
(3, 5, 6, 'incorrect hierarchy 1 '),
(4, 7, 8, 'incorrect result top level')
DECLARE #IntermediateTable TABLE(
Id INT,
SomeValue VARCHAR(MAX),
HierarchicalTableId INT
)
INSERT INTO #IntermediateTable
VALUES
(1, 'some value' ,NULL ),
(2, 'value ' ,NULL ),
(3, NULL ,1 ),
(4, 'value ' ,NULL ),
(5, 'incorrect result 1' ,NULL ),
(6, 'incorrect result 3' ,NULL ),
(7, 'incorrect result 3' ,NULL ),
(8, NULL ,3 )
DECLARE #TargetId INT = 2;
WITH CTE AS (
SELECT Id AS ResultId, LeftId, RightId, NULL AS HierarchicalTableId
FROM #HierarchicalTable
WHERE Id = #TargetId
UNION ALL
SELECT C.Id AS ResultId, C.LeftId, C.RightId, NULL AS HierarchicalTableId
FROM #HierarchicalTable C
INNER JOIN CTE P ON P.HierarchicalTableId = C.Id
UNION ALL
SELECT NULL AS ResultId, NULL AS LeftId, NULL AS RightId, C.HierarchicalTableId
FROM #IntermediateTable C
INNER JOIN CTE P ON P.LeftId = C.Id OR P.RightId = C.Id
)
SELECT *
FROM CTE
WHERE ResultId IS NOT NULL

Recursive select in SQL

I have an issue I just can't get my head around. I know what I want, just simply can't get it out on the screen.
What I have is a table looking like this:
Id, PK UniqueIdentifier, NotNull
Name, nvarchar(255), NotNull
ParentId, UniqueIdentifier, Null
ParentId have a FK to Id.
What I want to accomplish is to get a flat list of all the id's below the Id I pass in.
example:
1 TestName1 NULL
2 TestName2 1
3 TestName3 2
4 TestName4 NULL
5 TestName5 1
The tree would look like this:
-1
-> -2
-> -3
-> -5
-4
If I now ask for 4, I would only get 4 back, but if I ask for 1 I would get 1, 2, 3 and 5.
If I ask for 2, I would get 2 and 3 and so on.
Is there anyone who can point me in the right direction. My brain is fried so I appreciate all help I can get.
declare #T table(
Id int primary key,
Name nvarchar(255) not null,
ParentId int)
insert into #T values
(1, 'TestName1', NULL),
(2, 'TestName2', 1),
(3, 'TestName3', 2),
(4, 'TestName4', NULL),
(5, 'TestName5', 1)
declare #Id int = 1
;with cte as
(
select T.*
from #T as T
where T.Id = #Id
union all
select T.*
from #T as T
inner join cte as C
on T.ParentId = C.Id
)
select *
from cte
Result
Id Name ParentId
----------- -------------------- -----------
1 TestName1 NULL
2 TestName2 1
5 TestName5 1
3 TestName3 2
Here's a working example:
declare #t table (id int, name nvarchar(255), ParentID int)
insert #t values
(1, 'TestName1', NULL),
(2, 'TestName2', 1 ),
(3, 'TestName3', 2 ),
(4, 'TestName4', NULL),
(5, 'TestName5', 1 );
; with rec as
(
select t.name
, t.id as baseid
, t.id
, t.parentid
from #t t
union all
select t.name
, r.baseid
, t.id
, t.parentid
from rec r
join #t t
on t.ParentID = r.id
)
select *
from rec
where baseid = 1
You can filter on baseid, which contains the start of the tree you're querying for.
Try this:
WITH RecQry AS
(
SELECT *
FROM MyTable
UNION ALL
SELECT a.*
FROM MyTable a INNER JOIN RecQry b
ON a.ParentID = b.Id
)
SELECT *
FROM RecQry
Here is a good article about Hierarchy ID models. It goes right from the start of the data right through to the query designs.
Also, you could use a Recursive Query using a Common Table Expression.
I'm guessing that the easiest way to accomplish what you're looking for would be to write a recursive query using a Common Table Expression:
MSDN - Recursive Queries Using Common Table Expressions

Sql Server2005 query problem

i have a table which contains the following fields
Supervisorid
Empid
This is just like a referral program. A guy can refer 3 guys under him i.e,
3 is referring three guys namely 4 5 8 similarly 4 is referring 9 10 and 11 likewise 8 is referring 12, 13 it goes like this..
I want a query to get the total no of down line members under a guy say 3
You can make use of Recursive CTE.
Something like this
DECLARE #Table TABLE(
Supervisorid INT,
Empid INT
)
INSERT INTO #Table SELECT 3, 4
INSERT INTO #Table SELECT 3, 5
INSERT INTO #Table SELECT 3, 8
INSERT INTO #Table SELECT 4, 9
INSERT INTO #Table SELECT 4, 10
INSERT INTO #Table SELECT 4, 11
INSERT INTO #Table SELECT 8, 12
INSERT INTO #Table SELECT 8, 13
DECLARE #ID INT
SELECT #ID = 3
;WITH Vals AS (
SELECT *
FROM #Table
WHERE SuperVisorID = #ID
UNION ALL
SELECT v.SuperVisorID,
t.Empid
FROM Vals v INNER JOIN
#Table t ON v.Empid = t.Supervisorid
)
SELECT SuperVisorID,
COUNT(Empid) Total
FROM Vals
GROUP BY SuperVisorID