SQL - Query to identify the parentid in a hierarchy with some conditions - sql
I have a table in SQL azure database with a hierarchy of parents and i want to identify in the total hierarchy of the parents the parent id for each item that meets some conditions.
As an example this could be a great example
categoryId parentId Typeid
1 null 1
2 1 2
3 2 3
4 3 4
5 3 4
6 null 5
7 6 1
8 7 2
9 8 3
10 9 4
11 9 4
And I want to find for each categoryID the ParentId in the hierarchy that the type is 1 and if this category is the type 1 put the same category, in order to receive something like this.
categoryId parentId Typeid ParentSearch
1 null 1 1
2 1 2 1
3 2 3 1
4 3 4 1
5 3 4 1
6 null 5 null
7 6 1 7
8 7 2 7
9 8 3 7
10 9 4 7
11 9 4 7
As you can see all the categories from 1 to 5 the parent with type 1 is the category 1
and for the categories from 6 to 11, the 6 need to be null and the rest the parent is 7.
It could be possible?
Maybe creating a path or something. i have done doing several left joins at leves and it is fine but i dont want to create 500leves in order to be sure that we never have 500 levels childs.
Thanks and regards!
A recursive CTE is your friend:
WITH parents AS
(SELECT categoryid, parentid, typeid
, CASE WHEN typeid = 1 THEN categoryid ELSE NULL END AS parentsearch
FROM mytable
WHERE parentid IS NULL
UNION ALL
SELECT c.categoryid, c.parentid, c.typeid
, coalesce(p.parentsearch, CASE WHEN c.typeid = 1 THEN c.categoryid ELSE null END)
FROM mytable AS c
JOIN parents AS p ON c.parentid = p.categoryid)
SELECT * FROM parents ORDER BY categoryid;
categoryid parentid typeid parentsearch
---------- ---------- ---------- ------------
1 (null) 1 1
2 1 2 1
3 2 3 1
4 3 4 1
5 3 4 1
6 (null) 5 (null)
7 6 1 7
8 7 2 7
9 8 3 7
10 9 4 7
11 9 4 7
Sql Server db<>fiddle example.
Another solution that uses a recursive CTE.
But this one seeds from the childs.
WITH RCTE AS
(
SELECT t.categoryId, t.parentId, t.typeId,
0 as lvl,
t.categoryId as nextCategoryId,
t.parentId as nextParentId,
t.typeId as nextTypeId
FROM CategoryRelations t
UNION ALL
SELECT c.categoryId, c.parentId, c.typeId,
c.lvl + 1,
t.categoryId,
t.parentId,
t.typeId
FROM RCTE c
JOIN CategoryRelations t ON t.categoryId = c.nextParentId
WHERE c.typeId != 1
)
SELECT
c.categoryId,
c.parentId,
c.typeId,
(case when c.nextTypeId = 1 then c.nextCategoryId end) as ParentSearch
FROM RCTE c
WHERE (c.nextTypeId = 1 or c.parentId is null)
ORDER BY categoryId;
A test on rextester here
Related
SQL: subset data: select id when time_id for id satisfy a condition from another column
I have a data (dt) in SQL like the following: ID time_id act rd 11 1 1 1 11 2 4 1 11 3 7 0 12 1 8 1 12 2 2 0 12 3 4 1 12 4 3 1 12 5 4 1 13 1 4 1 13 2 1 0 15 1 3 1 16 1 8 0 16 2 8 0 16 3 8 0 16 4 8 0 16 5 8 0 and I want to take the subset of this data such that only ids (and their corresponding time_id, act, rd) that has time_id == 5 is retained. The desired output is the following ID time_id act rd 12 1 8 1 12 2 2 0 12 3 4 1 12 4 3 1 12 5 4 1 16 1 8 0 16 2 8 0 16 3 8 0 16 4 8 0 16 5 8 0 I know I should use having clause somehow but have not been successful so far (returns me empty outputs). below is my attempt: SELECT * FROM dt GROUP BY ID Having min(time_id) == 5;
This query: select id from tablename where time_id = 5 returns all the ids that you want in the results. Use it with the operator IN: select * from tablename where id in (select id from tablename where time_id = 5)
You can use a correlated subquery with exists: select t.* from t where exists (select 1 from t t2 where t2.id = t.id and t2.time_id = 5);
WITH temp AS ( SELECT id FROM tab WHERE time_id = 5 ) SELECT * FROM tab t join temp tp on(t.id=tp.id);
check this query select * from table t1 join (select distinct ID from table t where time_id = 5) t2 on t1.id =t2.id;
SQL Recursive CTE unexpectedly returns alternating sets
I am trying to get the use recursive CTE to repeat the same pattern over and over, resetting when "Scenario" increases in value. RowNumber repeats 1-21 (as desired), but whenever "Scenario" is an even number, there are too few items in the "Vals" column to feed into "Value". I can't figure out which part of the code is causing me to be 1 short for only even Scenarios. Below are the results of the code I'm using at the bottom. Scenario RowNumber Value Vals 1 1 A A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,B,C 1 2 A A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,B,C 1 3 A A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,B,C 1 4 A A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,B,C 1 5 A A,A,A,A,A,A,A,A,A,A,A,A,A,A,B,C 1 6 A A,A,A,A,A,A,A,A,A,A,A,A,A,B,C 1 7 A A,A,A,A,A,A,A,A,A,A,A,A,B,C 1 8 A A,A,A,A,A,A,A,A,A,A,A,B,C 1 9 A A,A,A,A,A,A,A,A,A,A,B,C 1 10 A A,A,A,A,A,A,A,A,A,B,C 1 11 A A,A,A,A,A,A,A,A,B,C 1 12 A A,A,A,A,A,A,A,B,C 1 13 A A,A,A,A,A,A,B,C 1 14 A A,A,A,A,A,B,C 1 15 A A,A,A,A,B,C 1 16 A A,A,A,B,C 1 17 A A,A,B,C 1 18 A A,B,C 1 19 A B,C 1 20 B C 1 21 C 2 1 A A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,B,B,C 2 2 A A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,B,B,C 2 3 A A,A,A,A,A,A,A,A,A,A,A,A,A,A,B,B,C 2 4 A A,A,A,A,A,A,A,A,A,A,A,A,A,B,B,C 2 5 A A,A,A,A,A,A,A,A,A,A,A,A,B,B,C 2 6 A A,A,A,A,A,A,A,A,A,A,A,B,B,C 2 7 A A,A,A,A,A,A,A,A,A,A,B,B,C 2 8 A A,A,A,A,A,A,A,A,A,B,B,C 2 9 A A,A,A,A,A,A,A,A,B,B,C 2 10 A A,A,A,A,A,A,A,B,B,C 2 11 A A,A,A,A,A,A,B,B,C 2 12 A A,A,A,A,A,B,B,C 2 13 A A,A,A,A,B,B,C 2 14 A A,A,A,B,B,C 2 15 A A,A,B,B,C 2 16 A A,B,B,C 2 17 A B,B,C 2 18 B B,C 2 19 B C 2 20 C 2 21 A A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,B,B,C 3 1 A A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,B,C,C 3 2 A A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,B,C,C 3 3 A A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,B,C,C 3 4 A A,A,A,A,A,A,A,A,A,A,A,A,A,A,B,C,C 3 5 A A,A,A,A,A,A,A,A,A,A,A,A,A,B,C,C 3 6 A A,A,A,A,A,A,A,A,A,A,A,A,B,C,C 3 7 A A,A,A,A,A,A,A,A,A,A,A,B,C,C 3 8 A A,A,A,A,A,A,A,A,A,A,B,C,C 3 9 A A,A,A,A,A,A,A,A,A,B,C,C 3 10 A A,A,A,A,A,A,A,A,B,C,C 3 11 A A,A,A,A,A,A,A,B,C,C 3 12 A A,A,A,A,A,A,B,C,C 3 13 A A,A,A,A,A,B,C,C 3 14 A A,A,A,A,B,C,C 3 15 A A,A,A,B,C,C 3 16 A A,A,B,C,C 3 17 A A,B,C,C 3 18 A B,C,C 3 19 B C,C 3 20 C C 3 21 C 4 1 A A,A,A,A,A,A,A,A,A,A,A,A,A,A,A,B,B,B,C 4 2 A A,A,A,A,A,A,A,A,A,A,A,A,A,A,B,B,B,C 4 3 A A,A,A,A,A,A,A,A,A,A,A,A,A,B,B,B,C 4 4 A A,A,A,A,A,A,A,A,A,A,A,A,B,B,B,C 4 5 A A,A,A,A,A,A,A,A,A,A,A,B,B,B,C 4 6 A A,A,A,A,A,A,A,A,A,A,B,B,B,C 4 7 A A,A,A,A,A,A,A,A,A,B,B,B,C 4 8 A A,A,A,A,A,A,A,A,B,B,B,C 4 9 A A,A,A,A,A,A,A,B,B,B,C 4 10 A A,A,A,A,A,A,B,B,B,C 4 11 A A,A,A,A,A,B,B,B,C 4 12 A A,A,A,A,B,B,B,C 4 13 A A,A,A,B,B,B,C 4 14 A A,A,B,B,B,C 4 15 A A,B,B,B,C 4 16 A B,B,B,C 4 17 B B,B,C 4 18 B B,C 4 19 B C 4 20 C This is the code I used to generate the above sample. Where am I going wrong? CREATE TABLE #temp3 ( Scenario INT ,Vals VARCHAR(64) ,LEN INT ) ; WITH vals AS ( SELECT v.* FROM (VALUES ('A'), ('B'), ('C')) v(x) ), CTE AS ( SELECT CAST('A' AS VARCHAR(MAX)) AS STR, 0 AS LEN UNION ALL SELECT (CTE.STR + ',' + vals.x), CTE.LEN + 1 FROM CTE JOIN vals ON vals.x >= RIGHT(CTE.STR, 1) WHERE CTE.LEN < 19 ) INSERT INTO #temp3 SELECT ROW_NUMBER() OVER(ORDER BY STR + ',C') AS Scenario ,STR + ',C' AS Vals ,LEN FROM CTE WHERE STR + 'C' LIKE '%B%' AND LEN = 19 ; -- Split strings created above into individual characters WITH cte(Scenario, Value, Vals) AS ( SELECT Scenario ,CAST(LEFT(Vals, CHARINDEX(',',Vals+',')-1) AS VARCHAR(10)) AS Value ,STUFF(Vals, 1, CHARINDEX(',',Vals+','), '') AS Vals FROM #temp3 UNION ALL SELECT Scenario ,CAST(LEFT(Vals, CHARINDEX(',',Vals+',')-1) AS VARCHAR(10)) ,STUFF(Vals, 1, CHARINDEX(',',Vals+','), '') FROM cte WHERE Vals > '' ) SELECT Scenario ,ROW_NUMBER() OVER (PARTITION BY Scenario ORDER BY Scenario) RowNumber ,Value ,Vals FROM cte t
I'm not exactly sure what the problem you are describing is, but the ROW_NUMBER() should use an ORDER BY clause that completely orders the rows in each partition. When you use "PARTITION BY Scenario ORDER BY Scenario" the order in which the ROW_NUMBER() values are assigned is undefined. Try something like WITH cte(Scenario, depth, Value, Vals) AS ( SELECT Scenario, 0 depth ,CAST(LEFT(Vals, CHARINDEX(',',Vals+',')-1) AS VARCHAR(10)) AS Value ,STUFF(Vals, 1, CHARINDEX(',',Vals+','), '') AS Vals FROM #temp3 UNION ALL SELECT Scenario, depth+1 ,CAST(LEFT(Vals, CHARINDEX(',',Vals+',')-1) AS VARCHAR(10)) ,STUFF(Vals, 1, CHARINDEX(',',Vals+','), '') FROM cte WHERE Vals > '' ) SELECT Scenario ,depth ,ROW_NUMBER() OVER (PARTITION BY Scenario ORDER BY depth ) RowNumber ,Value ,Vals FROM cte t
SQL recursive hierarchy
I am struggling to get one recursive CTE to work as desired but still with no chance.. So, I have the following similar table structures: tblMapping: map_id | type_id | name | parent_id 1 1 A1 0 2 1 A2 0 3 1 A3 1 4 1 A4 3 5 2 B1 0 6 2 B2 5 7 2 B3 6 8 1 A5 4 9 2 B4 0 tblRoleGroup: role_group_id | type_id | map_id | desc_id 1 1 0 null 1 2 0 null 2 1 3 1 2 2 6 0 3 1 8 1 3 2 9 1 In tblRoleGroup, the desc_id field means: null - allow all (used only in combination with map_id=0) 0 - allow all from parent including parent 1 - allow only current node Still in tblRoleGroup if map_id=0 then the query should get all elements from same type_id The query result should look like this: role_group_id | type_id | map_id | path 1 1 1 A1 1 1 2 A2 1 1 3 A1.A3 1 1 4 A1.A3.A4 1 1 8 A1.A3.A4.A5 1 2 5 B1 1 2 6 B1.B2 1 2 7 B1.B2.B3 1 2 9 B4 2 1 3 A1.A3 2 2 6 B1.B2 2 2 7 B1.B2.B3 3 1 8 A1.A3.A4.A5 3 2 9 B4 The query below solves only a part of the expected result, but I wasn't able to make it work as the expected result.. WITH Hierarchy(map_id, type_id, name, Path) AS ( SELECT t.map_id, t.type_id, t.name, CAST(t.name AS varchar(MAX)) AS Expr1 FROM dbo.tblMapping AS t LEFT JOIN dbo.tblMapping AS t1 ON t1.map_id = t.parent_id WHERE (t1.parent_id=0) UNION ALL SELECT t.map_id, t.type_id, t.name, CAST(h.Path + '.' + t.name AS varchar(MAX)) AS Expr1 FROM Hierarchy AS h JOIN dbo.tblMapping AS t ON t.parent_id = h.map_id ) SELECT h.map_id, h.type_id, t.role_group_id, h.Path AS Path FROM Hierarchy AS h LEFT JOIN dbo.tblRoleGroup t ON t.map_id = h.map_id Could someone help me on this? Thank you
At first I create a function that brings all descendants of passed map_id: CREATE FUNCTION mapping (#map_id int) RETURNS TABLE AS RETURN ( WITH rec AS ( SELECT map_id, [type_id], CAST(name as nvarchar(max)) as name, parent_id FROM tblMapping WHERE map_id = #map_id UNION ALL SELECT m.map_id, m.[type_id], r.name+'.'+m.name, m.parent_id FROM rec r INNER JOIN tblMapping m ON m.parent_id = r.map_id ) SELECT * FROM rec ); GO Then run this: ;WITH rec AS ( SELECT map_id, [type_id], CAST(name as nvarchar(max)) as name, parent_id FROM tblMapping WHERE parent_id=0 UNION ALL SELECT m.map_id, m.[type_id], r.name+'.'+m.name, m.parent_id FROM rec r INNER JOIN tblMapping m ON m.parent_id = r.map_id ) SELECT t.role_group_id, r.[type_id], r.map_id, r.name as [path] FROM tblRoleGroup t CROSS JOIN rec r WHERE r.[type_id] = CASE WHEN t.desc_id IS NULL AND t.map_id = 0 THEN t.[type_id] ELSE NULL END OR r.map_id = CASE WHEN t.desc_id = 1 THEN t.map_id ELSE NULL END OR r.map_id IN ( SELECT map_id FROM dbo.mapping (CASE WHEN t.desc_id = 0 THEN t.map_id ELSE NULL END) ) ORDER BY role_group_id, r.[type_id], r.map_id Will give you: role_group_id type_id map_id path 1 1 1 A1 1 1 2 A2 1 1 3 A1.A3 1 1 4 A1.A3.A4 1 1 8 A1.A3.A4.A5 1 2 5 B1 1 2 6 B1.B2 1 2 7 B1.B2.B3 1 2 9 B4 2 1 3 A1.A3 2 2 6 B1.B2 2 2 7 B1.B2.B3 3 1 8 A1.A3.A4.A5 3 2 9 B4
T-SQL Reverse Pivot on every character of a string
We have a table like below in an sql server 2005 db: event_id staff_id weeks 1 1 NNNYYYYNNYYY 1 2 YYYNNNYYYNNN 2 1 YYYYYYYYNYYY This is from a piece of timetabling software and is basically saying which staff members are assigned to an event (register) and the set of weeks they are teaching that register. So staff_id 1 isn't teaching the first 3 weeks of event 1 but is teaching the following 4.... Is there an easy way to convert that to an easier form such as: event_id staff_id week 1 1 4 1 1 5 1 1 6 1 1 7 1 1 10 1 1 11 1 1 12 1 2 1 1 2 2 1 2 3 1 2 7 1 2 8 1 2 9 2 1 1 2 1 2 2 1 3 2 1 4 2 1 5 2 1 6 2 1 7 2 1 8 2 1 10 2 1 11 2 1 12
WITH cte AS ( SELECT 1 AS [week] UNION ALL SELECT [week] + 1 FROM cte WHERE [week] < 53 ) SELECT t.event_id, t.staff_id, cte.[week] FROM your_table AS t INNER JOIN cte ON LEN(ISNULL(t.weeks, '')) >= cte.[week] AND SUBSTRING(t.weeks, cte.[week], 1) = 'Y' ORDER BY t.event_id, t.staff_id, cte.[week]
Grouping Hierarchical data (parentID+ID) and running sum?
I have the following data: ID parentID Text Price 1 Root 2 1 Flowers 3 1 Electro 4 2 Rose 10 5 2 Violet 5 6 4 Red Rose 12 7 3 Television 100 8 3 Radio 70 9 8 Webradio 90 I am trying to group this data with Reporting Services 2008 and have a sum of the price per group of level 1 (Flowers/Electro) and for level 0 (Root). I have a table grouped on [ID] with a recursive parent of [parendID] and I am able to calculate the sum for the level 0 (just one more row in the table outside the group), but somehow I am not able to create sum's per group as SRSS does "create" groups per level. My desired result looks like so: ID Text Price 1 Root |2 Flowers |-4 Rose 10 |-5 Violet 5 | |-6 Red Rose 12 | Group Sum-->27 |3 Electro |-7 Television 100 |-8 Radio 70 |-9 Webradio 90 Group Sum-->260 ---------------------- Total 287 (indentation of ID just added for level clarification) With my current approach I cannot get the group sums, so I figured out I would need the following data structure: ID parentID Text Price level0 level1 level2 level3 1 Root 1 2 1 Flowers 1 1 3 1 Electro 1 2 4 2 Rose 10 1 1 1 5 2 Violet 5 1 1 2 6 4 Red Rose 12 1 1 1 1 7 3 Television 100 1 2 1 8 3 Radio 70 1 2 2 9 8 Webradio 90 1 2 2 1 When having the above structure I can create an outer grouping of level0, with child groupings level1, level2, level3 accordingly . When now having a "group sum" on level1, and the total sum outside the group I have EXACTLY what I want. My question is the following: How do I either achieve my desired result with my current data structure, or how do I convert my current data structure (outer left joins?) into the "new data structure" temporarily - so I can run my report off of the temp table? Thanks for taking your time, Dennis
WITH q AS ( SELECT id, parentId, price FROM mytable UNION ALL SELECT p.id, p.parentID, q.price FROM q JOIN mytable p ON p.id = q.parentID ) SELECT id, SUM(price) FROM q GROUP BY id Update: A test script to check: DECLARE #table TABLE (id INT NOT NULL PRIMARY KEY, parentID INT, txt VARCHAR(200) NOT NULL, price MONEY) INSERT INTO #table SELECT 1, NULL, 'Root', NULL UNION ALL SELECT 2, 1, 'Flowers', NULL UNION ALL SELECT 3, 1, 'Electro', NULL UNION ALL SELECT 4, 2, 'Rose', 10 UNION ALL SELECT 5, 2, 'Violet', 5 UNION ALL SELECT 6, 4, 'Red Rose', 12 UNION ALL SELECT 7, 3, 'Television', 100 UNION ALL SELECT 8, 3, 'Radio', 70 UNION ALL SELECT 9, 8, 'Webradio', 90; WITH q AS ( SELECT id, parentId, price FROM #table UNION ALL SELECT p.id, p.parentID, q.price FROM q JOIN #table p ON p.id = q.parentID ) SELECT t.*, psum FROM ( SELECT id, SUM(price) AS psum FROM q GROUP BY id ) qo JOIN #table t ON t.id = qo.id Here's the result: 1 NULL Root NULL 287,00 2 1 Flowers NULL 27,00 3 1 Electro NULL 260,00 4 2 Rose 10,00 22,00 5 2 Violet 5,00 5,00 6 4 Red Rose 12,00 12,00 7 3 Television 100,00 100,00 8 3 Radio 70,00 160,00 9 8 Webradio 90,00 90,00
I found a really ugly way to do what I want - maybe there is something better? SELECT A.Text, A.Price, CASE WHEN D.Text IS NULL THEN CASE WHEN C.Text IS NULL THEN CASE WHEN B.Text IS NULL THEN A.ID ELSE B.ID END ELSE C.ID END ELSE D.ID END AS LEV0, CASE WHEN D.Text IS NULL THEN CASE WHEN C.Text IS NULL THEN CASE WHEN B.Text IS NULL THEN NULL ELSE A.ID END ELSE B.ID END ELSE C.ID END AS LEV1, CASE WHEN D.Text IS NULL THEN CASE WHEN C.Text IS NULL THEN NULL ELSE A.ID END ELSE B.ID END AS LEV2, CASE WHEN D.Text IS NULL THEN NULL ELSE A.ID END AS LEV3 FROM dbo.testOld AS A LEFT OUTER JOIN dbo.testOld AS B ON A.parentID = B.ID LEFT OUTER JOIN dbo.testOld AS C ON B.parentID = C.ID LEFT OUTER JOIN dbo.testOld AS D ON C.parentID = D.ID Output of this is: Text Price LEV0 LEV1 LEV2 LEV3 ---------- ----------- ----------- ----------- ----------- ----------- Root NULL 1 NULL NULL NULL Flowers NULL 1 3 NULL NULL Electro NULL 1 4 NULL NULL Television 100 1 4 5 NULL Radio 70 1 4 6 NULL Rose 10 1 3 7 NULL Violet 5 1 3 8 NULL Webradio 90 1 4 5 14 Red Rose 12 1 3 7 15 With this structure I can go ahead and create 4 nested groups on the LEV0-3 columns including subtotals per group (as shown above in my desired result).