Recursive SQL Query descending - sql

I use a recursive query to identify all related child categories of a given category-id.
WITH parent AS ( SELECT ParentId, Title, Id
FROM [CocoDb].[dbo].[Categories]
WHERE Id = #CategoryId),tree AS
(
SELECT x.ParentId, x.Id, x.Title
FROM [CocoDb].[dbo].[Categories] x
INNER JOIN parent ON x.ParentId = parent.Id
UNION ALL
SELECT y.ParentId, y.Id, y.Title
FROM [CocoDb].[dbo].[Categories] y
INNER JOIN tree t ON y.ParentId = t.Id
)
SELECT * FROM Tree
My table is:
Categories (ID INT PRIMARY KEY, ParentId INT, Title VARCHAR(MAX))
Question for SQL Server: can I have a query that returns ALL categories?
As example:
Books (Id: 1, Parent: 0)
Horror (Id:2, Parent: 1)
Steven King (Id:3, Parent: 2)
And now if I ask for Steven King (Id 3) I will get all related categories like in this case Books and Horror.
Is that possible with a recursive SQL query?
Best regards

Test Data
DECLARE #Categories TABLE(ID INT PRIMARY KEY, ParentId INT, Title VARCHAR(MAX))
INSERT INTO #Categories
VALUES (1, 0, 'Books'),(2, 1, 'Horro'),(3, 2, 'Steven King')
,(4, 3, 'Value 4'),(5, 4, 'Value 5')
Query
;WITH ParentCte
AS
(
SELECT p.ID ,p.ParentId ,p.Title
FROM #Categories p
WHERE p.Title = 'Steven King'
UNION ALL
SELECT c.ID ,c.ParentId,c.Title
FROM ParentCte cte INNER JOIN #Categories c
ON c.ID = cte.ParentId
)
SELECT *
FROM ParentCte m
Result Set
╔════╦══════════╦═════════════╗
║ ID ║ ParentId ║ Title ║
╠════╬══════════╬═════════════╣
║ 3 ║ 2 ║ Steven King ║
║ 2 ║ 1 ║ Horro ║
║ 1 ║ 0 ║ Books ║
╚════╩══════════╩═════════════╝

Related

How to do a recursive join to get the lowest level of data with TSQL

I have the following set of data:
ID ParentID
----------- ---------
8320 NULL
8321 8320
8322 8320
8323 8322
8325 NULL
8328 8325
8329 8328
What I am trying to achieve is to select all the rows that belongs to a specific ID. For instance, if I am querying ID = 8320, the following data must be returned:
ID ParentID
----------- ---------
8320 NULL
8321 8320
8322 8320
8323 8322
So far this is what I have attempted with no real success.
select *
from JobQueueLog JQL
left join JobQueueLog JQLC on
JQL.ID = JQLC.ParentID
and JQLC.ParentID is not null
where JQL.ID = 8320
Any help please?
You need to use a CTE and make a recursive query
with tmp (id, parentid) as (
select id, parentid
from rec
where id = 8320
union all
select rec.id, rec.parentid
from tmp
inner join rec on tmp.id = rec.parentid
)
select id, parentid
from tmp
Recursive CTE's are meant to do this
WITH data
AS (SELECT *
FROM ( VALUES ('8320',NULL),
('8321','8320'),
('8322','8320'),
('8323','8322'),
('8325',NULL),
('8328','8325'),
('8329','8328')) tc ([ID], [ParentID])),
rec_cte
AS (SELECT [ID],
[ParentID]
FROM data
WHERE [ID] = '8320'
UNION ALL
SELECT r.[ID],
r.[ParentID]
FROM rec_cte rc
JOIN data r
ON r.[ParentID] = rc.ID)
SELECT [ID],
[ParentID]
FROM rec_cte
Result :
╔══════╦══════════╗
║ ID ║ ParentID ║
╠══════╬══════════╣
║ 8320 ║ NULL ║
║ 8321 ║ 8320 ║
║ 8322 ║ 8320 ║
║ 8323 ║ 8322 ║
╚══════╩══════════╝
;with cte
as
(
select * from #temp where id=8320
union all
select t.* from
cte c
join
#temp t on c.id=t.parentid
)
select * from cte

How to write the query?

I have one table that contains customers (The goal of this table was to be able to add fields without DB-Update). The table looks like this:
CustId Property PropertyValue
1 Name Smith
1 Email smith#gmail.com
2 Name Donalds
2 Email donalds#gmail.com
3 Name john
(The customer 3 has no entry for "Email" in the table)
Expected result: I want to get one line per client (Mail) and if the customer has no email, display still one line with NULL.
CustId Property PropertyValue
1 Email smith#gmail.com
2 Email donalds#gmail.com
3 Email NULL
Has someone the solution ?
Query 1
Select t1.CustId
, ISNULL(t2.Property ,'Email') AS Property
, t2.PropertyValue
FROM TableName t1
LEFT JOIN TableName t2 ON t1.CustId = t2.CustId
AND t2.Property = 'Email'
WHERE t1.Property = 'Name'
Result Set 1
╔════════╦══════════╦═══════════════════╗
║ CustId ║ Property ║ PropertyValue ║
╠════════╬══════════╬═══════════════════╣
║ 1 ║ Email ║ smith#gmail.com ║
║ 2 ║ Email ║ donalds#gmail.com ║
║ 3 ║ Email ║ NULL ║
╚════════╩══════════╩═══════════════════╝
Query 2
Another query for a more readable result set should look something like....
Select t1.CustId
, t1.PropertyValue [CustomerName]
, t2.PropertyValue [CustomerEmail]
FROM TableName t1
LEFT JOIN TableName t2 ON t1.CustId = t2.CustId
AND t2.Property = 'Email'
WHERE t1.Property = 'Name'
Result Set 2
╔════════╦══════════════╦═══════════════════╗
║ CustId ║ CustomerName ║ CustomerEmail ║
╠════════╬══════════════╬═══════════════════╣
║ 1 ║ Smith ║ smith#gmail.com ║
║ 2 ║ Donalds ║ donalds#gmail.com ║
║ 3 ║ john ║ NULL ║
╚════════╩══════════════╩═══════════════════╝
DECLARE #t TABLE (
CustId INT,
Property VARCHAR(50),
PropertyValue VARCHAR(50)
)
INSERT INTO #t (CustId, Property, PropertyValue)
VALUES
(1, 'Name', 'Smith'),
(1, 'Email', 'smith#gmail.com'),
(2, 'Name', 'Donalds'),
(2, 'Email', 'donalds#gmail.com'),
(3, 'Name', 'john')
SELECT CustId
, Name = 'Email'
, Value = MAX(CASE WHEN Property = 'Email' THEN PropertyValue END)
FROM #t
GROUP BY CustId
You can do it using a derived table containing all possible ID's , and then left joining only to the Emails on the original table:
SELECT t.custID,'EMAIL',s.PropertyValue
FROM(SELECT DISTINCT custID
FROM YourTable) t
LEFT OUTER JOIN YourTable s
ON(t.custID = s.custID and s.property = 'Email')
Can also be done with a correlated query:
SELECT DISTINCT t.CustID,'EMAIL',
(SELECT s.PropertyValue
FROM YourTable s
WHERE s.custID = t.custID and s.Property = 'Email')
FROM YourTable t
Self join with same table, property passed via variable
DECLARE #prop nvarchar(max) = 'Email'
SELECT DISTINCT c.CustId, #prop as Property, c1.PropertyValue
FROM yourtable c
LEFT JOIN yourtable c1
ON c.CustId = c1.CustId and c1.Property = #prop
Output will be as you posted in your question.
SELECT CustId
, MIN(CASE WHEN Property IS NULL THEN 'Email' ELSE Property END) Property
, MIN(PropertyValue) PropertyValue
FROM TableName
GROUP BY CustId
HAVING Property = 'Email';

update table with values in related records

I have a table which must be with next structure:
╔════╦═══════╦════╦═════╗
║ id ║ a ║ c ║ b ║
╠════╬═══════╬════╬═════╣
║ 55 ║ 56;57 ║ ║ P25 ║
║ 56 ║ ║ 56 ║ 25 ║
║ 57 ║ ║ 57 ║ 25 ║
╚════╩═══════╩════╩═════╝
where:
1) record with id=55 is a parent record and
2) records with id=56, id=57 (listed in a column and separated with semicolon) are child records
At first table is next
╔════╦═══════╦════╦═════╗
║ id ║ a ║ c ║ b ║
╠════╬═══════╬════╬═════╣
║ 55 ║ 56;57 ║ ║ ║
║ 56 ║ ║ 56 ║ ║
║ 57 ║ ║ 57 ║ ║
╚════╩═══════╩════╩═════╝
so I must to update table such as first table
For this purpose I created next CTE
with My_CTE(PId, a, c, b, newC, inde) as
(
select
ST.PID, ST.a, ST.c, ST.b, res.C,
ind = case
when ST.a != ''
then (dense_rank() over(order by ST.a))
end
from STable as ST
outer APPLY
fnSplit(ST.a) as res
where (not(ST.a = '') or not(ST.c = ''))
)
UPDATE STable
Set b =
cte.inde
From STable as st
Join My_CTE as cte on st.PID = cte.PId;
GO
As a result I have table with next values
╔════╦═══════╦════╦═════╗
║ id ║ a ║ c ║ b ║
╠════╬═══════╬════╬═════╣
║ 55 ║ 56;57 ║ ║ 25 ║
║ 56 ║ ║ 56 ║ ║
║ 57 ║ ║ 57 ║ ║
╚════╩═══════╩════╩═════╝
So I need to set values in column b for children records.
Maybe it could be established in select statement of MyCTE?
Help please
I am not entirely sure if I understand your request correctly so apologies if I have misunderstood.
So you have already managed to get the result set to here
╔════╦═══════╦════╦═════╗
║ id ║ a ║ c ║ b ║
╠════╬═══════╬════╬═════╣
║ 55 ║ 56;57 ║ ║ 25 ║
║ 56 ║ ║ 56 ║ ║
║ 57 ║ ║ 57 ║ ║
╚════╩═══════╩════╩═════╝
Now you want this to look like this
╔════╦═══════╦════╦═════╗
║ id ║ a ║ c ║ b ║
╠════╬═══════╬════╬═════╣
║ 55 ║ 56;57 ║ ║ P25 ║
║ 56 ║ ║ 56 ║ 25 ║
║ 57 ║ ║ 57 ║ 25 ║
╚════╩═══════╩════╩═════╝
Please see if this script works for you.
IF OBJECT_ID(N'tempdb..#temp')>0
DROP TABLE #temp
IF OBJECT_ID(N'tempdb..#temp1')>0
DROP TABLE #temp1
CREATE TABLE #temp (id int, a varchar(100),c varchar(100),b varchar(100))
INSERT INTO #temp VALUES ('55','56;57',' ','25')
INSERT INTO #temp VALUES ('56',' ','56',' ')
INSERT INTO #temp VALUES ('57',' ','57',' ')
SELECT * FROM #temp t
SELECT y.id, fn.string AS a,y.b
INTO #temp1
FROM #temp AS y
CROSS APPLY dbo.fnParseStringTSQL(y.a, ';') AS fn
--SELECT * FROM #temp1
UPDATE t
SET t.b=CASE WHEN CHARINDEX(';',t.a)>0 THEN 'P'+t.b ELSE t1.b END
FROM #temp t
LEFT JOIN #temp1 t1
ON t.id = t1.a
--DROP TABLE #temp
SELECT * FROM #temp t
IF OBJECT_ID(N'tempdb..#temp')>0
DROP TABLE #temp
IF OBJECT_ID(N'tempdb..#temp1')>0
DROP TABLE #temp1
Function borrowed from this link
CREATE FUNCTION [dbo].[fnParseStringTSQL]
(
#string NVARCHAR(MAX),
#separator NCHAR(1)
)
RETURNS #parsedString TABLE (string NVARCHAR(MAX))
AS
BEGIN
DECLARE #position INT
SET #position = 1
SET #string = #string + #separator
WHILE CHARINDEX(#separator, #string, #position) <> 0
BEGIN
INSERT INTO #parsedString
SELECT SUBSTRING(
#string,
#position,
CHARINDEX(#separator, #string, #position) - #position
)
SET #position = CHARINDEX(#separator, #string, #position) + 1
END
RETURN
END
This is really not an ideal data structure, but the following will do it...
CREATE TABLE #STable
(
id int primary key clustered
, a varchar(500)
, c varchar(500)
, b varchar(500)
)
INSERT INTO #STable
(id, a, c, b)
VALUES (55, '56;57', '', '25')
, (56, '', '56', '')
, (57, '', '57', '')
/* >>>>> Get all parents <<<<< */
CREATE TABLE #folks
(
sno int identity(1,1)
, id int
, a varchar(500)
)
CREATE TABLE #family
(
parent int
, child int
)
INSERT INTO #folks
(id, a)
SELECT id, a
FROM #STable
WHERE a <> ''
DECLARE #NID int
, #XID int
, #parent int
, #Children varchar(500)
, #Child int
SELECT #NID = MIN(sno), #XID = MAX(sno)
FROM #folks
/* >>>>> Loop to figure out the children <<<<< */
WHILE #NID <= #XID
BEGIN
SELECT #parent = id, #Children = a
FROM #folks
WHERE sno = #NID
WHILE LEN(#Children) > 0
BEGIN
IF CHARINDEX(';', #Children) > 0
BEGIN
SET #Child = CAST(LEFT(#Children, CHARINDEX(';', #Children) -1) as int)
SET #Children = RIGHT(#Children, LEN(#Children) - CHARINDEX(';', #Children))
INSERT INTO #family
(parent, child)
VALUES (#parent, #Child)
END
ELSE
BEGIN
SET #Child = CAST(#Children AS INT)
SET #Children = ''
INSERT INTO #family
(parent, child)
VALUES (#parent, #Child)
END
END
SET #NID = #NID + 1
END
/* >>>>> Update <<<<< */
UPDATE c
SET b = p.b
FROM #family f
INNER JOIN #STable p
on f.parent = p.id
INNER JOIN #STable c
on f.child = c.id
SELECT *
FROM #STable
DROP TABLE #STable
DROP TABLE #folks
DROP TABLE #family

Using a SQL Server table, I need to create a new table recursive?

I have a simple table of related items, like so (SQL Server db)
id Item Parent
1 2 5
2 4 5
3 5 12
4 6 2
5 10 6
I'd like to output a table that shows, for each Item a full path of all inter-related items (up to 4 "levels"), like so
id Item ParentL1 ParentL2 ParentL3 ParentL4
1 2 5 12
2 4 5 12
3 5 12
4 6 2 5 12
5 10 6 2 5 12
Thanks!
This is the simple approach.
SELECT id, t1.Item as Item,
t1.Parent as ParentL1,
t2.Parent as ParentL2,
t3.Parent as ParentL3,
t4.Parent as ParentL4
FROM Items t1
LEFT JOIN Items t2 ON t1.Parent = t2.Id
LEFT JOIN Items t3 ON t2.Parent = t3.Id
LEFT JOIN Items t4 ON t3.Parent = t4.Id
The follwoing query should do the trick
SELECT t1.id, t1.Item, t1.Parent [ParentL1], t2.Parent [ParentL2], t3.Parent [ParentL3], t4.Parent [ParentL4]
FROM MyTable t1
LEFT JOIN MyTable t2
ON t1.Parent = t2.Item
LEFT JOIN MyTable t3
ON t2.Parent = t3.Item
LEFT JOIN MyTable t4
ON t3.Parent = t4.Item
Used the following to create the test table, MyTable to confirm the resultset
CREATE TABLE MyTable
(
id Int IDENTITY,
Item Int,
Parent Int
)
INSERT MyTable
VALUES (2, 5),
(4, 5),
(5, 12),
(6, 2),
(10, 6)
Ok, even though the LEFT JOINs are the simplest way in this case (when only 4 levels of recursion are needed), this is another option using recursive CTEs (SQL Server 2005+):
;WITH CTE AS
(
SELECT *, 1 RecursionLevel
FROM YourTable
UNION ALL
SELECT B.id, A.Item, B.Parent, RecursionLevel + 1
FROM CTE A
INNER JOIN YourTable B
ON A.Parent = B.Item
)
SELECT Item,
MIN(CASE WHEN RecursionLevel = 1 THEN Parent END) ParentL1,
MIN(CASE WHEN RecursionLevel = 2 THEN Parent END) ParentL2,
MIN(CASE WHEN RecursionLevel = 3 THEN Parent END) ParentL3,
MIN(CASE WHEN RecursionLevel = 4 THEN Parent END) ParentL4
FROM CTE
WHERE RecursionLevel <= 4
GROUP BY Item
This is the result:
╔══════╦══════════╦══════════╦══════════╦══════════╗
║ Item ║ ParentL1 ║ ParentL2 ║ ParentL3 ║ ParentL4 ║
╠══════╬══════════╬══════════╬══════════╬══════════╣
║ 2 ║ 5 ║ 12 ║ NULL ║ NULL ║
║ 4 ║ 5 ║ 12 ║ NULL ║ NULL ║
║ 5 ║ 12 ║ NULL ║ NULL ║ NULL ║
║ 6 ║ 2 ║ 5 ║ 12 ║ NULL ║
║ 10 ║ 6 ║ 2 ║ 5 ║ 12 ║
╚══════╩══════════╩══════════╩══════════╩══════════╝
And here is a sqlfiddle with a demo of this.

Concatenate row record base on group by in SQL Server 2008

I have one table (tblproduct) with fields: dept, product, qty.
sample data below:
dept product qty
IT A 2
IT B 1
PU C 4
SAL D 1
SER D 2
SER A 4
I want to create stored pro in sql server with the result below:
product qty remark
A 6 IT=2,SER=4
B 1 IT=1
C 4 PU=4
D 3 SAL=1,SER=2
this is my stored pro
select product,
sum(qty)
from tblproduct
group by product
order by product
Pls. any help. thanks.
SELECT
[product], SUM(qty) Total_Qty,
STUFF(
(SELECT ',' + dept + '=' + CAST(qty AS VARCHAR(10))
FROM TableName
WHERE [product] = a.[product]
FOR XML PATH (''))
, 1, 1, '') AS Remark
FROM TableName AS a
GROUP BY [product]
SQLFiddle Demo
OUTPUT
╔═════════╦═══════════╦═════════════╗
║ PRODUCT ║ TOTAL_QTY ║ REMARK ║
╠═════════╬═══════════╬═════════════╣
║ A ║ 6 ║ IT=2,SER=4 ║
║ B ║ 1 ║ IT=1 ║
║ C ║ 4 ║ PU=4 ║
║ D ║ 3 ║ SAL=1,SER=2 ║
╚═════════╩═══════════╩═════════════╝
Please try:
SELECT product, SUM(qty) Qty,
STUFF(
(SELECT ','+b.dept+'='+CAST(qty as nvarchar(10))
FROM YourTable b where b.product=a.product
FOR XML PATH(''),type).value('.','nvarchar(max)'), 1, 1, '')
from YourTable a
group by product