SQL SERVER 2008 tree query - sql

I have a table called Module.
The table structure some things like:
ModuleID ModuleName ParentID
1 System Manage 0
2 Database Manage 1
3 Area Manage 1
4 Basic Setting 0
I would like to get the results below by a sql statement.
ModuleID ModuleName ParentMoudle
1 System Manage 0
2 Database Manage System Manage
3 Area Manage System Manage
4 Basic Setting 0
I am a sql newbie .Thank you very much!

Parent child relationship can be achived by LEFT OUTER JOIN and querying same table.
select m.moduleid,m.modulename,COALESCE(p.modulename,0) from table as m
left outer join table as p on m.moduleid = p.module.id

This should work:
;WITH Tree AS
(
SELECT *, CAST(NULL AS VARCHAR(25)) AS ParentName
FROM #TT
WHERE ParentID IS NULL
UNION ALL
SELECT Fam.*,Tree.ModuleName AS ParentName
FROM #TT AS Fam
INNER JOIN Tree
ON Fam.ParentID = Tree.moduleID
)
SELECT * FROM Tree

Related

UNION columns in one SELECT

Let's say :
SELECT Item.Id, Item.ParentId FROM Item ..."
Returns me this data:
Id | ParentId
----------------
1 | NULL
2 | 17
3 | 13
Is there is a way to get this data as one column by using some kind of UNION but on columns from only one SELECT ? Something like:
SELECT (Item.Id UNION Item.ParentId) AS Id FROM Item...
Result :
Id |
----
1 |
2 |
3 |
NULL
17 |
13 |
EDIT EXAMPLE:
I have Media Table:
Id | ParentId
----------------
1 | NULL
2 | 1
3 | 2
It have relations with itself, this is some kind of 3 level tree structure
(Series -> Seasons -> Episodes)
There is another Table Offer which contain information about availability:
Id | MediaId | Availability
------------------------------
1 | 3 | true
I need to get id's of all media that are available, but also all parent's id, of all levels.
I was thinking about:
SELECT Media.Id, MediaSeason.Id, MediaSeries.Id FROM Media
LEFT JOIN Media AS MediaSeason ON MediaSeason.Id = Media.ParentId
LEFT JOIN Media AS MediaSeries ON MediaSeries.Id = MediaSeason.ParentId
LEFT JOIN Offer ON Offer.MediaId = Media.Id
WHERE Offer.Availability = true
This gives me all id's i need but in three different columns and I'm trying to find a way to put it into one, without repeating join and where login in 3 different SELECTS.
I'm using MSSQL.
Try this:
SELECT * FROM (SELECT Item.Id FROM Item ...
UNION ALL
SELECT Item.ParentId FROM Item ...)
If your children and parents are in the same table (Item)
SELECT Id FROM Item
Will retrieve all Items, including Parents because parents are also Items.
But if what you want is to not repeat the where clause and have Ids of any matched Media and its associated parents (even if the parent media does not match the where clause) you can try this:
SELECT
m.Id
FROM
Media m INNER JOIN (
SELECT
m2.Id, m2.ParentId
FROM
Media m2
LEFT JOIN Offer ON Offer.MediaId = m2.Id
WHERE
Offer.Availability = true
) tmp ON (tmp.Id = m.Id OR tmp.ParentId = m.Id)
Finally, for three levels:
SELECT
m.Id
FROM
Media m INNER JOIN (
SELECT
m2.Id, m2.ParentId, m3.ParentId AS GrandParentId
FROM
Media m2
LEFT JOIN Media m3 ON m2.ParentId = m3.Id
LEFT JOIN Offer ON Offer.MediaId = m2.Id
WHERE
Offer.Availability = true
) tmp ON (tmp.Id = m.Id OR tmp.ParentId = m.Id OR tmp.GrandParentId = m.Id)
SELECT DISTINCT
pivot_hierarchy.media_id
FROM
offers o
LEFT JOIN
media m1
ON m1.id = o.media_id
LEFT JOIN
media m2
ON m2.id = m1.parent_id
OUTER APPLY
(
SELECT o.media_id
UNION ALL
SELECT m1.parent_id WHERE m1.parent_id IS NOT NULL
UNION ALL
SELECT m2.parent_id WHERE m2.parent_id IS NOT NULL
)
AS pivot_hierarchy
WHERE
o.availability = 'true'
Everything up to the APPLY should be self explanatory. Get the offers, get the parent of that media if it has one, and the parent of that media if it has one.
The APPLY then joins each row on to a function that can return more than one row each. In this case the function returns 1, 2 or 3 rows. Those being the media id, it parent if it has one, and its grand-parent if it has one. To do that, the function unions the three input columns, provided that they’re not null.
This avoids having to join back on to the media table again.
Also, you need a distinct in the select. Otherwise the same series or season id could return multiple times.
Nested selects can be avoided in UNION
create table tab (
Id int,
ParentId int
);
insert into tab
values
(1, NULL),
(2, 17),
(3, 13);
then do
select ID as ID
from tab
union all
select ParentId as ID
from tab
NOTE: DB queries can be conveniently tested live, e.g. http://sqlfiddle.com/#!17/7a3a8/2

SQL Server: Select rows in table that share value with rows in another table

I have searched the questions but haven't been able to find an answer. Perhaps my wording may be wrong. My problem is as follows:
Given table Users:
ID code owner
1 777 James
2 432 George
3 111 Kale
And table Products:
ID product_name code
1 chair 777
2 table 777
3 fan 432
4 monitor 777
5 sofa 111
6 bed 111
I need a query that fetches N number of rows from table Users and then fetches all rows from table Products that have a code matching the code of Any row fetched previously.
Is this possible in SQL Server? Is it optimal?
For above example if i fetch first 2 rows (owners James and George) I should get all products with code 777 and 432.
To select the first n (here 2) users use TOP. Put that in a subquery and LEFT JOIN products on the common code to it.
SELECT *
FROM (SELECT TOP 2
*
FROM users
ORDER BY id) u
LEFT JOIN products p
ON p.code = u.code;
If you want to make users, that don't have at least one product, disappear you can replace the LEFT JOIN by an INNER JOIN.
Yes it is a common thing, called "Joins"
for example this would give you the answer:
Select products.product_name,users.owner FROM products LEFT JOIN users ON products.code=users.code
You can read more about JOINS here: https://www.w3schools.com/sql/sql_join.asp
Try this with Inner join :
Select products.product_name,users.owner FROM products inner JOIN users ON products.code=users.code
where users.code in (777,432)
Create table #Users
(
Id int Identity(1,1),
Code varchar(100),
Owner varchar(100)
)
Create table #Products
(
Id int Identity(1,1),
ProductName varchar(100),
Code varchar(100)
)
insert Into #Users(Code,Owner)
Select '777','James'
insert Into #Users(Code,Owner)
Select '432','George'
insert Into #Users(Code,Owner)
Select '111','Kale'
insert Into #Products(ProductName,Code)
Select 'Chair','777'
insert Into #Products(ProductName,Code)
Select 'Table','777'
insert Into #Products(ProductName,Code)
Select 'fan','432'
insert Into #Products(ProductName,Code)
Select 'monitor','777'
insert Into #Products(ProductName,Code)
Select 'Sofa','111'
insert Into #Products(ProductName,Code)
Select 'bed','111'
select * from #products
select * from #Products Left join #Users on #Users.Code=#Products.Code Where Owner in ('James','George')
This works even if code is not unique;
select * from Products where code in
(
select top 2 code from users order by id
)
Replace 2 with the relevant N

SQL Server: querying hierarchical and referenced data

I'm working on an asset database that has a hierarchy. Also, there is a "ReferenceAsset" table, that effectively points back to an asset. The Reference Asset basically functions as an override, but it is selected as if it were a unique, new asset. One of the overrides that gets set, is the parent_id.
Columns that are relevant to selecting the heirarchy:
Asset: id (primary), parent_id
Asset Reference: id (primary), asset_id (foreignkey->Asset), parent_id (always an Asset)
---EDITED 5/27----
Sample Relevent Table Data (after joins):
id | asset_id | name | parent_id | milestone | type
3 3 suit null march shape
4 4 suit_banker 3 april texture
5 5 tie null march shape
6 6 tie_red 5 march texture
7 7 tie_diamond 5 june texture
-5 6 tie_red 4 march texture
the id < 0 (like the last row) signify assets that are referenced. Referenced assets have a few columns that are overidden (in this case, only parent_id is important).
The expectation is that if I select all assets from april, I should do a secondary select to get the entire tree branches of the matching query:
so initially the query match would result in:
4 4 suit_banker 3 april texture
Then after the CTE, we get the complete hierarchy and our result should be this (so far this is working)
3 3 suit null march shape
4 4 suit_banker 3 april texture
-5 6 tie_red 4 march texture
and you see, the parent of id:-5 is there, but what is missing, that is needed, is the referenced asset, and the parent of the referenced asset:
5 5 tie null march shape
6 6 tie_red 5 march texture
Currently my solution works for this, but it is limited to only a single depth of references (and I feel the implementation is quite ugly).
---Edited----
Here is my primary Selection Function. This should better demonstrate where the real complication lies: the AssetReference.
Select A.id as id, A.id as asset_id, A.name,A.parent_id as parent_id, A.subPath, T.name as typeName, A2.name as parent_name, B.name as batchName,
L.name as locationName,AO.owner_name as ownerName, T.id as typeID,
M.name as milestoneName, A.deleted as bDeleted, 0 as reference, W.phase_name, W.status_name
FROM Asset as A Inner Join Type as T on A.type_id = T.id
Inner Join Batch as B on A.batch_id = B.id
Left Join Location L on A.location_id = L.id
Left Join Asset A2 on A.parent_id = A2.id
Left Join AssetOwner AO on A.owner_id = AO.owner_id
Left Join Milestone M on A.milestone_id = M.milestone_id
Left Join Workflow as W on W.asset_id = A.id
where A.deleted <= #showDeleted
UNION
Select -1*AR.id as id, AR.asset_id as asset_id, A.name, AR.parent_id as parent_id, A.subPath, T.name as typeName, A2.name as parent_name, B.name as batchName,
L.name as locationName,AO.owner_name as ownerName, T.id as typeID,
M.name as milestoneName, A.deleted as bDeleted, 1 as reference, NULL as phase_name, NULL as status_name
FROM Asset as A Inner Join Type as T on A.type_id = T.id
Inner Join Batch as B on A.batch_id = B.id
Left Join Location L on A.location_id = L.id
Left Join Asset A2 on AR.parent_id = A2.id
Left Join AssetOwner AO on A.owner_id = AO.owner_id
Left Join Milestone M on A.milestone_id = M.milestone_id
Inner Join AssetReference AR on AR.asset_id = A.id
where A.deleted <= #showDeleted
I have a stored procedure that takes a temp table (#temp) and finds all the elements of the hierarchy. The strategy I employed was this:
Select the entire system heirarchy into a temp table (#treeIDs) represented by a comma separated list of each entire tree branch
Get entire heirarchy of assets matching query (from #temp)
Get all reference assets pointed to by Assets from heirarchy
Parse the heirarchy of all reference assets
This works for now because reference assets are always the last item on a branch, but if they weren't, i think i would be in trouble. I feel like i need some better form of recursion.
Here is my current code, which is working, but i am not proud of it, and I know it is not robust (because it only works if the references are at the bottom):
Step 1. build the entire hierarchy
;WITH Recursive_CTE AS (
SELECT Cast(id as varchar(100)) as Hierarchy, parent_id, id
FROM #assetIDs
Where parent_id is Null
UNION ALL
SELECT
CAST(parent.Hierarchy + ',' + CAST(t.id as varchar(100)) as varchar(100)) as Hierarchy, t.parent_id, t.id
FROM Recursive_CTE parent
INNER JOIN #assetIDs t ON t.parent_id = parent.id
)
Select Distinct h.id, Hierarchy as idList into #treeIDs
FROM ( Select Hierarchy, id FROM Recursive_CTE ) parent
CROSS APPLY dbo.SplitIDs(Hierarchy) as h
Step 2. Select the branches of all assets that match the query
Select DISTINCT L.id into #RelativeIDs FROM #treeIDs
CROSS APPLY dbo.SplitIDs(idList) as L
WHERE #treeIDs.id in (Select id FROM #temp)
Step 3. Get all Reference Assets in the branches
(Reference assets have negative id values, hence the id < 0 part)
Select asset_id INTO #REFLinks FROM #AllAssets WHERE id in
(Select #AllAssets.asset_id FROM #AllAssets Inner Join #RelativeIDs
on #AllAssets.id = #RelativeIDs.id Where #RelativeIDs.id < 0)
Step 4. Get the branches of anything found in step 3
Select DISTINCT L.id into #extraRelativeIDs FROM #treeIDs
CROSS APPLY dbo.SplitIDs(idList) as L
WHERE
exists (Select #REFLinks.asset_id FROM #REFLinks WHERE #REFLinks.asset_id = #treeIDs.id)
and Not Exists (select id FROM #RelativeIDs Where id = #treeIDs.id)
I've tried to just show the relevant code. I am super grateful to anyone who can help me find a better solution!
--getting all of the children of a root node ( could be > 1 ) and it would require revising the query a bit
DECLARE #AssetID int = (select AssetId from Asset where AssetID is null);
--algorithm is relational recursion
--gets the top level in hierarchy we want. The hierarchy column
--will show the row's place in the hierarchy from this query only
--not in the overall reality of the row's place in the table
WITH Hierarchy(Asset_ID, AssetID, Levelcode, Asset_hierarchy)
AS
(
SELECT AssetID, Asset_ID,
1 as levelcode, CAST(Assetid as varchar(max)) as Asset_hierarchy
FROM Asset
WHERE AssetID=#AssetID
UNION ALL
--joins back to the CTE to recursively retrieve the rows
--note that treelevel is incremented on each iteration
SELECT A.Parent_ID, B.AssetID,
Levelcode + 1 as LevelCode,
A.assetID + '\' + cast(A.Asset_id as varchar(20)) as Asset_Hierarchy
FROM Asset AS a
INNER JOIN dbo.Batch AS Hierarchy
--use to get children, since the parentId of the child will be set the value
--of the current row
on a.assetId= b.assetID
--use to get parents, since the parent of the Asset_Hierarchy row will be the asset,
--not the parent.
on Asset.AssetId= Asset_Hierarchy.parentID
SELECT a.Assetid,a.name,
Asset_Hierarchy.LevelCode, Asset_Hierarchy.hierarchy
FROM Asset AS a
INNER JOIN Asset_Hierarchy
ON A.AssetID= Asset_Hierarchy.AssetID
ORDER BY Hierarchy ;
--return results from the CTE, joining to the Asset data to get the asset name
---that is the structure you will want. I would need a little more clarification of your table structure
It would help to know your underlying table structure. There are two approaches which should work depending on your environment: SQL understands XML so you could have your SQL as an xml structure or simply have a single table with each row item having a unique primary key id and a parentid. id is the fk for the parentid. The data for the node are just standard columns. You can use a cte or a function powering a calculated column to determin the degree of nesting for each node. The limit is that a node can only have one parent.

Find root in a database tree-structured table using SQL

I have the following DB table describing a Bill Of Materials(BOM), basically a tree-structure:
Part(PartId, ParentId, PartName)
The Parts with ParentId = 0 are finished product, meaning they do not compose any other product.
Now given a PartId I would like to know to which products it belongs by using plain SQL (MS SQL Server) or LINQ lambda
Try the following:
;WITH CTE AS
(
SELECT PartId, ParentId
FROM Part
WHERE PartId = #PartId
UNION ALL
SELECT B.PartId, B.ParentId
FROM CTE A
INNER JOIN #Part B
ON A.ParentId = B.PartId
)
SELECT DISTINCT PartId
FROM CTE
WHERE ParentId = 0

Finding all children for multiple parents in single SQL query

Suppose I have a table with parent-child relationships.
parent child
1 4
1 5
2 6
3 7
4 8
6 9
7 10
8 11
Now I have a query that returns a list of people (eg. 1 and 2) and I want to find all their children, grandchildren etc. (in this case: 4, 5, 6, 8, 9, 11).
I know I can use common table expressions to search recursively, but I wondered if I could create a SQL statement to find all descendents at once without having to iterate over the input set.
Edit: sorry for not being clear enough. I'm looking for something like:
SELECT {Hierarchical relation} from table where parent in (1,2)
which should result in a single output column with rows for 4, 5, 6, 8, 9, 11.
I'm no longer interested in the relationship in the output, just the complete set of family members for multiple families.
Here it is
---- PlainTable ----
parent idElement (child)
Null 1
1 4
1 5
2 6
3 7
4 8
6 9
7 10
8 11
WITH tableR (parent, idElement)
AS
(
-- Anchor member definition
SELECT e.parent, e.idElement
FROM PlainTable AS e
WHERE parent in (1,2)
UNION ALL
-- Recursive member definition
SELECT e.parent, e.idElement
FROM PlainTable AS e
INNER JOIN tableR AS d
ON e.parent = d.idElement
)
-- Statement that executes the CTE
SELECT idElement
FROM tableR --inner join to plain table by id if needed
Ok, danihp's solution did put me on the right track. This is the solution I came up with:
DECLARE #Input TABLE (
id int
)
INSERT INTO #Input VALUES (1),(2)
;WITH Relations (parent, child)
AS
(
SELECT e.parent, e.child
FROM RelationTable AS e
WHERE parent in (SELECT * FROM #Input)
UNION ALL
SELECT e.parent, e.child
FROM RelationTable AS e
INNER JOIN Relations AS d
ON e.parent = d.child
)
SELECT child
FROM Relations
It results in a list of child ids (excluding the 2 parent ids like I said earlier in the question):
4,5,6,8,9,11
using CTE
Recursive Queries Using Common Table Expressions
http://msdn.microsoft.com/en-us/library/ms186243.aspx
and
http://www.sqlservercentral.com/articles/T-SQL/65540/
be can help you.
SQL Server 2008 has built in features to facilitate hierarchical data: http://msdn.microsoft.com/en-us/magazine/cc794278.aspx
I know I can use common table expressions to search recursively, but I
wondered if I could create a SQL statement to find all descendents at
once without having to iterate over the input set.
I'm not sure what you mean by that. Most (maybe all?) CTE's can be accomplished through the use of subqueries, but using a subqueries wouldn't be any faster. When you say of you don't want to 'iterate' over the input set it sounds like you're talking about the use of cursors and of course you can do it as a set based operation (using CTEs or subqueries) but there's no way around the recursion.
Edit: I'm sorry, I'm not thinking straight... of course you can't do recursion with normal subqueries but the point still stands that even if you could it wouldn't be faster. If you'd like to see strategies for doing recursion without CTE's then try searches for something like 'recursion sql 2000' since CTE's weren't around back then. Here are some examples: http://www.mssqltips.com/sqlservertip/938/recursive-queries-with-sql-server-2000/. Of course, the answer to your question remains the same though.
Typically I use a Nested Set Model for this and Yes you can have SQL do all the work for you, in fact you have SQL output XML that can be directly attached to .net treeview. Hope this helps
Link: Is there a simple way to query the children of a node?
While waiting for an updated post:
SELECT DISTINCT
p.parent AS parent
, c.child AS child
, IFNULL(g.child, 'NONE') AS grandchild_of_parent
FROM parent_child as p
LEFT JOIN parent_child AS c ON p.parent = c.parent
LEFT JOIN parent_child AS g ON c.child = g.parent;
Results would look like this:
parent child grandchild_of_parent
1 4 8
1 5 NONE
2 6 9
3 7 10
4 8 11
6 9 NONE
7 10 NONE
8 11 NONE
Such a simple-minded-but-probably-harder-to-maintain type of code, but since I'm not familiar with SQL Server 2008's built in features to handle this type of request, I'll just throw a long shot...
EDIT:
Just so you can see results for yourself while you study common table expressions and/or perhaps pivots ... this will get your results, but only up to the great grandchildren of 1 and 2.
-- A. Parents 1 and 2
SELECT DISTINCT p.parent FROM parent_child AS p
WHERE p.parent IN (1,2)
UNION
-- B : Children of A
SELECT DISTINCT p.child FROM parent_child AS p
WHERE p.parent IN (1,2)
UNION
-- C : Children of B, Grandchildren of A
SELECT DISTINCT p.child FROM parent_child AS p
WHERE p.parent IN (
SELECT DISTINCT p.child FROM parent_child AS p
WHERE p.parent IN (1,2)
)
UNION
-- D : Children of C, Great-Grandchildren of A
SELECT DISTINCT p.child FROM parent_child AS p
WHERE p.parent IN (
SELECT DISTINCT p.child FROM parent_child AS p
WHERE p.parent IN (
SELECT DISTINCT p.child FROM parent_child AS p
WHERE p.parent IN (1,2)
)
)
Again, I strongly suggest you study what the others have been posting.. and look into the links they provided. The inelegant query I provided you is not going to last long->it will absolutely FAIL once you have great-great-grandchildren.
---- PlainTable ----
parent idElement (child_id)
Null 1
1 4
1 5
2 6
3 7
4 8
6 9
7 10
8 11
**Table value function to get Child ids at 4(any) Level of parent in the same table:-**
FUNCTION fc_get_Child_IDs(Parent)
DECLARE #tbl TABLE (ID int)
DECLARE #level int=4
DECLARE #i int=1
insert into #tbl values (Parent)
while #i < #level
BEGIN
INSERT into #tbl
select child_id from PlainTable where Parent in (select ID from #tbl) and child_id not in (select ID from #tbl)
set #i = #i + 1
END