TSQL recursive CTE order - tsql

I am having trouble figuring out how I can use recursive CTEs to order my results recursively. Here is what I mean (this is a simplified dataset):
I have this as input:
declare #sections table (id int, parent int);
insert into #sections values (1, 1);
insert into #sections values (2, 2);
insert into #sections values (3, 2);
insert into #sections values (4, 2);
insert into #sections values (5, 4);
insert into #sections values (6, 1);
insert into #sections values (7, 6);
insert into #sections values (8, 6);
insert into #sections values (9, 6);
insert into #sections values (10, 9);
-- hierarchical view
--1
-- 6
-- 7
-- 8
-- 10
-- 9
--2
-- 3
-- 4
-- 5
And I want this as output
EDIT: The order of the rows is the important part here
-- id parent depth
-- 1 1 0
-- 6 1 1
-- 7 6 2
-- 8 6 2
-- 10 8 3
-- 9 6 2
-- 2 2 0
This is the best I can do:
with section_cte as
(
select id, parent, 0 'depth' from #sections where id = parent
union all
select cte.id, cte.parent, depth + 1
from #sections s join section_cte cte on s.parent = cte.id where s.id <> s.parent
)
select *from section_cte
Can anyone please help me tweak this query to get what I need?
Thanks!

You missed the part where you need to identify the depth is from the cte
WITH CTE AS (
SELECT
id
, parent
, 0 AS depth
FROM
#sections
WHERE
id=parent
UNION ALL
SELECT
s.id
, s.parent
, c.depth + 1
FROM
#sections s
JOIN CTE c ON s.parent=c.id AND s.id <> s.parent
)
SELECT *
FROM CTE;

During the recursive build, it is a small matter to add a sequence. In the example below, the order is driven by the alphabetical order of the title, but you can use any other available key/sequence.
Declare #Table table (ID int,Pt int,Title varchar(50))
Insert into #Table values (0,null,'Tags'),(1,0,'Transportation'),(2,1,'Boats'),(3,1,'Cars'),(4,1,'Planes'),(5,1,'Trains'),(6,0,'Technology'),(7,6,'FTP'),(8,6,'HTTP'),(9,0,'Finance'),(10,9,'FTP'),(11,9,'401K'),(12,2,'Sail'),(13,2,'Powered'),(14,6,'Internet'),(15,6,'Database'),(16,15,'SQL Server'),(17,15,'MySQL'),(18,15,'MS Access')
Declare #Top int = null --<< Sets top of Hier Try 9
Declare #Nest varchar(25) =' ' --<< Optional: Added for readability
;with cteHB (Seq,ID,Pt,Lvl,Title) as (
Select Seq = cast(1000+Row_Number() over (Order by Title) as varchar(500))
,ID
,Pt
,Lvl=1
,Title
From #Table
Where IsNull(#Top,-1) = case when #Top is null then isnull(Pt,-1) else ID end
Union All
Select Seq = cast(concat(cteHB.Seq,'.',1000+Row_Number() over (Order by cteCD.Title)) as varchar(500))
,cteCD.ID
,cteCD.Pt,cteHB.Lvl+1
,cteCD.Title
From #Table cteCD
Join cteHB on cteCD.Pt = cteHB.ID)
,cteR1 as (Select Seq,ID,R1=Row_Number() over (Order By Seq) From cteHB)
,cteR2 as (Select A.Seq,A.ID,R2=Max(B.R1) From cteR1 A Join cteR1 B on (B.Seq like A.Seq+'%') Group By A.Seq,A.ID )
Select Hier='HierName'
,B.R1
,C.R2
,A.ID
,A.Pt
,A.Lvl
,Title = Replicate(#Nest,A.Lvl) + A.Title
--,A.Seq --<< Normally Excluded, but you can see how the sequence is built
From cteHB A
Join cteR1 B on A.ID=B.ID
Join cteR2 C on A.ID=C.ID
Order By A.Seq --<< Use R1 if Range Keys are used
Returns
Hier R1 R2 ID Pt Lvl Title
HierName 1 19 0 NULL 1 Tags
HierName 2 4 9 0 2 Finance
HierName 3 3 11 9 3 401K
HierName 4 4 10 9 3 FTP
HierName 5 12 6 0 2 Technology
HierName 6 9 15 6 3 Database
HierName 7 7 18 15 4 MS Access
HierName 8 8 17 15 4 MySQL
HierName 9 9 16 15 4 SQL Server
HierName 10 10 7 6 3 FTP
HierName 11 11 8 6 3 HTTP
HierName 12 12 14 6 3 Internet
HierName 13 19 1 0 2 Transportation
HierName 14 16 2 1 3 Boats
HierName 15 15 13 2 4 Powered
HierName 16 16 12 2 4 Sail
HierName 17 17 3 1 3 Cars
HierName 18 18 4 1 3 Planes
HierName 19 19 5 1 3 Trains
Now, you may have noticed R1 and R2. These are my range keys, and are often used to aggregate data without recursion. If you don't need or want these, just remove the cteR1 and cteR2 (and the corresponding references in the final SELECT).
EDIT
You also have the option of selecting a portion of the hierarchy (i.e. Technology and its children).

The trick you need is to create a sortstring which match your business rule. Something like this.
WITH CTE AS (
SELECT
id, parent, 0 AS depth
--For MS SQL Server 2012+
,cast(format(id,'0000') as varchar(max)) sort
--For previous versions
,cast(stuff('0000',5-len(cast(id as varchar)),len(cast(id as varchar)),id) as varchar(max)) sort1
FROM #sections
WHERE id=parent
UNION ALL
SELECT s.id, s.parent, c.depth + 1
,sort + cast(format(s.id,'0000') as varchar(max)) sort
,sort + cast(stuff('0000',5-len(cast(s.id as varchar)),len(cast(s.id as varchar)),s.id) as varchar(max)) sort1
FROM #sections s
inner join CTE c ON s.parent=c.id AND s.id <> s.parent
)
SELECT *
FROM CTE
order by sort --or by sort1 depending on version

Related

Columns to Rows in T-SQL

I have table tbl_Survey:
SurveyID 1 2 3 4
7 4 4 4 4
8 3 3 3 3
9 2 2 2 2
My goal is to transfer table headers - 1 2 3 4 into rows, as the following:
enter
SurveyID Ouestion Rating
7 1 4
7 2 4
7 3 4
7 4 4
8 1 3
8 2 3
8 3 3
8 4 3
9 1 2
9 2 2
9 3 2
9 4 2
My code is (trying to follow help recommendations):
SELECT [SurveyID]
,[Question]
,[Rating]
FROM
[tbl_Survey]
cross apply
(
values
('1', 1 ),
('2', 2 ),
('3', 3 ),
('4', 4 )
) c (Question, Rating);
Results are not fully correct (Rating column is a problem):
SurveyID Ouestion Rating
7 1 1
7 2 2
7 3 3
7 4 4
8 1 1
8 2 2
8 3 3
8 4 4
9 1 1
9 2 2
9 3 3
9 4 4
Please, help...
My problem (because of which I couldn't proceed) was that I haven't used brackets for my code.
Here is the updated code for this:
SELECT [SurveyID], [Question], [Rating]
FROM [dbo].[tbl_Survey]
UNPIVOT
(
[Rating]
FOR [Question] in ([1], [2], [3], [4])
) AS SurveyUnpivot
How about this:
DECLARE #T TABLE (SurveyID int, q1 int, q2 int, q3 int, q4 int)
INSERT #T (SurveyID, q1, q2, q3, q4)
VALUES (7,4,4,4,4), (8,3,3,3,3), (9, 2, 2, 2, 2)
SELECT SurveyID, REPLACE(Question,'q','') as Question, Rating
FROM #T UNPIVOT (Rating FOR Question in (q1, q2, q3, q4)) as UPV
Same approach. Just make sure you use a global temporary table as a temp table will not be visible in the scope of the EXEC statement. This should work with any column name and any number of columns.
IF OBJECT_ID('tempdb..##T') IS NOT NULL DROP TABLE ##T
CREATE TABLE ##T (SurveyID int, xxxxx int, yyyyy int, zzzzzz int, tttttt int)
INSERT ##T VALUES (7,4,4,4,4), (8,3,3,3,3), (9, 2, 2, 2, 2)
DECLARE #Colnames nvarchar(4000)
SELECT #Colnames = STUFF((SELECT ',[' + [name] +']' FROM tempdb.sys.columns where object_id = object_id('tempdb..##T') AND name <> 'SurveyID' FOR XML PATH('') ),1,1,'')
DECLARE #SQL nvarchar(4000) SET #SQL = 'SELECT SurveyID, Question, Rating FROM ##T UNPIVOT (Rating FOR Question in ('+#colnames+')) as UPV'
EXEC(#SQL)

Group and Stuff multiple rows based on Count condition

I have a script that runs every 10 minutes and returns table with events from past 24 hours (marked by the script run time)
ID Name TimeOfEvent EventCategory TeamColor
1 Verlene Bucy 2015-01-30 09:10:00.000 1 Blue
2 Geneva Rendon 2015-01-30 09:20:00.000 2 Blue
3 Juliane Hartwig 2015-01-30 09:25:00.000 3 Blue
4 Vina Dutton 2015-01-30 12:55:00.000 2 Red
5 Cristin Lewis 2015-01-30 15:50:00.000 2 Red
6 Reiko Cushman 2015-01-30 17:10:00.000 1 Red
7 Mallie Temme 2015-01-30 18:35:00.000 3 Blue
8 Keshia Seip 2015-01-30 19:55:00.000 2 Blue
9 Rosalia Maher 2015-01-30 20:35:00.000 3 Red
10 Keven Gabel 2015-01-30 21:25:00.000 3 Red
Now I'd like to select two groups of Names based on these conditions:
1) Select Names from same EventCategory having 4 or more records in past 24 hours.
2) Select Names from same EventCategory and same TeamColor having 2 or more records in past 1 hour.
So my result would be:
4+per24h: Geneva Rendon, Vina Dutton, Cristin Lewis, Keshia Seip EventCategory = 2
4+per24h: Juliane Hartwig, Mallie Temme, Rosalia Maher, Keven Gabel EventCategory = 3
2+per1h: Rosalia Maher, Keven Gabel EventCategory = 3, TeamColor = Red
For the first one, I have written this:
SELECT mt.EventCategory, MAX(mt.[name]), MAX(mt.TimeOfEvent), MAX(mt.TeamColor)
FROM #mytable mt
GROUP BY mt.EventCategory
HAVING COUNT(mt.EventCategory) >= 4
because I don't care for the actual time as long as it's in the past 24 hours (and it always is), but I have trouble stuffing the names in one row.
The second part, I have no idea how to do. Because the results need to have both same EventCategory and TeamColor and also be limited by the one hour bracket.
this is possible, but you mix two separate issues. Here you find them combined with UNION:
Just paste this into an empty query window and execute. Adapt to your needs:
DECLARE #tbl TABLE(ID INT,Name VARCHAR(100),TimeOfEvent DATETIME,EventCategory INT,TeamColor VARCHAR(10));
INSERT INTO #tbl VALUES
(1,'Verlene Bucy','2015-01-30T09:10:00.000',1,'Blue')
,(2,'Geneva Rendon','2015-01-30T09:20:00.000',2,'Blue')
,(3,'Juliane Hartwig','2015-01-30T09:25:00.000',3,'Blue')
,(4,'Vina Dutton','2015-01-30T12:55:00.000',2,'Red')
,(5,'Cristin Lewis','2015-01-30T15:50:00.000',2,'Red')
,(6,'Reiko Cushman','2015-01-30T17:10:00.000',1,'Red')
,(7,'Mallie Temme','2015-01-30T18:35:00.000',3,'Blue')
,(8,'Keshia Seip','2015-01-30T19:55:00.000',2,'Blue')
,(9,'Rosalia Maher','2015-01-30T20:35:00.000',3,'Red')
,(10,'Keven Gabel','2015-01-30T21:25:00.000',3,'Red');
WITH Extended AS
(
SELECT *
,DATEDIFF(MINUTE,'2015-01-30T21:26:00.000',TimeOfEvent) AS MinuteDiff --use GETDATE() here...
,COUNT(*) OVER(PARTITION BY EventCategory) AS CountCategory
FROM #tbl AS tbl
)
,Filtered24Hours AS
(
SELECT *
FROM Extended
WHERE CountCategory >=4
)
,Filtered60Mins AS
(
SELECT *
FROM Extended
WHERE MinuteDiff >=-60
AND CountCategory >=2
)
SELECT DISTINCT (SELECT COUNT(*) FROM Filtered24Hours AS x WHERE x.EventCategory=outerSource.EventCategory) AS CountNames
,'per24h' AS TimeIntervall
,STUFF((
SELECT ' ,' + innerSource.Name
FROM Filtered24Hours AS innerSource
WHERE innerSource.EventCategory=outerSource.EventCategory
ORDER BY innerSource.TimeOfEvent
FOR XML PATH('')
),1,2,'') AS Names
,EventCategory
,NULL
FROM Filtered24Hours AS outerSource
UNION
SELECT DISTINCT (SELECT COUNT(*) FROM Filtered60Mins AS x WHERE x.EventCategory=outerSource.EventCategory)
,'per1h'
,STUFF((
SELECT ' ,' + innerSource.Name
FROM Filtered60Mins AS innerSource
WHERE innerSource.EventCategory=outerSource.EventCategory
ORDER BY innerSource.TimeOfEvent
FOR XML PATH('')
),1,2,'')
,EventCategory
,TeamColor
FROM Filtered60Mins AS outerSource
The result
Count Interv Names Category Team
4 per24h Geneva Rendon ,Vina Dutton ,Cristin Lewis ,Keshia Seip 2 NULL
4 per24h Juliane Hartwig ,Mallie Temme ,Rosalia Maher ,Keven Gabel 3 NULL
2 per1h Rosalia Maher ,Keven Gabel 3 Red

Postgresql: only keep unique values from integer array

Let's say I have an array of integers
1 6 6 3 3 8 4 4
It will be always of the form n*(pairs of number) + 2 (unique numbers).
Is there an efficient way of keeping only the 2 uniques values (i.e. the 2 with single occurence)?
Here, I would like to get 1 and 8.
So far is what I have:
SELECT node_id
FROM
( SELECT node_id, COUNT(*)
FROM unnest(array[1, 6, 6 , 3, 3 , 8 , 4 ,4]) AS node_id
GROUP BY node_id
) foo
ORDER BY count LIMIT 2;
You are very close, I think:
SELECT node_id
FROM (SELECT node_id, COUNT(*)
FROM unnest(array[1, 6, 6 , 3, 3 , 8 , 4 ,4]) AS node_id
GROUP BY node_id
HAVING count(*) = 1
) foo ;
You can group these back into an array, if you like, using array_agg().

Build a query that pulls records based on a value in a column

My table has a parent/child relationship, along the lines of parent.id,id. There is also a column that contains a quantity, and another ID representing a grand-parent, like so:
id parent.id qty Org
1 1 1 100
2 1 0 100
3 1 4 100
4 4 1 101
5 4 2 101
6 6 1 102
7 6 0 102
8 6 1 102
What this is supposed to show is ID 1 is the parent, and ID 2 and 3 are children which belongs to ID 1, and ID 1, 2, and 3 all belong to the grandparent 100.
I would like to know if any child or parent has QTY = 0, what are all the other id's associated to that parent, and what are all the other parents associated with that grandparent?
For example, I would want to see a report that shows me this:
Org id parent.id qty
100 1 1 1
100 2 1 0
100 3 1 4
102 6 6 1
102 7 6 0
102 8 6 1
Much appreciate any help you can offer to build a MS SQL 2000 (yeah, I know) query to handle this.
Try this
select * from tablename a
where exists (select 1 from tablename x
where x.parent_id = a.parent_id and qty = 0)
Example:
;with cte as
( select 1 id,1 parent_id, 1 qty, 100 org
union all select 2,1,0,100
union all select 3,1,4,100
union all select 4,4,1,101
union all select 5,4,2,101
union all select 6,6,1,102
union all select 7,6,0,102
union all select 8,6,1,102
)
select * from cte a
where exists (select 1 from cte x
where x.parent_id = a.parent_id and qty = 0)
SQL DEMO HERE

How to read all records recursively and show by level depth TSQL

Is there a way to read records recursively in similar table and order by depth level?
#table:
id int | parent int | value string
--------------------------------------------
1 -1 some
2 1 some2
3 2 some3
4 2 some4
5 3 some5
6 4 some6
7 3 some5
8 3 some5
9 8 some5
10 8 some5
So is there a way to recursively select where result table would look like this.
select * from #table where id=3
id int | parent int | value string | depth
--------------------------------------------------------
3 2 some3 0
5 3 some5 1
7 3 some5 1
8 3 some5 1
9 8 some5 2
10 8 some5 2
So if I choose id=3 I would see recursion for id=3 and children
Thank you
;with C as
(
select id,
parent,
value,
0 as depth
from YourTable
where id = 3
union all
select T.id,
T.parent,
T.value,
C.depth + 1
from YourTable as T
inner join C
on T.parent = C.id
)
select *
from C
SE-Data
You can accomplish using CTEs, in particular rCTEs.
See this, and this for more information.
Example to follow:
WITH sampleCTE (id, parent, value, depth)
AS (
-- Anchor definition
SELECT id
, parent
, value
, 0
FROM #table
WHERE id = #targetId
-- Recursive definition
UNION ALL
SELECT child.id
, child.parent
, child.value
, sampleCTE.depth + 1
FROM #table child
INNER JOIN sampleCTE ON sampleCTE.id = child.parent
)