TSQL recursive CTE threaded sorting - tsql

I have the following table:
ID parentID name
1 0 car1
2 1 tire
3 2 rubber
4 0 car2
5 2 nut
6 3 black
To help with testing...
CREATE TABLE #TT (ID int
,ParentID int
,Name varchar(25)
)
INSERT #TT
SELECT 1,0,'car1' UNION ALL
SELECT 2,1,'tire' UNION ALL
SELECT 3,2,'rubber' UNION ALL
SELECT 4,0,'car2' UNION ALL
SELECT 5,2,'nut' UNION ALL
SELECT 6,3,'black'
I'm trying to create a "threaded" hierarchy, but I want to list the child nodes under their parents like so:
ID parentID name
1 0 car1
2 1 tire
3 2 rubber
6 3 black
5 2 nut
4 0 car2
If I use a recursive CTE like this one...
;WITH Features
AS
(
SELECT *
FROM #TT
WHERE ParentID = 0
UNION ALL
SELECT F.*
FROM #TT AS F
INNER JOIN Features
ON F.ParentID = Features.ID
)
SELECT *
FROM Features
I end up with this...
ID parentID name
1 0 car1
4 0 car2
2 1 tire
3 2 rubber
5 2 nut
6 3 black
any ideas? Thank you in advance.

You can build a tree path as you go along, and order it by that
Something like
DECLARE #TT TABLE(ID int, ParentID int, Name varchar(25))
INSERT #TT
SELECT 1,0,'car1' UNION ALL
SELECT 2,1,'tire' UNION ALL
SELECT 3,2,'rubber' UNION ALL
SELECT 4,0,'car2' UNION ALL
SELECT 5,2,'nut' UNION ALL
SELECT 6,3,'black'
SELECT *
FROM #TT
;WITH Features AS (
SELECT *,
CAST(ID AS VARCHAR(MAX)) + '/' AS TreePath
FROM #TT
WHERE ParentID = 0
UNION ALL
SELECT tt.*,
f.TreePath + CAST(tt.ID AS VARCHAR(10)) + '/'
FROM #TT tt INNER JOIN
Features f ON tt.ParentID = f.ID
)
SELECT *
FROM Features
ORDER BY TreePath

Try adding an ORDER BY clause such as:
SELECT * FROM Features
ORDER BY parentID

Related

SQL adding numbers column

Let's say I have a table:
Table1
ID | Table2_ID | Title
1 1 Breaking_Bad
2 1 Breaking_Bad
3 2 Simpsons
4 1 House_Of_Cards
I want to rename the title by adding '_XX' (where XX is a number) to only to those entries that are the same title and have the same Table2_ID.
So end results would be
Table1
ID | Table2_ID | Title
1 1 Breaking_Bad_01
2 1 Breaking_Bad_02
3 2 Simpsons
4 1 House_Of_Cards
How could I do this with TSQL?
You can do this with
WITH T
AS (SELECT *,
COUNT(*) OVER (PARTITION BY Table2_ID, Title) AS Cnt,
ROW_NUMBER() OVER (PARTITION BY Table2_ID, Title ORDER BY ID) AS RN
FROM Table1)
UPDATE T
SET Title = Title + '_' + FORMAT(RN, 'D2')
WHERE Cnt > 1;
SQL Fiddle
Or if you are on a version without FORMAT
SET Title = Title + CASE WHEN RN < 10 THEN '_0' ELSE '_' END + CAST(RN AS VARCHAR(10))
Will this work in SQL Server? The padding to 2 digits would be straightforward.
UPDATE TABLE1 A SET TITLE = TITLE + '_' + (SELECT COUNT(*) FROM TABLE1 B WHERE A.TITLE=B.TITLE AND A.ID<=B.ID) WHERE A.ID IN (SELECT B.ID FROM TABLE1 B
WHERE A.Id<>B.ID and A.TITLE=B.TITLE)

TSQL: Inserting missing records into table

I am stuck at this T-SQL query.
I have table below
Age SectioName Cost
---------------------
1 Section1 100
2 Section1 200
1 Section2 500
3 Section2 100
4 Section2 200
Lets say for each section I can have maximum 5 Age. In above table there are some missing Ages. How do I insert missing Ages for each section. (Possibly without using cursor). The cost would be zero for missing Ages
So after the insertion the table should look like
Age SectioName Cost
---------------------
1 Section1 100
2 Section1 200
3 Section1 0
4 Section1 0
5 Section1 0
1 Section2 500
2 Section2 0
3 Section2 100
4 Section2 200
5 Section2 0
EDIT1
I should have been more clear with my question. The maximum age is dynamic value. It could be 5,6,10 or someother value but it will be always less than 25.
I think I got it
;WITH tally AS
(
SELECT 1 AS r
UNION ALL
SELECT r + 1 AS r
FROM tally
WHERE r < 5 -- this value could be dynamic now
)
select n.r, t.SectionName, 0 as Cost
from (select distinct SectionName from TempFormsSectionValues) t
cross join
(select ta.r FROM tally ta) n
where not exists
(select * from TempFormsSectionValues where YearsAgo = n.r and SectionName = t.SectionName)
order by t.SectionName, n.r
You can use this query to select missing value:
select n.num, t.SectioName, 0 as Cost
from (select distinct SectioName from table1) t
cross join
(select 1 as num union select 2 union select 3 union select 4 union select 5) n
where not exists
(select * from table1 where table1.age = n.num and table1.SectioName = t.SectioName)
It creates a Cartesian product of sections and numbers 1 to 5 and then selects those that doesn't exist yet. You can then use this query for the source of insert into your table.
SQL Fiddle (it has order by added to check the results easier but it's not necessary for inserting).
Use below query to generate missing rows
SELECT t1.Age,t1.Section,ISNULL(t2.Cost,0) as Cost
FROM
(
SELECT 1 as Age,'Section1' as Section,0 as Cost
UNION
SELECT 2,'Section1',0
UNION
SELECT 3,'Section1',0
UNION
SELECT 4,'Section1',0
UNION
SELECT 5,'Section1',0
UNION
SELECT 1,'Section2',0
UNION
SELECT 2,'Section2',0
UNION
SELECT 3,'Section2',0
UNION
SELECT 4,'Section2',0
UNION
SELECT 5,'Section2',0
) as t1
LEFT JOIN test t2
ON t1.Age=t2.Age AND t1.Section=t2.Section
ORDER BY Section,Age
SQL Fiddle
You can utilize above result set for inserting missing rows by using EXCEPT operator to exclude already existing rows in table -
INSERT INTO test
SELECT t1.Age,t1.Section,ISNULL(t2.Cost,0) as Cost
FROM
(
SELECT 1 as Age,'Section1' as Section,0 as Cost
UNION
SELECT 2,'Section1',0
UNION
SELECT 3,'Section1',0
UNION
SELECT 4,'Section1',0
UNION
SELECT 5,'Section1',0
UNION
SELECT 1,'Section2',0
UNION
SELECT 2,'Section2',0
UNION
SELECT 3,'Section2',0
UNION
SELECT 4,'Section2',0
UNION
SELECT 5,'Section2',0
) as t1
LEFT JOIN test t2
ON t1.Age=t2.Age AND t1.Section=t2.Section
EXCEPT
SELECT Age,Section,Cost
FROM test
SELECT * FROM test
ORDER BY Section,Age
http://www.sqlfiddle.com/#!3/d9035/11

how to do dead reckoning on column of table, postgresql

I have a table looks like,
x y
1 2
2 null
3 null
1 null
11 null
I want to fill the null value by conducting a rolling
function to apply y_{i+1}=y_{i}+x_{i+1} with sql as simple as possible (inplace)
so the expected result
x y
1 2
2 4
3 7
1 8
11 19
implement in postgresql. I may encapsulate it in a window function, but the implementation of custom function seems always complex
WITH RECURSIVE t AS (
select x, y, 1 as rank from my_table where y is not null
UNION ALL
SELECT A.x, A.x+ t.y y , t.rank + 1 rank FROM t
inner join
(select row_number() over () rank, x, y from my_table ) A
on t.rank+1 = A.rank
)
SELECT x,y FROM t;
You can iterate over rows using a recursive CTE. But in order to do so, you need a way to jump from row to row. Here's an example using an ID column:
; with recursive cte as
(
select id
, y
from Table1
where id = 1
union all
select cur.id
, prev.y + cur.x
from Table1 cur
join cte prev
on cur.id = prev.id + 1
)
select *
from cte
;
You can see the query at SQL Fiddle. If you don't have an ID column, but you do have another way to order the rows, you can use row_number() to get an ID:
; with recursive sorted as
(
-- Specify your ordering here. This example sorts by the dt column.
select row_number() over (order by dt) as id
, *
from Table1
)
, cte as
(
select id
, y
from sorted
where id = 1
union all
select cur.id
, prev.y + cur.x
from sorted cur
join cte prev
on cur.id = prev.id + 1
)
select *
from cte
;
Here's the SQL Fiddle link.

Hierarchical query rollup Rollup

I have the following table:
parent_id child_id child_class
1 2 1
1 3 1
1 4 2
2 5 2
2 6 2
Parent_id represents a folder id. Child id represents either a child folder (where child_class=1) or child file (where child_class=2).
I'd like to get a rollup counter (bottom up) of all files only (child_class=2) the following way. for example if C is a leaf folder (no child folders) with 5 files, and B is a parent folder of C that has 4 files in it, the counter on C should say 5 and the counter on B should say 9 (=5 from C plus 4 files in B) and so forth recursively going bottom up taking into consideration sibling folders etc.
In the example above I expect the results below (notice 3 is a child folder with no files in it):
parent_id FilesCounter
3 0
2 2
1 3
I prefer an SQL query for performance but function is also possible.
I tried mixing hirarchical query with rollup (sql 2008 r2) with no success so far.
Please advise.
This CTE should do the trick... Here is the SQLFiddle.
SELECT parent_id, child_id, child_class,
(SELECT COUNT(*) FROM tbl a WHERE a.parent_id = e.parent_id AND child_class <> 1) AS child_count
INTO tbl2
FROM tbl e
;WITH CTE (parent_id, child_id, child_class, child_count)
AS
(
-- Start with leaf nodes
SELECT parent_id, child_id, child_class, child_count
FROM tbl2
WHERE child_id NOT IN (SELECT parent_id from tbl)
UNION ALL
-- Recursively go up the chain
SELECT e.parent_id, e.child_id, e.child_class, e.child_count + d.child_count
FROM tbl2 e
INNER JOIN CTE AS d
ON e.child_id = d.parent_id
)
-- Statement that executes the CTE
SELECT FOLDERS.parent_id, max(ISNULL(child_count,0)) FilesCounter
FROM (SELECT parent_id FROM tbl2 WHERE parent_id NOT IN (select child_id from tbl2)
UNION
SELECT child_id FROM tbl2 WHERE child_class = 1) FOLDERS
LEFT JOIN CTE ON FOLDERS.parent_id = CTE.parent_id
GROUP BY FOLDERS.parent_id
Zak's answer was close, but the root folder did not rollup well. The following does the work:
with par_child as (
select 1 as parent_id, 2 as child_id, 1 as child_class
union all select 1, 3, 1
union all select 1, 4, 2
union all select 2, 5, 1
union all select 2, 6, 2
union all select 2, 10, 2
union all select 3, 11, 2
union all select 3, 7 , 2
union all select 5, 8 , 2
union all select 5, 9 , 2
union all select 5, 12, 1
union all select 5, 13, 1
)
, child_cnt as
(
select parent_id as root_parent_id, parent_id, child_id, child_class, 1 as lvl from par_child union all
select cc.root_parent_id, pc.parent_id, pc.child_id, pc.child_class, cc.lvl + 1 as lvl from
par_child pc join child_cnt cc on (pc.parent_id=cc.child_id)
),
distinct_folders as (
select distinct child_id as folder_id from par_child where child_class=1
)
select root_parent_id, count(child_id) as cnt from child_cnt where child_class=2 group by root_parent_id
union all
select folder_id, 0 from distinct_folders df where not exists (select 1 from par_child pc where df.folder_id=pc.parent_id)

Make a column values header for rest of columns using TSQL

I have following table
ID | Group | Type | Product
1 Dairy Milk Fresh Milk
2 Dairy Butter Butter Cream
3 Beverage Coke Coca cola
4 Beverage Diet Dew
5 Beverage Juice Fresh Juice
I need following output/query result:
ID | Group | Type | Product
1 Dairy
1 Milk Fresh Milk
2 Butter Butter Cream
2 Beverage
1 Coke Coca cola
2 Diet Dew
3 Juice Fresh Juice
For above sample a hard coded script can do the job but I look for a dynamic script for any number of groups. I do not have any idea how it can be done so, I do not have a sample query yet. I need ideas, examples that at least give me an idea. PIVOT looks a close option but does not looks to be fully fit for this case.
Here's a possible way. It basically unions the "Group-Headers" and the "Group-Items". The difficulty was to order them correctly.
WITH CTE AS
(
SELECT ID,[Group],Type,Product,
ROW_NUMBER() OVER (PARTITION BY [Group] Order By ID)AS RN
FROM Drink
)
SELECT ID,[Group],Type,Product
FROM(
SELECT RN AS ID,[Group],[Id]AS OriginalId,'' As Type,'' As Product, 0 AS RN, 'Group' As RowType
FROM CTE WHERE RN = 1
UNION ALL
SELECT RN AS ID,'' AS [Group],[Id]AS OriginalId,Type,Product, RN, 'Item' As RowType
FROM CTE
)X
ORDER BY OriginalId ASC
, CASE WHEN RowType='Group' THEN 0 ELSE 1 END ASC
, RN ASC
Here's a demo-fiddle: http://sqlfiddle.com/#!6/ed6ca/2/0
A slightly simplified approach:
With Groups As
(
Select Distinct Min(Id) As Id, [Group], '' As [Type], '' As Product
From dbo.Source
Group By [Group]
)
Select Coalesce(Cast(Z.Id As varchar(10)),'') As Id
, Coalesce(Z.[Group],'') As [Group]
, Z.[Type], Z.Product
From (
Select Id As Sort, Id, [Group], [Type], Product
From Groups
Union All
Select G.Id, Null, Null, S.[Type], S.Product
From dbo.Source As S
Join Groups As G
On G.[Group] = S.[Group]
) As Z
Order By Sort
It should be noted that the use of Coalesce is purely for aesthetic reasons. You could simply return null in these cases.
SQL Fiddle
And an approach with ROW_NUMBER:
IF OBJECT_ID('dbo.grouprows') IS NOT NULL DROP TABLE dbo.grouprows;
CREATE TABLE dbo.grouprows(
ID INT,
Grp NVARCHAR(MAX),
Type NVARCHAR(MAX),
Product NVARCHAR(MAX)
);
INSERT INTO dbo.grouprows VALUES
(1,'Dairy','Milk','Fresh Milk'),
(2,'Dairy','Butter','Butter Cream'),
(3,'Beverage','Coke','Coca cola'),
(4,'Beverage','Diet','Dew'),
(5,'Beverage','Juice','Fresh Juice');
SELECT
CASE WHEN gg = 0 THEN dr1 END GrpId,
CASE WHEN gg = 1 THEN rn1 END TypeId,
ISNULL(Grp,'')Grp,
CASE WHEN gg = 1 THEN Type ELSE '' END Type,
CASE WHEN gg = 1 THEN Product ELSE '' END Product
FROM(
SELECT *,
DENSE_RANK()OVER(ORDER BY Grp DESC) dr1
FROM(
SELECT *,
ROW_NUMBER()OVER(PARTITION BY Grp ORDER BY type,gg) rn1,
ROW_NUMBER()OVER(ORDER BY type,gg) rn0
FROM(
SELECT Grp,Type,Product, GROUPING(Grp) gg, GROUPING(type) tg FROM dbo.grouprows
GROUP BY Product, Type, Grp
WITH ROLLUP
)X1
WHERE tg = 0
)X2
WHERE gg=1 OR rn1 = 1
)X3
ORDER BY rn0