SQL Server get path with recursive CTE - sql-server

I want to get the path for each department with this format 1.1, 1.2 and so on.
This is my department table :
id name parentId
--------------------
1 Dep 1 0
2 Dep 2 1
3 Dep 3 0
4 Dep 4 1
5 Dep 5 4
6 Dep 6 2
This is my recursive CTE that give me the parents and children in a flat table starting from a root department.
WITH recursiveCte (parentId, id, name, Level)
AS
(
-- Anchor member definition
SELECT
d.parentId, d.id, d.name,
0 AS Level
FROM
Department AS d
WHERE
parentId = 0
UNION ALL
-- Recursive member definition
SELECT
d.parentId, d.id, d.name,
Level + 1
FROM
Department AS d
INNER JOIN
recursiveCte AS r ON d.parentId = r.id
)
-- Statement that executes the CTE
SELECT parentId,id, name, Level
FROM recursiveCte
ORDER BY id
Current results:
parentId id name Level
-------------------------------
0 1 Dep 1 0
1 2 Dep 2 1
0 3 Dep 3 0
1 4 Dep 4 1
4 5 Dep 5 2
2 6 Dep 6 2
Desired results:
parentId id name Level Path
--------------------------------------
0 1 Dep 1 0 1
1 2 Dep 2 1 1.1
2 6 Dep 6 2 1.1.1
1 4 Dep 4 1 1.2
4 5 Dep 5 2 1.2.1
0 3 Dep 3 0 2
Thanks.

Here is a working solution. It is difficult to describe in words why this works, so I recommend taking apart the query to see how it works yourself. Basically, we recursively build the path string you want to see, using ROW_NUMBER to keep track to which particular parent each new path addition belongs.
recursiveCte (parentId, id, name, Level, Path, FullPath) AS (
SELECT d.parentId, d.id, d.name, 0 AS Level,
CAST(ROW_NUMBER() OVER (ORDER BY d.id) AS nvarchar(max)),
RIGHT('000' + CAST(ROW_NUMBER() OVER (ORDER BY d.id) AS nvarchar(max)), 3)
FROM Department AS d
WHERE parentId = 0
UNION ALL
SELECT d.parentId, d.id, d.name, r.Level + 1,
r.Path + '.' +
CAST(ROW_NUMBER() OVER (PARTITION BY r.Level ORDER BY d.id) AS nvarchar(max)),
r.FullPath + '.' + RIGHT('000' + CAST(ROW_NUMBER() OVER
(PARTITION BY r.Level ORDER BY d.id) AS nvarchar(max)), 3)
FROM Department AS d
INNER JOIN recursiveCte AS r
ON d.parentId = r.id
)
SELECT parentId, id, name, Level, Path, FullPath
FROM recursiveCte
ORDER BY FullPath;
Demo
Edit:
I slightly edited my original answer so that it now sorts the path string using a fixed-width version, i.e. every number has a fixed width of 3 digits. This means that 001 will always sort before 010, which is the behavior we want.

Related

Grouping in SQL Server

I have data similar to below. I need to group the status column based on the occurrence of data.
Id Status Value
1 K 1
2 K 3
3 K 2
4 B 2
5 B 3
6 K 6
7 J 5
8 J 2
I want data as below
Status Value
K 6
B 5
K 6
J 7
I need the cumulative sum of the value column.In the status column if the data is same consecutively, then I need to add the value columns. I cannot apply group by. In the example given K is repeated twice, because they are not consecutive.
I have tried below query, but it doesn't work as required.
select Status,
(select sum(value)
from table t2
where
t2.Status = t.Status and
t2.SNO <= t.SNO
) as total
from table t;
This is a Gaps and Islands Question
I tackle these by using the incrementing Id and combing this with ROW_NUMBER window function
--Using a CTE just to replicate the sample data
;WITH cteX (Id,Status,Value)
AS(
SELECT 1,'K', 1 UNION ALL
SELECT 2,'K', 3 UNION ALL
SELECT 3,'K', 2 UNION ALL
SELECT 4,'B', 2 UNION ALL
SELECT 5,'B', 3 UNION ALL
SELECT 6,'K', 6 UNION ALL
SELECT 7,'J', 5 UNION ALL
SELECT 8,'J', 2
)
SELECT
Grp = Id - ROW_NUMBER()OVER( PARTITION BY X.Status ORDER BY X.Id)
, X.Id
, X.Status
, X.Value
FROM
cteX X
ORDER BY
X.Id
This gives this result set, note the Grp column
Grp Id Status Value
------- ------- ------- -------
0 1 K 1
0 2 K 3
0 3 K 2
3 4 B 2
3 5 B 3
2 6 K 6
6 7 J 5
6 8 J 2
Then combine with a CTE or derived table you can get your expected output
--Using a CTE just to replicate the sample data
;WITH cteX (Id,Status,Value)
AS(
SELECT 1,'K', 1 UNION ALL
SELECT 2,'K', 3 UNION ALL
SELECT 3,'K', 2 UNION ALL
SELECT 4,'B', 2 UNION ALL
SELECT 5,'B', 3 UNION ALL
SELECT 6,'K', 6 UNION ALL
SELECT 7,'J', 5 UNION ALL
SELECT 8,'J', 2
)
SELECT Y.Status
, Value = SUM(Y.Value)
FROM
(
SELECT TOP 100 PERCENT
Grp = Id - ROW_NUMBER()OVER( PARTITION BY X.Status ORDER BY X.Id)
, X.Id
, X.Status
, X.Value
FROM
cteX X
ORDER BY
X.Id
) Y
GROUP BY
Y.Grp, Y.Status
Output
Status Value
------- -------
B 5
J 7
K 6
K 6
Update Question include "Preserve order" solution
Just include an Order by MIN(Id)
--Using a CTE just to replicate the sample data
;WITH cteX (Id,Status,Value)
AS(
SELECT 1,'K', 1 UNION ALL
SELECT 2,'K', 3 UNION ALL
SELECT 3,'K', 2 UNION ALL
SELECT 4,'B', 2 UNION ALL
SELECT 5,'B', 3 UNION ALL
SELECT 6,'K', 6 UNION ALL
SELECT 7,'J', 5 UNION ALL
SELECT 8,'J', 2
)
SELECT
Y.[Status]
,[Value] = SUM(Y.[Value])
FROM
(
SELECT
Grp = Id - ROW_NUMBER()OVER( PARTITION BY X.[Status] ORDER BY X.Id)
, X.Id
, X.[Status]
, X.[Value]
FROM
cteX X
) Y
GROUP BY
Y.Grp, Y.[Status]
ORDER BY
MIN(Y.Id) --preserve the Status Order
Output
Status Value
------- -------
K 6
B 5
K 6
J 7

Group value via range and value sql query

I have two table .
Table1 have following fields.
From To id
---- ---- ----
0 0 1
1 5 2
5 10 3
10 15 4
Table 2:
Table 1 ID Value
--------- -------
1 10
2 10
3 15
4 10
current output:
from To Value
----- ------ -------
0 15 10
5 10 15
Required Output
From To Value
------ ---- ------
0 5 10
5 10 15
10 15 10
How get the output like
code
SELECT MIN(DiscountFrom) FromDiscount ,
MAX(DiscountTo) Todiscount ,
Amount
FROM table1 t1
JOIN table2 t2 ON t1.id = t2.id
GROUP BY Amount
Here for me grouping makes no sense so, i used lead() function to access next record if 0 found
select distinct
case when t.[from] = 1 then 0 else t.[from] end [from],
case when t.[to] = 0 then lead(t.[to]) over (order by t.id) else t.[to] end [to],
t1.value
from table1 t
join table2 t1 on t1.id = t.id
Result :
from to value
0 5 10
5 10 15
10 15 10
It looks like your data is inconsistent, for this to work i had to change the first record to
From To id
---- ---- ----
0 1 1
then this works for all cases
;WITH test1
AS (SELECT
t.id
,[from]
,[to]
,value
FROM
table1 t
JOIN table2 t1
ON t1.id = t.id),
MyTest
AS (SELECT
Anchor.[from]
,Anchor.[To]
,Anchor.value
,1 AS Grp
FROM
test1 AS Anchor
WHERE
[From] = 0
UNION ALL
SELECT
Child.[from]
,Child.[To]
,Child.value
,CASE WHEN Mytest.value = child.value THEN 0 ELSE 1 END + MyTest.Grp AS grp
FROM
test1 AS Child
INNER JOIN MyTest
ON Mytest.[To] = child.[From])
SELECT
Min([From]) AS [From]
,Max([To]) AS [To]
,Max(Value) AS Value
FROM
mytest
GROUP BY
Grp

how to select rows where column value has changed

I have a table in which I have few columns like below:
Cusnbr Name LoadNumber
1 Z 10
1 Z 9
1 Z 8
1 C 7
1 C 6
1 C 5
1 B 4
1 B 3
1 A 2
1 A 1
it is just for one cusnbr there are million of cusnbr like this..
I want output like below
Cusnbr Name LoadNumber
1 C 7
1 B 4
1 A 2
For that I write below query in sql server 2008:
;With x as
(
Select * ,rn=Row_number() over (order by cusnbr,loadnumber) from table
)
select x.* from x left outer join x as y on x.rn=y.rn+1
and x.name<>y.name where y.name is not null
but I am not getting the desired output in the above code I am getting last Z also which I don't want and I am getting irregular data not in the correct form in which I want
Any help will be appreciated !!
like this I want but not able to get the desired output
I use this example
Though the question is not clear to me , Guessing from the output I have tried out Dense Rank . I guessed you want the record with highest LoadNumber with the same name .
Select * from cteTrial where LoadNumber in (
Select MAX(x.LoadNumber) as LoadNumber from (
Select cusnbr , name , LoadNumber , DENSE_RANK() over (order by Name desc )
as Dense from cteTrial) as x group by x.Dense
)
If you can use CTE it will produce better performance .
i written the code as per expected
;With cte(Cusnbr , Name , LoadNumber)
AS
(
SELECT 1,'Z', 10 Union all
SELECT 1,'Z', 9 Union all
SELECT 1,'Z', 8 Union all
SELECT 1,'C', 7 Union all
SELECT 1,'C', 6 Union all
SELECT 1,'C', 5 Union all
SELECT 1,'B', 4 Union all
SELECT 1,'B', 3 Union all
SELECT 1,'A', 2 Union all
SELECT 1,'A', 1
)
SELECT cusnbr,
NAME,
loadnumber
FROM (SELECT *,
Row_number()
OVER(
partition BY NAME
ORDER BY loadnumber DESC) AS RNk,
Row_number()
OVER(
ORDER BY (SELECT 1)) - 1 AS RNO
FROM (SELECT *
FROM cte)dt)DT2
WHERE DT2.rnk = 1
AND rno > 0
ORDER BY NAME DESC
Result
cusnbr NAME loadnumber
-------------------------
1 C 7
1 B 4
1 A 2

How to uniquely number subnodes of a parent

I have a table like so
ID Node ParentID
1 A 0
2 B 1
3 C 1
4 D 2
5 E 2
6 F 3
7 G 3
8 H 3
9 I 4
10 J 4
11 K 10
12 L 11
I need a query to generate a 'position' field with the order that a node appears within its parent. Example below
ID Node ParentID Positon
1 A 0 0
2 B 1 0
3 C 1 1
4 D 2 0
5 E 2 1
6 F 3 0
7 G 3 1
8 H 3 2
9 I 4 0
10 J 4 1
11 K 10 0
12 L 11 0
select *
, row_number() over (partition by ParentID order by ID) - 1 as Position
from YourTable
As an update query:
update yt
set Position = nr
from (
select *
, row_number() over (partition by ParentID order by ID) - 1 rn
from YourTable
) yt
To update position in the original table join it to already suggested statement, either as sub-query or CTE:
;with cte (ID, Pos)
as (
select ID, row_number() over (partition by ParentID order by ID) - 1
from [Table]
)
update T
set T.Position = cte.Pos
from [Table] T
join cte on cte.ID = T.ID

Finding customers with identical orders

I need to find customers who have made identical orders. (Using T-SQL)
Order
OrderID Customerer
1 2
2 5
3 6
4 2
5 4
6 6
7 8
OrderLine
OrderLineID OrderID OrderDate OrderType Quantity Reference
1 1 01/01/2011 1 1 Coca Cola
2 1 01/01/2011 1 3 Tea
3 2 02/02/2011 2 1 Coffee
4 2 02/02/2011 2 2 Solo
5 2 03/02/2011 1 1 Soda
6 3 03/02/2011 1 3 Tea
7 3 03/02/2011 1 1 Coca Cola
8 4 05/06/2011 1 1 Beer
9 5 06/06/2011 2 1 Tea
10 5 06/06/2011 2 1 Coca Cola
11 6 07/07/2011 1 1 Coffee
12 6 07/07/2011 1 2 Solo
13 6 07/07/2011 1 1 Soda
14 6 07/07/2011 1 1 Beer
15 7 08/08/2011 1 1 Beer
Here orders with OrderID 1 and 3 are considered to be identical because the number for orderlines, "Quantity" and "Reference" are identical on both orders. Meaning that customer 2 and 6 have placed identical orders.
Order 5 are not identical to order 1 and 3 because Quantity differ.
Order 2 are not identical to order 6 because orderlines differ.
Order 4 and 7 are also identical.
I am searching for a ressult like this:
IdenticalOrders
OrderID CustomeerID
1 2
3 6
4 2
7 8
It seems like an easy task, but I just can't understand where to start.
(I am still new to t-sql :-) )
Here's one way.
SELECT O1.OrderID ,
O1.Customer ,
O2.OrderID ,
O2.Customer
FROM [Order] O1
JOIN [Order] O2 ON O1.OrderID < O2.OrderID
AND O1.Customer <> O2.Customer
WHERE NOT EXISTS ( SELECT Quantity ,
Reference
FROM OrderLine
WHERE O1.OrderID = OrderLine.OrderID
EXCEPT
SELECT Quantity ,
Reference
FROM OrderLine
WHERE O2.OrderID = OrderLine.OrderID )
AND NOT EXISTS ( SELECT Quantity ,
Reference
FROM OrderLine
WHERE O2.OrderID = OrderLine.OrderID
EXCEPT
SELECT Quantity ,
Reference
FROM OrderLine
WHERE O1.OrderID = OrderLine.OrderID )
You can also use XML PATH to simulate GROUP_CONCAT then JOIN the two result sets
DECLARE #T TABLE
(
OrderId INT PRIMARY KEY,
Customer INT ,
complete_order VARCHAR(MAX)
)
INSERT INTO #T
SELECT *
FROM [Order] O
CROSS APPLY ( SELECT CAST(Quantity AS VARCHAR(30))
+ '~' + Reference + '~~'
FROM OrderLine OL
WHERE OL.OrderID = O.OrderID
ORDER BY Reference ,
Quantity
FOR
XML PATH('')
) T ( complete_order )
SELECT T1.OrderId,
T1.Customer
FROM #T T1
WHERE EXISTS ( SELECT *
FROM #T T2
WHERE T1.Customer <> T2.Customer
AND T1.OrderId <> T2.OrderId
AND T1.complete_order = T2.complete_order )
This is an extension of Martin's second suggestion. This will show all matching combinations without any repetitions.
;With FmtOL(customer, orderid, complete_order) as
(
SELECT customer, orderid, complete_order
FROM Order O
cross apply ( SELECT CAST(Quantity AS VARCHAR(30))
+ '~' + Reference + '~~'
FROM OrderLine OL
WHERE OL.OrderID = O.OrderID
ORDER BY Reference ,
Quantity
FOR
XML PATH('')
) T ( complete_order )
)
SELECT T1.OrderId,
T1.Customer,
STUFF(C1.a, 1, 2, '') as [SameAs]
FROM FmtOL T1
Cross apply ( SELECT '; ' + 'Customer ' + Cast(T2.Customer as varchar(30))
+ '''s order ' + Cast(T2.OrderID as varchar(30))
FROM FmtOL T2
WHERE T1.Customer < T2.Customer
AND T1.OrderId < T2.OrderId
AND T1.complete_order = T2.complete_order
order by ';' + Cast(T2.Customer as varchar(30))
+ '''s order ' + Cast(T2.OrderID as varchar(30))
, t2.orderid
for xml path('')
) C1 (a)
where C1.a is not null
Results should look like this:
OrderId Customer SameAs
1 2 Customer 6's order 3
4 2 Customer 8's order 7
Here's the most simple approach.
-- sample table
create table x
(
LineId int identity(1, 1)
,InvoiceFk int
,ProductFk int
,Quantity int
)
-- sample data
insert into x
(InvoiceFk, ProductFk, Quantity) values
(11, 1, 1)
,(11, 2, 1)
,(11, 3, 1)
,(12, 1, 2)
,(12, 2, 2)
,(12, 3, 2)
,(13, 1, 3)
,(13, 2, 3)
,(13, 3, 3)
-- your order, probably from a parameter
declare #order table
(
InvoiceFk int
,ProductFk int
,Quantity int
)
insert into #order
(InvoiceFk, ProductFk, Quantity) values
(14, 1, 1) -- duplicate invoice 11
,(14, 2, 1)
,(14, 3, 1)
-- your order unique checksum
declare #orderCheck int
select #orderCheck = checksum_agg(checksum(ProductFk, Quantity))
from #order
-- test your order in existing data
declare #match int
select #match =
(
select TOP 1 InvoiceFk from
(
select
InvoiceFk
,checksum_agg(Col1) as Col2
from
(
select
InvoiceFk
,checksum(productfk, quantity) as Col1
from x
) as T1
group by
InvoiceFk
) as T2
where
T2.Col2 = #orderCheck
)
-- evaluate if your order is unique or not
if (#match is not null)
begin
print 'Identical to invoice: ' + Str(#match);
end
else
begin
print 'Order is unique';
end
-- clean up sample table
drop table x
Best of luck!

Resources