So I've been struggling with this for the past hour or so now
I'm writing a script to extract some data in a nice format for me to use in another process but I can't quite figure out this particular bit (I don't use SQL Server much)
I've got several tables that are all involved in this script that link together to get all the information. All of these tables are just using fake data and names but it shows the structure hopefully
tblCategories
cat_id cat_name
1 Trousers
2 Tracksuits
3 Woolen
tblCategoryHierarchy
ch_parentid ch_childid
0 1
1 2
2 3
I've also got my product table which has the cat_id in it
tblProduct
p_id p_name p_cat_id
1 Red Trouser 3
So from this, I want to display the product id, name and the hierarchy of all categories linked to the one in the tblProduct.
So for this example, it would display:
id name cats
1 Red Trouser Trousers > Tracksuits > Woolen
Hopefully somebody can give me a hand here! Thanks
Try this:
;WITH CTE AS (
SELECT p.p_id AS id, p.p_cat_id, 1 AS level,
p.p_name, CAST(c.cat_name AS VARCHAR(200)) AS cat
FROM tblProduct AS p
JOIN tblCategories AS c ON p.p_cat_id = c.cat_id
UNION ALL
SELECT c.id, ch.ch_parentid AS cat_id, level = c.level + 1,
c.p_name, CAST(c2.cat_name AS VARCHAR(200)) AS cat
FROM tblCategoryHierarchy AS ch
JOIN CTE AS c ON ch.ch_childid = c.p_cat_id
JOIN tblCategories AS c2 ON ch.ch_parentid = c2.cat_id
)
SELECT id, p_name,
STUFF(REPLACE((SELECT CONCAT('>', cat)
FROM CTE
ORDER BY level DESC
FOR XML PATH('')), '>', '>'), 1, 1, '') AS cat
FROM CTE
The recursive part of the query returns all categories from child up to parent level. The query uses FOR XML PATH in order to concatenate category names in reverse level order (i.e. from parent to child).
REPLACE is used because '>' character is rendered as 'gt;' from FOR XML PATH.
Related
I am attempting to use a CTE to find the first common ancestor in a hierarchy for all values in a source table. I am able to return the entire hierarchy, but cannot seem to find a way to get the first common ancestor.
The ultimate goal is to update the source table to use only common ancestors.
Source table:
Type_Id Id Parent_Id Group_Id
A 3 2 1
A 4 2 1
A 5 4 1
After the CTE the table I get is:
Type_Id Id Child_Id Parent_Id Group_Id Level
A 3 3 2 1 1
A 4 4 2 1 1
A 5 5 4 1 1
A 5 4 2 1 2
What I'm looking to do is update the id of the source so that for each type and group combination, the new id is to be the first common parent. In this case, it would be set all ids to 2 as that's the first common entry.
You can try to use cte recursive self-join by your logic.
;WITH CTE as (
SELECT Type_Id,Id,id Child_Id,Parent_Id,Group_Id,1 Level
FROM T
UNION ALL
SELECT c.Type_Id,c.Id,t.id Child_Id,t.Parent_Id,t.Group_Id,c.Level+1
FROM CTE c
INNER JOIN T t
ON c.Parent_Id = t.ID AND c.Type_Id = t.Type_Id
)
SELECT *
FROM CTE
sqlfiddle
By building a list of all ancestor relationships (including self) and identifying the depth of each node from the root, you can then query all ancestors for the N selected nodes of interest. Any ancestors occurring N times are candidate common ancestors. The one with the deepest depth is the answer.
The following is what I came up with using simplified Node data containing only Id and ParentId. (The initial CTE was adapted from D-Shih's post.)
;WITH Ancestry as (
-- Recursive list of ancestors (including self) for each node
SELECT ChildId = N.Id, AncestorLevel = 0, AncestorId = N.Id, AncestorParentId = N.ParentId
FROM #Node N
UNION ALL
SELECT C.ChildId, AncestorLevel = C.AncestorLevel + 1, AncestorId = P.Id, AncestorParentId = P.ParentId
FROM Ancestry C
INNER JOIN #Node P ON P.Id = C.AncestorParentId
),
AncestryDepth AS (
-- Calculate depth from root (There may be a quicker way)
SELECT *, Depth = COUNT(*) OVER(PARTITION BY ChildId) - AncestorLevel
FROM Ancestry
),
Answer AS (
-- Answer is deepest node that is an ancestor of all selected nodes
SELECT TOP 1 Answer = A.AncestorId
FROM #Selected S
JOIN AncestryDepth A ON A.ChildId = S.Id
GROUP BY A.AncestorId, A.Depth
HAVING COUNT(*) = (SELECT COUNT(*) FROM #Selected) -- Ancestor covers all
ORDER BY A.Depth DESC -- Deepest
)
SELECT *
FROM Answer
There may be room for improvement, possibly a more direct calculation of depth, but this appears to give the correct result.
See this db<>fiddle for a working demo with data and several test scenarios.
We have for example this table:
pl_num camp_type products
1 T 1,2,3
2 B 1,3,4
Yeah, I know it's not in 1NF but we need to work with it
because of application loads data in such way.
And we have table DICT_PRODUCT, for example (in reality, there are more than 500 product):
id product_name
1 a
2 b
3 c
4 d
So, what we need is to create view where product_id was replaced by its name in dictionary
---V_TAB1 ---
pl_num camp_type products
1 T 1,b,c
2 B a,c,d
Try this. It will work if products column in TAB1 contain numbers and not any other characters.
WITH prod
AS (SELECT pl_num, camp_type, TO_NUMBER (TRIM (COLUMN_VALUE)) product
FROM Tab1 t, XMLTABLE (t.products))
SELECT prod.pl_num,
prod.camp_type,
LISTAGG (d.product_name, ',') WITHIN GROUP (ORDER BY id) products
FROM prod JOIN dict_product d ON prod.product = d.id
GROUP BY prod.pl_num, prod.camp_type;
DEMO
Try this one:
select distinct *
from (
select t.u_name, u_id, regexp_substr(t.prod,'[^,]+', 1, level) id
from (select prod,u_id, u_name from cmdm.t_prod) t
connect by regexp_substr(prod,'[^,]+',1,level) is not null) ut
inner join cmdm.t_dct dt
on ut.id=dt.id
I have a table category that has an int field that can reference the primary key in the same table.
like this:
ID category isSubCategoryOf orderingNumber
3 "red t-shirts" 2 2
1 "clothes" NULL 1
4 "cars" NULL 1
6 "Baby toys" 5 1
5 "Toys" NULL 1
2 "t-shirt" 1 1
I want the table to be order such that under each category all sub-categories are listed and under that category all of that sub-category.
ID category isSubCategoryOf orderingNumber
1 "clothes" NULL 1
2 "t-shirt" 1 1
3 "red t-shirts" 2 2
4 "cars" NULL 1
5 "Toys" NULL 1
6 "Baby toys" 5 1
Is such a thing possible to do with SQL or do I have to order this later by hand?
I I understand your needs correctly, you'll need a recursive query to deal with your hierarchical data:
WITH recCTE AS
(
--recursive seed
SELECT
category,
ID,
isSubCategoryOf as ParentID,
orderingNumber,
CAST(ID as VARCHAR(100)) as Path,
1 as depth
FROM table
WHERE isSubCategoryOf IS NULL
UNION ALL
--recursive term
SELECT
table.category,
table.id,
table.isSubCategoryOf,
table.orderingNumber,
recCTE.path + '>' + table.id,
recCTE.depth + 1
FROM recCTE
INNER JOIN table ON
recCTE.ID = table.isSubCategoryOf
)
SELECT * FROM recCTE ORDER BY path
Recursive queries are made up of three parts.
The recursive seed, which is the starting point of the recursive look ups. In your case it's any record with a NULL isSubCategoryOf. You can think of these as the Root of your hierarchy.
The recursive term, which is the part of the recursive CTE that refers back to itself. It iterates until it comes up with no records for each leg of the hierarchy
The final Select statement that selects from the recursive CTE.
Here I made a path field that stitches together each ID that is part of the hieararchy. This gives you the field you can sort on to get your hierarchical sort as asked.
It seems like, with your data, your orderingNumber is akin to the depth field that I added to the recursive CTE above. If that's the case, then you can remove that field from the CTE and save a bit of processing.
if you just want to sotrt and print you can make calculated column
select ID, category, isSubCategoryOf, orderingNumber, ID + '_' + isSubCategoryOf as SortOrder
from table
order by ID + '_' + isSubCategoryOf
I have a table which has these columns: id, text, parentid. And when a row is a root item (doesn't have any parent item), then parentid = 0.
What I want to do, is find text of the first root (root of root of ... root of item) of a specific item.
Here's an example:
SELECT parentid FROM cat WHERE id = 1234 --returns 1120
SELECT parentid FROM cat WHERE id = 1120 --returns 1011
SELECT parentid FROM cat WHERE id = 1011 --returns 0. So this the first root.
SELECT text FROM cat WHERE id = 1011 --returns what I want.
I know it's easily possible with Loops, but I'm using sqlite which doesn't support loops.
So, the question is, is there any way to implement this in sqlite without using any other scripts?
This recursive CTE will give you the desired result. Please note that the CTEs are available only in the latest versions of SQLite starting from version 3.8.3
;with cte as (
select id, parentid, text, 1 level
from t where id = 1234
UNION all
select t.id, t.parentid, t.text, cte.level + 1
from cte inner join t on cte.parentid = t.id
where cte.parentid <> 0)
select * from cte where parentid = 0
I'm creating a SQL Server 2008 query that would output the list of employees in a company along with the team they are on with an additional column.
Example of the org tree:
Level 0: CEO
Level 1: A, B, and C
Level 2:
For A:1,2,3
For B:4,5,6
For C:7,8,9
In my resulting set, I should see three columns -- name, level (of the tree), and team. For 1,2, and 3, I'd see 'A' as their team and 2 as the level. For 4,5, and 6, 'B' and 2 for the level and so on.
I'm using a recursive query to navigate the tree (no problems there), but since I need to "carry" the team name down the query (in case there's a level 8 -- it should still show the person in level 1 they report to), I'm doing this:
(...)
UNION ALL
-- Recursive Member Definition
-- in here level increments one each time, and the team should output the child
-- of the top manager
SELECT A.treenodeid, A.parentnodeid, A.email, LEVEL+1, team =
CASE LEVEL
When 1 then SET #salead = A.Email
Else #salead
END
FROM XX as A
INNER JOIN TeamsTable as B on A.parentnodeid = b.treenodeID
Since I'm trying to use a CASE to check if the level is 1 (to update the team name to whatever the team lead's email name is), SQL keeps saying that in the case I have "Incorrect syntax near SET".
Is it possible to do this sort of assignment in a CASE? I've looked around and haven't found if this can work with my recursive case.
Here's all the query (assuming that the root is 'JohnSmith'):
WITH TeamsTable (treenodeid, parentnodeid, email, Level, team)
AS
(
-- Anchor - Level starts with 0, and the team is empty for the top manager
SELECT treenodeid,parentnodeid,email,0,''
FROM XX WHERE email = 'JohnSmith'
UNION ALL
-- Recursive Member Definition - in here level increments one each time, and the team should output the child of the top manager
SELECT
A.treenodeid, A.parentnodeid, A.email, LEVEL+1, team =
CASE LEVEL
When 1 then SET #salead = A.Email
Else #salead
END
FROM XX as A
INNER JOIN TeamsTable as B on A.parentnodeid = b.treenodeID
)
-- Statement that executes the CTE
SELECT *
FROM TeamsTable
Thanks a lot, guys!
Why do you need the variable at all? I don't see it being used subsequently
I would approach this problem from the bottom up, not from the top down.
You've not said what teams A, B, C or CEO should be in, so I've made that up.
I've also included the sample data in a usable form:
create table Org (
ID int not null,
Name varchar(10) not null,
ParentID int null
)
go
insert into Org (ID,Name,ParentID) values
(1,'CEO',null),
(2,'A',1),
(3,'B',1),
(4,'C',1),
(5,'1',2),
(6,'2',2),
(7,'3',2),
(8,'4',3),
(9,'5',3),
(10,'6',3),
(11,'7',4),
(12,'8',4),
(13,'9',4)
Query:
;With AllPeople as (
select ID,ID as LastParentID,ParentID as NextParentID, CASE WHEN ParentID is null THEN 0 ELSE 1 END as Level
from Org
union all
select ap.ID,ap.NextParentID,o.ParentID,Level + 1
from
AllPeople ap
inner join
Org o
on
ap.NextParentID = o.ID and
o.ParentID is not null
), Roots as (
select ID from Org where ParentID is null
), RootedPeople as (
select * from AllPeople where NextParentID is null or NextParentID in (select ID from Roots)
), Names as (
select
oself.Name,
oteam.Name as Team,
Level
from
RootedPeople rp
inner join
Org oself on rp.ID = oself.ID
left join
Org oteam on rp.LastParentID = oteam.ID
)
select * from Names
Result:
Name Team Level
---------- ---------- -----------
CEO CEO 0
A A 1
B B 1
C C 1
9 C 2
8 C 2
7 C 2
6 B 2
5 B 2
4 B 2
3 A 2
2 A 2
1 A 2
Explanation of the CTEs:
AllPeople is a recursive query that climbs up the organisation tree until it reaches the root. We use two columns (LastParentID and NextParentID) to track two levels of the hierarchy - because, apparently, once we reach the root, we want the level before that.
Roots finds all of the people who don't have a parent. It's how we identify the rows from AllPeople that were complete, in,
RootedPeople where we find rows which never successfully found any parents, or where the recursion had reached the top of the tree.
Names and finally we join back to the Org table to assign names to individuals and teams. This one isn't necessary - it could be the final query by itself.
Note also that, due to the recursive way the AllPeople CTE is built, we calculate the levels as we go - every time we recurse, we add one to the Level that this row represents.