Displaying sorted hierarchy rows in SQL server? - sql-server

Assuming I have this table : ( c is a child of parent p)
c p
------
40 0
2 3
2 40
3 1
7 2
1 0
Where (0 means root) — I want the order of select to be displayed as :
c b
------
1 0
3 1
2 3
40 0
2 40
7 2
That's becuase we have 2 roots (1,40) and 1 < 40.
So we start at 1 and then display below it - all it's descendants.
Then we get to 40. same logic again.
Question:
How can I do it ?
I've succeeded to display it recursively + finding level of hierarchy*(not sure if it helps though)*
WITH cte(c, p) AS (
SELECT 40, 0 UNION ALL
SELECT 2,3 UNION ALL
SELECT 2,40 UNION ALL
SELECT 3,1 UNION ALL
SELECT 7,2 UNION ALL
SELECT 1,0
) , cte2 AS(
SELECT c,
p,
PLevel = 1
FROM cte
WHERE p = 0
UNION ALL
SELECT cte.c,
cte.p,
PLevel = cte2.PLevel + 1
FROM cte
INNER JOIN cte2
ON cte2.c = cte.p
)
SELECT *
FROM cte2
Full SQL fiddle

You have almost done it. Just add a rank to identify each group and then sort the data on it.
Also, as you are working with more complex hierarchy we need to change the [level] value. In is now not a number, put the full path of the current element to its parent. Where \ means parent. For example the following string:
\1\5\4\1
represents the hierarchy below:
1
--> 5
--> 4
--> 1
I get the idea from hierarchyid type. You may want to consider storing hierarchies using it, as it has handy build-in functions for working with such structures.
Here is full working example with the new data:
DECLARE #DataSource TABLE
(
[c] TINYINT
,[p] TINYINT
);
INSERT INTO #DataSource ([c], [p])
VALUES (1,0)
,(3, 1)
,(2, 3)
,(5,1)
,(7, 2)
,(40, 0)
,(2, 40);
WITH DataSource ([c], [p], [level], [rank])AS
(
SELECT [c]
,[p]
,CAST('/' AS VARCHAR(24))
,ROW_NUMBER() OVER (ORDER BY [c] ASC)
FROM #DataSource
WHERE [p] = 0
UNION ALL
SELECT DS.[c]
,DS.[p]
,CAST(DS1.[level] + CAST(DS.[c] AS VARCHAR(3)) + '/' AS VARCHAR(24))
,DS1.[rank]
FROM #DataSource DS
INNER JOIN DataSource DS1
ON DS1.[c] = DS.[p]
)
SELECT [c]
,[p]
FROM DataSource
ORDER BY [Rank]
,CAST([level] AS hierarchyid);
Again, pay attention to the node (7,2) which is participating in the two groups (even in your example). I guess this is just a sample data and you have a way to defined where the node should be included.

Related

Comparing the length of two similar strings and picking the longest

I am Trying to compare two strings and pick the longest if they are similar, I have managed to pick the longest by using the following code:
SELECT D.RID, ProductID, Product, [Length] FROM (
SELECT RID, MAX([Length]) AS theLength FROM SortData GROUP BY RID)
AS X INNER JOIN SortData AS D ON D.RID = X.RID AND D.[Length] = X.theLength
But I am now trying to make sure that the code only pick the longest string if it is a like the word it is comparing it to, I have attempted the following code in a few ways but I would be grateful if somebody could help me:
SELECT D.RID, D.ProductID, Product, [Length] FROM (
SELECT RID, Product, MAX([Length]) AS theLength FROM SortData GROUP BY RID)
AS X INNER JOIN SortData AS D ON D.RID = X.RID AND D.[Length] = X.theLength WHERE
D.Product LIKE Product
Using this code I get the Following Error:
Msg 8120, Level 16, State 1, Line 3 Column 'SortData.Product' is
invalid in the select list because it is not contained in either an
aggregate function or the GROUP BY clause. Msg 209, Level 16, State 1,
Line 5 Ambiguous column name 'Product'. Msg 209, Level 16, State 1,
Line 2 Ambiguous column name 'Product'.
Example of the Data I would Like to pick:
1 Sam
1 Samantha
2 Oliver
3 Ollie
4 Benjamin
4 Ben
...
I would expect the output list to be like:
1 Samantha
2 Oliver
3 Ollie
4 Benjamin
...
To Clarify what I am trying to do in the context of this example, I am trying to compare the two Names and if the are LIKE (e.g. x.Name LIKE Name) then pick the longest...
As Requested here is further test data:
1 Hydrogen
1 Hydrogen Oxide
1 Carbon Monoxide
2 Carbon
2 Carbon
2 Carbon Dioxide
3 Carbon Monoxide
3 Carbon Dioxide
3 Oxygen
4 Hydrogen Dioxide
Desired Results are as so:
1 Hydrogen Oxide
1 Carbon Monoxide
2 Carbon Dioxide
3 Carbon Monoxide
3 Oxygen
4 Hydrogen Dioxide
Perhaps another option: The WITH TIES clause in concert with Row_Number()
Example
Select Top 1 with ties *
From YourTable
Order By Row_Number() over (Partition by ID Order By Len(Name) desc)
Your query doesn't come close to your sample data and output. So I built this around the sample data provided to demonstrate one way of solving this.
declare #Something table
(
Col1 int
, Col2 varchar(20)
)
insert #Something values
(1, 'Sam')
, (1, 'Samantha')
, (2, 'Oliver')
, (3, 'Ollie')
select x.Col1
, x.Col2
from
(
select *
, RowNum = ROW_NUMBER() over(partition by Col1 order by LEN(Col2) desc)
from #Something
) x
where x.RowNum = 1
---EDIT---
To demonstrate that this code still returns the desired output from your new sample data...
declare #Something table
(
Col1 int
, Col2 varchar(20)
)
insert #Something values
(1, 'Sam')
, (1, 'Samantha')
, (2, 'Oliver')
, (3, 'Ollie')
, (4, 'Benjamin')
, (4, 'Ben')
select x.Col1
, x.Col2
from
(
select *
, RowNum = ROW_NUMBER() over(partition by Col1 order by LEN(Col2) desc)
from #Something
) x
where x.RowNum = 1
This returns:
1 Samantha
2 Oliver
3 Ollie
4 Benjamin
Since you claim this still doesn't work you need to provide an example of how or why this doesn't work. You keep mentioning LIKE but have not explained or demonstrated how that comes into play here. Help me understand the problem and I can help you find a solution.
I Ended up figuring it out and using the following code:
SELECT D.RID, ProductID, D.Product, [Length] FROM
(
SELECT RID, MAX([Length]) AS theLength
FROM SortData GROUP BY RID
) AS X
INNER JOIN SortData AS D ON D.RID = X.RID AND D.[Length] = X.theLength
WHERE D.Product LIKE Product
GO

Recursive hierarchical traversal in MSSQL

Using MSSQL, I am trying to traverse through a table with parent child relationship. I need my result set so that I get all elements in a proper indented manner, till the last leaf, like shown below.
A parent item 36 has 2 children 17 and 18. Each of those children 17 and 18 have one more children to them 26, 42 respectively
36 - 17
17 - 26
36 - 18
18 - 42
But my recursion is working OK in terms of the data traversal, but order wise, it is failing. My recursive query gives me the following output
36 - 17
36 - 18
17 - 26
18 - 42
It brings all levels at once, stores them in a record, then traverses through each of the children of those levels.
Oracle's "connect by prior" seems to be working fine, but, MSSQL is not. I am pasting a sample of what I am using
WITH SRC (Level, PARITEMID, CHIITEMID) AS
(
SELECT
0 as Level,
PI.pitem_id as PARITEMID,
CI.pitem_id as CHIITEMID
FROM PI, CI JOIN <Condition> where PI.PITEM_ID =
UNION ALL
SELECT
Level + 1,
PI1.pitem_id as PARITEMID,
CI1.pitem_id AS CHIITEMID
FROM PI1, CI1 JOIN <Condition>
)
Select * from SRC
Is there something I need to do on the SRC I obtain by ordering it, or is there fundamentally something wrong with the recursion itself?
Wasn't clear on your field names so I assumed the following:
cItem_ID - Child ID
pItem_ID - Parent ID
item_Title - Item name/Description
Also, not clear on the Sequence, So I assumed Item_Title (alphabetical). However, you can use any field available. (see the the "10000+Row_Number()" lines)
I should note, cteR1, and cteR2 are not necessary. I do like the range keys, they server many purposes. If you do remove them, just set the final Order By to Order By A.Seq
Declare #MyTable table (pItem_ID int,cItem_ID int,item_Title varchar(50))
Insert into #MyTable values
(null,36,'Item 36')
,(36,17,'Item 17')
,(17,26,'Item 26')
,(36,18,'Item 18')
,(18,42,'Item 42')
Declare #Top int = null --<< Sets top of Hier Try 7
Declare #Nest varchar(25) = '|-----' --<< Optional: Added for readability
;with cteP as (
Select Seq = cast(10000+Row_Number() over (Order by item_Title) as varchar(500))
,cItem_ID
,pItem_ID
,Lvl=1
,item_Title
From #MyTable
Where IsNull(#Top,-1) = case when #Top is null then isnull(pItem_ID,-1) else cItem_ID end
Union All
Select Seq = cast(concat(p.Seq,'.',10000+Row_Number() over (Order by r.item_Title)) as varchar(500))
,r.cItem_ID
,r.pItem_ID
,p.Lvl+1
,r.item_Title
From #MyTable r
Join cteP p on r.pItem_ID = p.cItem_ID)
,cteR1 as (Select *,R1=Row_Number() over (Order By Seq) From cteP)
,cteR2 as (Select A.Seq,A.cItem_ID,R2=Max(B.R1) From cteR1 A Join cteR1 B on (B.Seq like A.Seq+'%') Group By A.Seq,A.cItem_ID )
Select A.R1
,B.R2
,A.cItem_ID
,A.pItem_ID
,A.Lvl
,item_Title = Replicate(#Nest,A.Lvl-1) + A.item_Title
From cteR1 A
Join cteR2 B on A.cItem_ID=B.cItem_ID
Order By A.R1
Returns

Is there a way to detect a cycle in Hierarchical Queries in SQL Server?

In Oracle, we can use the function CONNECT_BY_ISCYCLE to detect a cycle in Hierarchical Queries. I try to do the same in SQL Server. Is there a way to do this?
Thanks a lot!
Concatenate the records IDs / build a bitmap based on ROW_NUMBERs of the records and verify each new record against the list/bitmap
create table t (id int,pid int)
insert into t values (1,3),(2,1),(3,2)
List
Identify Cycles
with cte (id,pid,list,is_cycle)
as
(
select id,pid,',' + cast (id as varchar(max)) + ',',0
from t
where id = 1
union all
select t.id,t.pid,cte.list + cast (t.id as varchar(10)) + ',' ,case when cte.list like '%,' + cast (t.id as varchar(10)) + ',%' then 1 else 0 end
from cte join t on t.pid = cte.id
where cte.is_cycle = 0
)
select *
from cte
where is_cycle = 1
id pid list is_cycle
-- --- ---- --------
1 3 ,1,2,3,1, 1
Traverse Thorough Graph with Cycles
with cte (id,pid,list)
as
(
select id,pid,',' + cast (id as varchar(max)) + ','
from t
where id = 1
union all
select t.id,t.pid,cte.list + cast (t.id as varchar(10)) + ','
from cte join t on t.pid = cte.id
where cte.list not like '%,' + cast (t.id as varchar(10)) + ',%'
)
select *
from cte
id pid list
1 3 ,1,
2 1 ,1,2,
3 2 ,1,2,3,
Bitmap
ID should be a sequence of numbers starting with 1.
If necessary generate it using ROW_NUMBER.
Identify Cycles
with cte (id,pid,bitmap,is_cycle)
as
(
select id,pid,cast (power(2,id-1) as varbinary(max)) ,0
from t
where id = 1
union all
select t.id,t.pid,cast (cte.bitmap|power(2,t.id-1) as varbinary(max)),case when cte.bitmap & power(2,t.id-1) > 0 then 1 else 0 end
from cte join t on t.pid = cte.id
where cte.is_cycle = 0
)
select *
from cte
where is_cycle = 1
id pid bitmap is_cycle
1 3 0x00000007 1
Traverse Thorough Graph with Cycles
with cte (id,pid,bitmap)
as
(
select id,pid,cast (power(2,id-1) as varbinary(max))
from t
where id = 1
union all
select t.id,t.pid,cast (cte.bitmap|power(2,t.id-1) as varbinary(max))
from cte join t on t.pid = cte.id
where cte.bitmap & power(2,t.id-1) = 0
)
select *
from cte
id pid bitmap
1 3 0x00000001
2 1 0x00000003
3 2 0x00000007
If you look at a specific path across a hierarchy, you can say that it is a singled/doubled linked list (normally singled).
One easy way of making sure that you don't have a close loop, it to traverse the chain through two paths, each with its own index, one that advances by one position while the other by two.
If there is no closed loop, one of the indices will fall at some point (e.g. will reach the root node of the hierarchy which does not have any parent). If there is a loop, you will reach a point where the two indices point to the same node within the chain.
This is a rather old method but works beautifully.

How do I look for matching data in SQL?

I have a database table for a todo list application, and i need a way to track tasks which are dependant on other tasks, I already have a table with ID,title, description, IsComplete and a DependsOnTask column, containing the unique identifier for the task another given task is dependant on.
the problem is, when I try the below in SQL it doesn't give any results!
SELECT TOP 1000 [id]
,[title]
,[description]
,[complete]
,[DependsOnTask]
FROM [master].[dbo].[ToDoItems] where ToDoItems.id =ToDoItems.DependsOnTask;
So my question is, is there a way to find all records with a unique identifier matching DependsOnTask?
Thanks in advance :)
You are missing a JOIN:
SELECT tdi.*, dot.*
FROM dbo.ToDoItems tdi JOIN
dbo.ToDoItems dot
ON dot.id = tdi.DependsOnTask;
This returns all tasks where DependsOnTask is not null, along with information from that record.
Notes:
You don't need to use square braces when they are not necessary. They just clutter up queries.
Use table aliases and qualify column names, so you know where columns are coming from.
You need to use an explicit JOIN for references back to the same table.
If you have a hierarchical structure and task could have a parent and that parent is a child to another task, yo can use recursive CTE to find all hierarchy of determined task.
Let me show an example.
You got structure like this:
SELECT *
FROM (VALUES
(1,'Title1','Do some stuff 1', 0, NULL),
(2,'Title2','Do some stuff 2', 0, NULL),
(3,'Title3','Do some stuff 3', 1, 1),
(4,'Title4','Do some stuff 4', 1, 1),
(5,'Title5','Do some stuff 5', 0, 2),
(6,'Title6','Do some stuff 6', 1, 2),
(7,'Title7','Do some stuff 7', 0, 4),
(8,'Title8','Do some stuff 8', 0, NULL)
) as t([id],[title],[description],[complete],[DependsOnTask])
So task 1 has 2 child tasks - 3 and 4. The 4th task got 1 child - 7. You want to get all child tasks of task with id = 1:
DECLARE #taskid int = 1
;WITH cte AS (
SELECT [id]
,[title]
,[description]
,[complete]
,[DependsOnTask]
FROM [ToDoItems]
WHERE [id] = #taskid
UNION ALL
SELECT t.*
FROM [ToDoItems] t
INNER JOIN cte c
ON c.id = t.DependsOnTask
)
SELECT *
FROM cte
Output:
id title description complete DependsOnTask
1 Title1 Do some stuff 1 0 NULL
3 Title3 Do some stuff 3 1 1
4 Title4 Do some stuff 4 1 1
7 Title7 Do some stuff 7 0 4
So if you change last select to:
SELECT #taskid as main,
id,
DependsOnTask
FROM cte
You will get:
main id DependsOnTask
1 1 NULL
1 3 1
1 4 1
1 7 4
So you get all child tasks of Task1.
If you change CTE like this:
;WITH cte AS (
SELECT [id]
,[title]
,[description]
,[complete]
,[DependsOnTask]
,[id] as Parent
FROM [ToDoItems]
WHERE [DependsOnTask] IS NULL
UNION ALL
SELECT t.*,
c.Parent
FROM [ToDoItems] t
INNER JOIN cte c
ON c.id = t.DependsOnTask
)
SELECT Parent,
id,
DependsOnTask
FROM cte
You will got all you need: Parent task, Child tasks and what are they dependent on:
Parent id DependsOnTask
1 1 NULL
2 2 NULL
8 8 NULL
2 5 2
2 6 2
1 3 1
1 4 1
1 7 4

CTE Recursion to get tree hierarchy

I need to get an ordered hierarchy of a tree, in a specific way. The table in question looks a bit like this (all ID fields are uniqueidentifiers, I've simplified the data for sake of example):
EstimateItemID EstimateID ParentEstimateItemID ItemType
-------------- ---------- -------------------- --------
1 A NULL product
2 A 1 product
3 A 2 service
4 A NULL product
5 A 4 product
6 A 5 service
7 A 1 service
8 A 4 product
Graphical view of the tree structure (* denotes 'service'):
A
___/ \___
/ \
1 4
/ \ / \
2 7* 5 8
/ /
3* 6*
Using this query, I can get the hierarchy (just pretend 'A' is a uniqueidentifier, I know it isn't in real life):
DECLARE #EstimateID uniqueidentifier
SELECT #EstimateID = 'A'
;WITH temp as(
SELECT * FROM EstimateItem
WHERE EstimateID = #EstimateID
UNION ALL
SELECT ei.* FROM EstimateItem ei
INNER JOIN temp x ON ei.ParentEstimateItemID = x.EstimateItemID
)
SELECT * FROM temp
This gives me the children of EstimateID 'A', but in the order that it appears in the table. ie:
EstimateItemID
--------------
1
2
3
4
5
6
7
8
Unfortunately, what I need is an ordered hierarchy with a result set that follows the following constraints:
1. each branch must be grouped
2. records with ItemType 'product' and parent are the top node
3. records with ItemType 'product' and non-NULL parent grouped after top node
4. records with ItemType 'service' are bottom node of a branch
So, the order that I need the results, in this example, is:
EstimateItemID
--------------
1
2
3
7
4
5
8
6
What do I need to add to my query to accomplish this?
Try this:
;WITH items AS (
SELECT EstimateItemID, ItemType
, 0 AS Level
, CAST(EstimateItemID AS VARCHAR(255)) AS Path
FROM EstimateItem
WHERE ParentEstimateItemID IS NULL AND EstimateID = #EstimateID
UNION ALL
SELECT i.EstimateItemID, i.ItemType
, Level + 1
, CAST(Path + '.' + CAST(i.EstimateItemID AS VARCHAR(255)) AS VARCHAR(255))
FROM EstimateItem i
INNER JOIN items itms ON itms.EstimateItemID = i.ParentEstimateItemID
)
SELECT * FROM items ORDER BY Path
With Path - rows a sorted by parents nodes
If you want sort childnodes by ItemType for each level, than you can play with Level and SUBSTRING of Pathcolumn....
Here SQLFiddle with sample of data
This is an add-on to Fabio's great idea from above. Like I said in my reply to his original post. I have re-posted his idea using more common data, table name, and fields to make it easier for others to follow.
Thank you Fabio! Great name by the way.
First some data to work with:
CREATE TABLE tblLocations (ID INT IDENTITY(1,1), Code VARCHAR(1), ParentID INT, Name VARCHAR(20));
INSERT INTO tblLocations (Code, ParentID, Name) VALUES
('A', NULL, 'West'),
('A', 1, 'WA'),
('A', 2, 'Seattle'),
('A', NULL, 'East'),
('A', 4, 'NY'),
('A', 5, 'New York'),
('A', 1, 'NV'),
('A', 7, 'Las Vegas'),
('A', 2, 'Vancouver'),
('A', 4, 'FL'),
('A', 5, 'Buffalo'),
('A', 1, 'CA'),
('A', 10, 'Miami'),
('A', 12, 'Los Angeles'),
('A', 7, 'Reno'),
('A', 12, 'San Francisco'),
('A', 10, 'Orlando'),
('A', 12, 'Sacramento');
Now the recursive query:
-- Note: The 'Code' field isn't used, but you could add it to display more info.
;WITH MyCTE AS (
SELECT ID, Name, 0 AS TreeLevel, CAST(ID AS VARCHAR(255)) AS TreePath
FROM tblLocations T1
WHERE ParentID IS NULL
UNION ALL
SELECT T2.ID, T2.Name, TreeLevel + 1, CAST(TreePath + '.' + CAST(T2.ID AS VARCHAR(255)) AS VARCHAR(255)) AS TreePath
FROM tblLocations T2
INNER JOIN MyCTE itms ON itms.ID = T2.ParentID
)
-- Note: The 'replicate' function is not needed. Added it to give a visual of the results.
SELECT ID, Replicate('.', TreeLevel * 4)+Name 'Name', TreeLevel, TreePath
FROM MyCTE
ORDER BY TreePath;
I believe that you need to add the following to the results of your CTE...
BranchID = some kind of identifier that uniquely identifies the branch. Forgive me for not being more specific, but I'm not sure what identifies a branch for your needs. Your example shows a binary tree in which all branches flow back to the root.
ItemTypeID where (for example) 0 = Product and 1 = service.
Parent = identifies the parent.
If those exist in the output, I think you should be able to use the output from your query as either another CTE or as the FROM clause in a query. Order by BranchID, ItemTypeID, Parent.

Resources