Show a unary relationship hierarchy in one field in MS SQL - sql-server

I have a table called "Services" which contains a ServiceID, a ParentID and a Description, where the ParentID is the ServiceID of another record. The data is setup in such a way that it forms a multiple level hierarchy of items and the "Root" items have the ParentID set to zero. How can I have a query with a new field that shows a thread of all the parents up to the root parent for each record. Of course, root items will have this field as blank. Using cars as an example, I would like to have such text inside this field for the entry 'X3' and 'Punto' respectively:
Automobiles > Germany > BMW > 4 Wheel Drive > X3
Automobiles > Italy > FIAT > Front Wheel Drive > Punto
I suspect I should have a function to which I feed the ServiceID and which does the necessary recursion to get me the string value containing the threaded descriptions. Tried Googling unary relations but could not find an example with the code for the function I need.
Thanks in advance!
UPDATE:
Here is what my table looks like:
CREATE TABLE [dbo].[Services](
[ServiceID] [int] IDENTITY(1,1) NOT NULL,
[ParentID] [int] NULL,
[ServiceDescription] [nvarchar](250) NULL)

Here is a generic example. You can copy, paste and run it and it will show you the output. If this works you should be able to modify the table and column values to get it to work for your situation.
If Object_ID('dbo.Services') Is Not Null Drop Table [dbo].[Services];
Create Table [dbo].[Services] (ServiceID Int Identity(1,1) NOT NULL, ParenServiceID Int NULL, ServiceDescription Nvarchar(250) NULL);
Insert [dbo].[Services]
Select null, 'Automobiles'
Union All
Select 1, 'Germany'
Union All
Select 2, 'BMW'
Union All
Select 3, '4 Wheel Drive'
Union All
Select 1, 'Italy'
Union All
Select 5, 'FIAT'
Union All
Select 4, 'X3'
Union All
Select 6, 'Front Wheel Drive'
Union All
Select 8, 'Punto';
With recurCTE As
(
Select h.ServiceID, h2.ParenServiceID As nextParent, Convert(Varchar(max),Isnull(h2.ServiceDescription + ' > ','') + h.ServiceDescription) As Hierarchy
From [dbo].[Services] h
Left Join [dbo].[Services] h2
On h.ParenServiceID = h2.ServiceID
Union All
Select rc.ServiceID, h.ParenServiceID As nextParent, Convert(Varchar(max),Isnull(h.ServiceDescription + ' > ','') + rc.Hierarchy) As Hierarchy
From recurCTE rc
Join [dbo].[Services] h
On rc.nextParent = h.ServiceID
), orderedResults As
(
Select ServiceID, Hierarchy
From (Select Row_Number() Over (Partition By ServiceID Order By Len(Hierarchy) Desc) As lenPriID,
ServiceID,
Hierarchy
From recurCTE) As n
Where lenPriID = 1
)
Select h.*, o.Hierarchy
From orderedResults o
Join [dbo].[Services] h
On o.ServiceID = h.ServiceID
Where ServiceDescription In ('X3','Punto');

I would use left joins to some reasonable limit (like 20) and then concatatinate the strings:
declare #services table (ServiceID int , ParentID int,[Description] varchar(20))
insert #services
select 1,null,'automobiles' union
select 2,null,'hotels' union
select 3,1,'Germany' union
select 4,3,'BMW' union
select 5,3,'Audi' union
select 6,2,'Hawaii' union
select 7,2,'Australia'
select
s.Description+'>'+s2.Description+'>'+isnull(s3.Description,'')+'>'+isnull(s4.Description,'')
from #services s
left join #services s2 on (s2.ParentID=s.ServiceID)
left join #services s3 on (s3.ParentID=s2.ServiceID)
left join #services s4 on (s4.ParentID=s3.ServiceID)
left join #services s5 on (s5.ParentID=s4.ServiceID)

Related

T-SQL hierarchy query

I have a table with hierarchical data:
This is a sample of data with id, parent id, name, code (which is sometimes not filled), level and isroot column. In real scenario there will be more levels than just 2 but now let's look at the simplified example.
What I need to do is to loop over all records and find rows where id is not filled at any level of the hierarchy:
Rows with id ranging should be returned from 6 till 10 because they do not have code filled in at any point of the hierarchy
Rows from 1 till 5 should not be returned because code was mentioned somewhere in the hierarchy.
How should one solve this problem with T-SQL?
The only solution that came to my mind was recursivity (cte or WHILE) but I what I was trying to implement was too complex and did not solve the problem.
Slightly different than #DhruvJoshi's answer, since it may be useful:
WITH recCTE AS
(
SELECT
id,
parent_id,
CASE WHEN CODE IS NOT NULL THEN 1 ELSE 0 END as code_check,
1 as depth,
CAST(id as VARCHAR(50)) as path
FROM table
WHERE isRootLevel = 1
UNION ALL
SELECT
table.id,
table.parent_id,
CASE WHEN CODE IS NOT NULL OR reccte.code_check = 1 THEN 1 ELSE 0 END,
depth + 1 as depth,
reccte.path + CAST(table.id AS varchar(10)) as path
FROM
recCTE
INNER JOIN table ON
recCTE.ID = table.parent_id
WHERE depth < 20 /*just in case you start cycling/endless looping*/
)
SELECT * FROM recCTE where code_check = 0 ORDER BY path, depth;
Here is another example, for anyone who may still struggling with hierarchical data (like me).
Say we got a following hierarchical structure:
CEO
|-- Sales Director
│ |-- Sales Manager 1
│ `-- Sales Manager 2
`-- Technical Director
|-- Product Manager
|-- R&D Team Lead
`-- QA Team Lead
Using recursive cte to get the level for each node:
with cte as (
select id, parentId, roleName, 1 as lvl from roles where id = 1 -- root node
union all
select r.id, r.parentId, r.roleName, cte.lvl+1 as lvl from roles r -- child nodes
inner join cte on cte.id = r.parentid
)
select * from cte;
To get the path for each node:
with cte as (
select id, roleName, cast(roleName as varchar(200)) as hierPath
from roles where id = 1
union all
select r.id, r.rolename, cast(cte.hierPath + ' / ' + r.rolename as varchar(200)) as hierPath
from roles r
inner join cte on cte.id = r.parentid
)
select * from cte;
Using row_number() and power() to get the sorted hierarchy tree result (parent followed by all of its children, then all the children of each one of those and so on):
with cte as (
select id, roleName, cast(roleName as varchar(200)) as hierPath, 1 as lvl,
row_number()over(partition by parentid order by roleName) / power(10.0,1) as sortNo
from roles where id = 1
union all
select r.id, r.rolename, cast(cte.hierPath + ' / ' + r.rolename as varchar(200)) as hierPath, cte.lvl+1 as lvl,
cte.sortNo + row_number()over(partition by r.parentid order by r.roleName) / power(10.0,cte.lvl+1) as sortNo
from roles r
inner join cte on cte.id = r.parentid
)
select * from cte
order by sortNo;
Setup test data:
create table roles (
id int not null,
parentId int,
roleName varchar(50) not null
);
insert into roles
(id, parentId, roleName)
values
(1, null, 'CEO'),
(2, 1, 'Sales Director'),
(3, 1, 'Technical Director'),
(4, 2, 'Sales Manager 1'),
(5, 2, 'Sales Manager 2'),
(6, 3, 'Product Manager'),
(7, 3, 'R&D Team Lead'),
(8, 3, 'QA Team Lead');
Query like this should work:
; with cte as
(
select id, parent_id,code,parent_id as RootId from tableT where IsRootLevel=1
UNION ALL
select T2.id,T2.parent_id,T2.code,T1.RootId as RootId from tableT T2 join
cte T1 on T1.id=T2.parent_id and IsRootLevel=0
)
,
cte2 as
(select id,MAX(case when code ='' then NULL else code end) over( partition by RootId) as code from cte)
select T1.* from tableT T1 left join cte2 T2
on T1.id=T2.id
where T2.code is NULL
See working demo

Get All records with parent Child relationship to the Nth Level

I have a Company table having ParentCompnayID column in it along with other columns which tells which company is parent of other companies.
DECLARE #company TABLE
(
CompanyID INT IDENTITY(1, 1) ,
CompanyName VARCHAR(50) ,
ParentCompnayID INT
)
INSERT INTO #company
( CompanyName ,
ParentCompnayID
)
SELECT 'Company A' ,
0
UNION
SELECT 'Company AB' ,
1
UNION
SELECT 'Company AA' ,
1
UNION
SELECT 'Company AAA' ,
2
SELECT * FROM #company
Now As you see in the code above Compnay A have parentCompanyID=0 which means it does not have any parent,
where as Company AB and Company AA have parent which is Company A.
Furthermore Company AAA have parent which is Compnay AA.
Now my problem is that if i pass compnayID 1 in my query/procedure i want to get all its child companies plus the childs companies of Child of Company 1 and up to so on...
According to the above code if I will pass CompanyID 1, I should get Company AA, Company AB and Company AAA (which is the child of Company AA)
Can anyone help me in building its sql. i am using SQL server 2012.
You can do this with the help of common table expression
declare #companyId int
set #companyId = 1
;WITH cte
AS
(
SELECT CompanyId, CompanyName, ParentCompanyId, 0 as steps
FROM dbo.tblCompany
--WHERE ParentCompanyId IS NOT NULL
WHERE companyid = #companyId
UNION ALL
SELECT c.CompanyId, c.CompanyName, c.ParentCompanyId, cte.steps +1 as steps
FROM dbo.tblCompany AS c
inner JOIN cte ON cte.CompanyId = c.ParentCompanyId
)
SELECT CompanyId, CompanyName, ParentCompanyId, steps
FROM cte;
In the above query step is the level in the hierarchy.

How to find the maximum value in join without using if in sql stored procedure

I have a two tables like below
A
Id Name
1 a
2 b
B
Id Name
1 t
6 s
My requirement is to find the maximum id from table and display the name and id for that maximum without using case and if.
i findout the maximum by using below query
SELECT MAX(id)
FROM (SELECT id,name FROM A
UNION
SELECT id,name FROM B) as c
I findout the maximum 6 using the above query.but i can't able to find the name.I tried the below query but it's not working
SELECT MAX(id)
FROM (SELECT id,name FROM A
UNION
SELECT id,name FROM B) as c
How to find the name?
Any help will be greatly appreciated!!!
First combine the tables, since you need to search both. Next, determine the id you need. JOIN the id back with the temporarily created table to retreive the name that belongs to that id:
WITH tmpTable AS (
SELECT id,name FROM A
UNION
SELECT id,name FROM B
)
, max AS (
SELECT MAX(id) id
FROM tmpTable
)
SELECT t.id, t.name
FROM max m
JOIN tmpTable t ON m.id = t.id
You could use ROW_NUMBER(). You have to UNION ALL TableA and TableB first.
WITH TableA(Id, Name) AS(
SELECT 1, 'a' UNION ALL
SELECT 2, 'b'
)
,TableB(Id, Name) AS(
SELECT 1, 't' UNION ALL
SELECT 6, 's'
)
,Combined(Id, Name) AS(
SELECT * FROM TableA UNION ALL
SELECT * FROM TableB
)
,CTE AS(
SELECT *, RN = ROW_NUMBER() OVER(ORDER BY ID DESC) FROM Combined
)
SELECT Id, Name
FROM CTE
WHERE RN = 1
Just order by over the union and take first row:
SELECT TOP 1 * FROM (SELECT * FROM A UNION SELECT * FROM B) x
ORDER BY ID DESC
This won't show ties though.
For you stated that you used SQL Server 2008. Therefore,I used FULL JOIN and NESTED SELECT to get what your looking for. See below:
SELECT
(SELECT
1,
ISNULL(A.Id,B.Id)Id
FROM A FULL JOIN B ON A.Id=B.Id) AS Id,
(SELECT
1,
ISNULL(A.Name,B.Name)Name
FROM A FULL JOIN B ON A.Id=B.Id) AS Name
It's possible to use ROW_NUMBER() or DENSE_RANK() functions to get new numiration by Id, and then select value with newly created orderId equal to 1
Use:
ROW_NUMBER() to get only one value (even if there are some repetitions of max id)
DENSE_RANK() to get all equal max id values
Here is an example:
DECLARE #tb1 AS TABLE
(
Id INT
,[Name] NVARCHAR(255)
)
DECLARE #tb2 AS TABLE
(
Id INT
,[Name] NVARCHAR(255)
)
INSERT INTO #tb1 VALUES (1, 'A');
INSERT INTO #tb1 VALUES (7, 'B');
INSERT INTO #tb2 VALUES (4, 'C');
INSERT INTO #tb1 VALUES (7, 'D');
SELECT * FROM
(SELECT Id, Name, ROW_NUMBER() OVER (ORDER BY Id DESC) AS [orderId]
FROM
(SELECT Id, Name FROM #tb1
UNION
SELECT Id, Name FROM #tb2) as tb3) AS TB
WHERE [orderId] = 1
SELECT * FROM
(SELECT Id, Name, DENSE_RANK() OVER (ORDER BY Id DESC) AS [orderId]
FROM
(SELECT Id, Name FROM #tb1
UNION
SELECT Id, Name FROM #tb2) as tb3) AS TB
WHERE [orderId] = 1
Results are:

How do I find the root of a hierarchy using LINQ and EF?

I have used the accepted answer to the question Hierarchical data in Linq - options and performance successfully to query a hierarchical table for all descendants of a particular record/node. Now I need to find the root of the tree that a particular node is a descendant of. How can I use as close a possible solution as that accepted answer to do this?
The hierarchy is represented by a self-referencing table with a ParentId column, which is null for the top item in the hierarchy.
Is this the kind of thing you're looking for
CREATE TABLE [dbo].[hierarchical_table](
[id] INT,
[parent_id] INT,
[data] VARCHAR(255)
)
GO
INSERT [dbo].[hierarchical_table]
SELECT 1,NULL,'/1' UNION ALL
SELECT 2,1,'/1/2' UNION ALL
SELECT 3,2,'/1/2/3' UNION ALL
SELECT 4,2,'/1/2/4' UNION ALL
SELECT 5,NULL, '/5' UNION ALL
SELECT 6,5,'/5/6'
GO
CREATE FUNCTION [dbo].[fn_root_for_node]
(
#id int
)
RETURNS TABLE AS
RETURN (
WITH [hierarchy_cte](id, parent_id, data, [Level]) AS
(
SELECT
id,
parent_id,
data,
0 AS [Level]
FROM dbo.hierarchical_table
WHERE id = #id
UNION ALL
SELECT t1.id, t1.parent_id, t1.data, h.[Level] + 1 AS [Level]
FROM dbo.hierarchical_table AS t1
INNER JOIN hierarchy_cte AS h
ON t1.id = h.parent_id
)
SELECT TOP 1
id, parent_id, data, [Level]
FROM [hierarchy_cte]
ORDER BY [Level] DESC
)
GO
SELECT * FROM [dbo].[fn_root_for_node] (6)
GO

How to show recursive parentID in a single column in SQL

Here is the example structure of the table:
ID Name ParentID
-----------------------
1 Ancestor NULL
2 GrandFather 1
3 GrandMother 1
4 Child 3
I'm trying to write a query that would return
ID Name Family
----------------------------
1 Ancestor
2 GrandFather Ancestor
3 GrandMother Ancestor
4 Child Ancestor^GrandMother
The tricky part is that I want to show the family of all rows and in a top-down order.
If anyone can point me in the right direction, it would be appreciated :)
EDIT :: This is the real query, but it follows the same idea. it returns an error on line : marketparent.family + '^'+ t2.marketGroupName because it cant find marketparent
WITH marketparent ( marketGroupID,parentGroupID, marketGroupName,family)
AS
(
SELECT marketGroupID,
parentGroupID,
marketGroupName,
'' as family
FROM EVE.dbo.invMarketGroups
WHERE parentGroupID IS NULL
UNION ALL
SELECT t2.parentGroupID,
t2.marketGroupID,
t2.marketGroupName,
marketparent.family + '^'+ t2.marketGroupName
FROM EVE.dbo.invMarketGroups as t2
INNER JOIN marketparent as mp
ON mp.marketGroupID = t2.parentGroupID
)
-- Statement using the CTE
SELECT TOP 10 *
FROM marketparent;
You did not specify your DBMS, so I'm assuming PostgreSQL
WITH RECURSIVE fam_tree (id, name, parent, family) as
(
SELECT id,
name,
parentid,
''::text as family
FROM the_unknown_table
WHERE parent IS NULL
UNION ALL
SELECT t2.id,
t2.name,
t2.parentid,
fam_tree.family || '^' || t2.name
FROM the_unknown_table t2
INNER JOIN fam_tree ON fam_tree.id = t2.parentid
)
SELECT *
FROM fam_tree;
This is standard SQL (except for the ::text typecast) that should work with very few changes on most modern DBMS.
Edit:
For SQL Server you would need to replace the standard concatention character with Microsoft's non-standar + (and you need to remove the recursive keyword which is required by the standard but for some strange reason rejected by SQL Server)
WITH fam_tree (id, name, parent, family) as
(
SELECT id,
name,
parentid,
'' as family
FROM the_unknown_table
WHERE parent IS NULL
UNION ALL
SELECT t2.id,
t2.name,
t2.parentid,
fam_tree.family + '^' + t2.name
FROM the_unknown_table t2
INNER JOIN fam_tree ON fam_tree.id = t2.parentid
)
SELECT *
FROM fam_tree;
You can use a recursive Common Table Expression.
declare #T table
(
ID int,
Name nvarchar(15),
ParentID int
);
insert into #T values
(1, N'Ancestor', NULL),
(2, N'GrandFather', 1),
(3, N'GrandMother', 1),
(4, N'Child', 3);
with C as
(
select T.ID,
T.Name,
T.ParentID,
cast(N' ' as nvarchar(max)) as Family
from #T as T
where T.ParentID is null
union all
select T.ID,
T.Name,
T.ParentID,
C.Family+'^'+C.Name
from #T as T
inner join C
on T.ParentID = C.ID
)
select C.ID,
C.Name,
stuff(C.Family, 1, 2, '') as Family
from C;
select T.ID, T.Name, (select name from table where ID=T.ParentID)as Family
from table T

Resources