Return Query Based on Tree Structure - sql

I am looking to return a query that shows exactly like this:
Root 1
--> Child 2
----->Child 3
Root 2
--> Child 4
---->Child 5
So the query should return Root 1 as One row, ---> Child 2 as another row. Assume n Levels, and "--->" format is placed for each child. Level is higher then "---->" increases.
My Table definition is
[NodeId, ParentId, Name, Level]

On SQL Server 2008 and above, you can use hierarchyId datatype to quickly achieve the desired sorting. You can use REPLICATE() to get the dashes.
;with cte as (
select NodeId, ParentId, Name, 0 Level, '/' + cast(NodeId as varchar(max)) + '/' Hier
from tbl1
where ParentId is null
union all
select t.NodeId, t.ParentId, t.Name, Level+1, Hier + cast(t.NodeId as varchar(max)) + '/'
from tbl1 t
join cte c on t.ParentId = c.NodeId
)
select case when level=0
then ''
else replicate('-',level*2) + '>' end + Name
from cte
order by cast(Hier as hierarchyid);
SQL Fiddle
On earlier SQL Server 2005, you can emulate the hierarchyId sorting using zero-padded strings:
;with cte as (
select NodeId, ParentId, Name, 0 Level, right(replicate('0',10)+cast(NodeId as varchar(max)),11) Hier
from tbl1
where ParentId is null
union all
select t.NodeId, t.ParentId, t.Name, Level+1, Hier + right(replicate('0',10)+cast(t.NodeId as varchar(max)),11)
from tbl1 t
join cte c on t.ParentId = c.NodeId
)
select case when level=0
then ''
else replicate('-',level*2) + '>' end + Name
from cte
order by Hier;

Related

SQL Recursive CTE Replacing records in each recursion

I have a table like this:
ItemID ItemFormula
100 'ID_3+ID_5'
110 'ID_2+ID_6'
120 'ID_100+ID_110'
130 'ID_120+ID_4'
This is the simplified version of a formula table with nearly 1000 records and up to 40 levels of reference (items used in other items). The task is breaking down the formulas to just one level reference where no other items are in one item. For example in the table above for id=130 I should have '((ID_3+ID_5)+(ID_2+ID_6))+ID_4'
EDIT: The operations do not limit to "+" and items have a character between them to be recognizable. For the sake of simplicity, I removed that character.
I can use recursive CTE for that. but my problem is that due to high levels of reference, my recursive select has lots of records joining so it takes a lot to complete.
My question is that: Can I keep the previous recursion only each time the recursion happens?
Here is my CTE Code
WITH Formula
AS (SELECT A.ItemID
,'ID_' + CONVERT(VARCHAR(20), A.ItemID) AS ItemText
,CONVERT(VARCHAR(MAX), A.ItemFormula) AS ItemFormula
FROM (VALUES (100,'ID_3+ID_5'),
(110,'ID_2+ID_6'),
(120,'ID_100+ID_110'),
(130,'ID_120+ID_4')
) A (ItemID,ItemFormula)
)
,REC
AS
(
SELECT A.ItemID
,A.ItemText
,A.ItemFormula
,1 AS LevelID
FROM Formula A
UNION ALL
SELECT A.ItemID
,A.ItemText
,' '
+ TRIM (REPLACE (REPLACE (A.ItemFormula, B.ItemText, ' ( ' + B.ItemFormula + ' ) '), ' ', ' '))
+ ' ' AS ItemFormula
,A.LevelID + 1 AS LevelID
FROM REC A
CROSS APPLY
(
SELECT *
FROM
(
SELECT *
,ROW_NUMBER () OVER (ORDER BY GETDATE ()) AS RowNum
FROM Formula B2
WHERE CHARINDEX (B2.ItemText, A.ItemFormula) > 0
) B3
WHERE B3.RowNum = 1
) B
)
,FinalQ
AS
(
SELECT A2.ItemID
,A2.ItemFormula
,A2.LevelID
FROM
(
SELECT A.ItemID
,REPLACE (TRIM (A.ItemFormula), ' ', '') AS ItemFormula
,A.LevelID
,ROW_NUMBER () OVER (PARTITION BY A.ItemID ORDER BY A.LevelID DESC) AS RowNum
FROM REC A
) A2
WHERE A2.RowNum = 1
)
SELECT * FROM FinalQ A2 ORDER BY A2.ItemID;
Thanks in advance.
My question is that: Can I keep the previous recursion only each time the recursion happens?
No. The recursive CTE will keep adding rows to the ones found in previous iterations. You don't have some kind of control that would allow you to remove rows of the recursive CTE during its iterations.
You can, however, filter them out after the recursive CTE is complete, maybe on a secondary CTE that takes into account only the last meaninful rows (by some kind of rule to be defined).
The only vaguely similar idea is found in PostgreSQL where you can use the UNION clause in addition to UNION ALL, to avoid producing more identical rows. But this is different to what you need, anyway.
This is an enormously complicated problem. Here are the ideas:
Find which items do not need any insertions. These are the ones that have no references to any others.
Build an ordering for item insertion. An insertion can go into an item, assuming that the item is already defined. A recursive CTE can be used for this.
Enumerate the insertions. Everything from (1) gets a "1". The rest are in order.
Process the insertions in the insertion order.
Here is my solution:
with ordering as (
select itemid, itemtext, itemformula, convert(varchar(max), null) as otheritemtext, 1 as lev
from formula f
where not exists (select 1
from formula f2 join
string_split(f.itemformula, '+') s
on f2.itemtext = s.value
where f2.itemid <> f.itemid
)
union all
select f.itemid, f.itemtext, f.itemformula, convert(varchar(max), s.value), lev + 1
from formula f cross apply
string_split(f.itemformula, '+') s join
ordering o
on o.itemtext = s.value
-- where lev <= 2
),
ordered as (
select distinct o.*,
dense_rank() over (order by (case when lev = 1 then -1 else lev end), (case when lev = 1 then '' else otheritemtext end)) as seqnum
from ordering o
),
cte as (
select o.itemid, o.itemtext, o.itemformula, convert(varchar(max), o.otheritemtext) as otheritemtext,
o.itemformula as newformula, o.seqnum, 1 as lev
from ordered o
where seqnum = 1
union all
select cte.itemid, o.itemtext, o.itemformula, convert(varchar(max), cte.itemtext),
replace(o.itemformula, o.otheritemtext, concat('(', cte.newformula, ')')), o.seqnum, cte.lev + 1
from cte join
ordered o
on cte.itemtext = o.otheritemtext and cte.seqnum < o.seqnum
)
select *
from cte;
And the db<>fiddle.
You could take advantage of the logical order of the formulas if any (Item_100 can not reference Item_150) and process items in a descending order.
The following uses LIKE and it will not work for formulas which have overlapping patterns (eg ID_10 & ID_100) you could fix that by some string manipulation or by keeping ItemIDs of fixed length (eg. ID_10010 & ID_10100: start numbering of items from a high number like 10000)
declare #f table
(
ItemId int,
ItemFormula varchar(1000)
);
insert into #f(ItemId, ItemFormula)
values
(100, 'ID_3+ID_5'),
(110, 'ID_2+ID_6'),
(120, 'ID_100+ID_110'),
(130, 'ID_120+ID_4'),
(140, '(ID_130+ID_110)/ID_100'),
(150, 'sqrt(ID_140, ID_130)'),
(160, 'ID_150-ID_120+ID_140');
;with cte
as
(
select f.ItemId, replace(cast(f.ItemFormula as varchar(max)), isnull('ID_' + cast(r.ItemId as varchar(max)), ''), isnull('(' + r.ItemFormula+ ')', '')) as therepl, 1 as lvl
from #f as f
outer apply (
select *
from
(
select rr.*, row_number() over(order by rr.ItemId desc) as rownum
from #f as rr
where f.ItemFormula like '%ID_' + cast(rr.ItemId as varchar(1000)) + '%'
) as src
where rownum = 1
) as r
union all
select c.ItemId, replace(c.therepl, 'ID_' + cast(r.ItemId as varchar(max)), '(' + r.ItemFormula+ ')'), c.lvl+1
from cte as c
cross apply (
select *
from
(
select rr.*, row_number() over(order by rr.ItemId desc) as rownum
from #f as rr
where c.therepl like '%ID_' + cast(rr.ItemId as varchar(1000)) + '%'
) as src
where rownum = 1
) as r
),
rown
as
(
select *, row_number() over (partition by itemid order by lvl desc) as rownum
from cte
)
select *
from rown
where rownum = 1;

Lazy upward recursive query in sql

I'm trying to generate a path from the name of an item's parents. For example if test has for parent dad the path would be dad/test; and if dad had for parent gran the path of test would be gran/dad/test.
I only have the id of the child, so far I only have a query which generates the paths of everyone recursively and then selects the right one but that doesn't really seem efficient.
WITH SubItems
AS (
SELECT CAST([Name] AS VARCHAR(255)) AS [Path],
Id,
ParentId,
0 AS Depth
FROM Items
WHERE Id = 1 -- First parent of everyone
UNION ALL
SELECT CAST(CONCAT(parent.[Path], '/', sub.[Name]) AS VARCHAR(255)),
sub.Id,
sub.ParentId,
parent.Depth + 1
FROM Items sub
JOIN SubItems parent ON parent.Id = sub.ParentId
)
SELECT [Path]
FROM SubItems
WHERE Id = 1425 -- SubItem I want the path of
I can also go upwards, which would be faster but I can't create the path this way. I could try to concatenate all the results ordered by the "depth" but again this doesn't seem right.
DECLARE #Path;
WITH ParentItems
AS (
SELECT [Name],
Id,
ParentId,
0 AS Depth
FROM Items
WHERE Id = 1425 -- SubItem I want the path of
UNION ALL
SELECT [Name],
parent.Id,
parent.ParentId,
sub.Depth - 1
FROM Items parent
JOIN ParentItems sub ON sub.ParentId = parent.Id
)
SELECT #Path = COALESCE(#Path + '/', '') + [Name]
FROM ParentItems
ORDER BY Depth;
SELECT #Path;
Is there a way to go upwards recursively?
Something like this for example, where ParentPath would be equal to CONCAT(ParentPath, '/', [Path]) again:
WITH ...
SELECT CONCAT(ParentPath, '/', [Name])
FROM Items
I know in C# you could do something like:
function getPath() {
return (parent?.getPath() ?? "") + "/" + this.Name;
}
Edit: Why I can't construct the path going up, like this:
WITH ParentItems AS (
SELECT i.Name, i.Id, i.ParentId, 0 AS Depth,
CONVERT(VARCHAR(MAX), i.Name) as path
FROM Items i
WHERE i.Id = 1425 -- SubItem I want the path of
UNION ALL
SELECT i.Name, i.Id, i.ParentId, pi.Depth - 1,
CONCAT(pi.Name, '/', i.[Path])
FROM Items i JOIN
ParentItems pi
ON pi.ParentId = parent.Id
)
SELECT *
FROM ParentItems
ORDER BY Depth;
Assuming the example from above where gran is parent to dad is parent to test, the result of this query would be:
| name | path |
|------|---------------|
| gran | gran/dad/test |
| dad | dad/test |
| test | test |
While it should be the opposite:
| name | path |
|------|---------------|
| gran | gran/ |
| dad | gran/dad |
| test | gran/dad/test |
This is because of the way the query passes the name of the child upwards, adding it to the path of its parent rather than the opposite.
Why can't you construct the path going up?
WITH ParentItems AS (
SELECT i.Name, i.Id, i.ParentId, 0 AS Depth,
CONVERT(VARCHAR(MAX), i.Name) as path
FROM Items i
WHERE i.Id = 1425 -- SubItem I want the path of
UNION ALL
SELECT i.Name, i.Id, i.ParentId, pi.Depth + 1,
CONCAT(i.Name, '/', pi.path)
FROM Items i JOIN
ParentItems pi
ON pi.ParentId = parent.Id
)
SELECT *
FROM ParentItems
ORDER BY Depth DESC;
For future references, using FOR XML PATH('') seems to work.
WITH ParentItems
AS (
SELECT [Name],
Id,
ParentId,
0 AS Depth
FROM Items
WHERE Id = 1425 -- SubItem I want the path of
UNION ALL
SELECT [Name],
parent.Id,
parent.ParentId,
sub.Depth - 1
FROM Items parent
JOIN ParentItems sub ON sub.ParentId = parent.Id
)
SELECT (
SELECT '/' + [Name]
FROM ParentItems
ORDER BY Depth
FOR XML PATH('')
)
The following code:
Walks the tree from the child up to the oldest ancestor while assembling a path.
Gets the path to the oldest ancestor and splits it into individuals.
Walks the list of individuals from the oldest ancestor back down to the starting child while assembling the path.
NB: This code does not use String_Split because it is documented thusly: "The output rows might be in any order. The order is not guaranteed to match the order of the substrings in the input string." A Jeff Moden string splitter is used which guarantees the order of the results.
Note that you can select the results of any of the intermediate CTEs in order to see how the process proceeds. Just replace the final select statement with one of the alternatives provided in comments.
Confession: I didn't try to generate the curious dangling solidus in the first row of the desired output ("gran/") rather than the more consistent "gran". It is assumed to be a typographical error in the sample data.
-- Sample data.
declare #Samples as Table ( Id Int Identity, Name VarChar(10), ParentName VarChar(10) );
insert into #Samples ( Name, ParentName ) values
( 'test', 'dad' ),
( 'dad', 'gran' ),
( 'gran', null );
select * from #Samples;
-- Starting point.
declare #ChildName as VarChar(10) = 'test';
-- Walk the tree.
with
Tree as (
-- Note that paths in this initial tree are built using Id , not Name .
-- This keeps the path length down, ensures rows are uniquely identified, avoids problems with "funny" names, ... .
-- Start at the target child name.
select Id, Name, ParentName, 0 as Depth,
Cast( Id as VarChar(100) ) as Path
from #Samples
where Name = #ChildName
union all
-- Walk up the tree one level at a time.
select S.Id, S.Name, S.ParentName, T.Depth + 1,
Cast( Cast( S.Id as VarChar(100) ) + '/' + T.Path as VarChar(100) )
from Tree as T inner join
#Samples as S on S.Name = T.ParentName
),
TreePath as (
-- Take the path of the oldest ancestor and split it apart.
select ItemNumber, Cast( Item as Int ) as Item from Tree as T cross apply
dbo.DelimitedSplit8K( T.Path, '/' ) where T.ParentName is NULL ),
InvertedTree as (
-- Start at the first item on path, i.e. the oldest ancestor.
select S.Name, 1 as Depth,
Cast( S.Name as VarChar(100) ) as Path
from TreePath as TP inner join
#Samples as S on S.Id = TP.Item
where TP.ItemNumber = 1
union all
-- Add chldren on the way down.
select S.Name, IT.Depth + 1,
Cast( IT.Path + '/' + S.Name as VarChar(100) )
from InvertedTree as IT inner join
TreePath as TP on TP.ItemNumber = IT.Depth + 1 inner join
#Samples as S on S.Id = TP.Item
)
-- To see the intermediate results use one of the following select statements:
-- select * from Tree;
-- select * from TreePath;
-- select * from InvertedTree;
select Name, Path
from InvertedTree
order by Depth;
The Jeff Moden string splitter:
CREATE FUNCTION [dbo].[DelimitedSplit8K]
--===== Define I/O parameters
(#pString VARCHAR(8000), #pDelimiter VARCHAR(16))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE! IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
-- enough to cover VARCHAR(8000)
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT TOP (ISNULL(DATALENGTH(#pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+ Len( #pDelimiter ) FROM cteTally t WHERE SUBSTRING(#pString,t.N, Len( #pDelimiter ) ) = #pDelimiter
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1 ,8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
;

recursive query Sort

I have a Standard table, which sotres parent, child category relationship...like this.
id, parent, catName, sort
And I use the following query to create a recursive tree
;WITH cte AS (
SELECT 0 AS lvl, id, catName, parent,levels,sort,
CAST(id AS VARCHAR(128)) AS path
FROM CategoriesMap WHERE parent =0
UNION ALL
SELECT p.lvl + 1, c.id, c.catName, c.parent,c.levels,c.sort,
CAST(p.path + '_' + CAST(c.id AS VARCHAR) AS VARCHAR(128))
FROM CategoriesMap c
INNER JOIN cte p ON p.id = c.parent
)
SELECT
id,
catName AS catName,
lvl,
levels,
path,
parent,
sort
FROM cte
ORDER BY path
And the output is like this Image:
Look for the Row with value ASP.NET & CLASSIC ASP, these are the last leaf(children) for the technology > Software (parents), I want to sort the LAST CHILDREN of any given parent (last parent). I can have multiple parents for a given node (last child) & all I care about is sort the LAST Children (leaf) using the "Sort" column.
so basicly "Classic Asp" shoud be before "Asp.Net" (Last Column is SORT column in my image).
My query is fine, it returns the results as expected...only challenege is i want to SORT the last NODE using the SORT column in table, last node can have 3 or 4 children which I want to sort, all nodes above the last node are its parents (which are in correct order already).
I want output like this.... Internet > ISP's > CableVision (1) :
Verizon (2) as you can see CableVision & Verizon have Sort Value of 1
& then 2, Now lets say we have Shopping > Coupons > Macys(0) : Sears
(2), same thing....I want Macys & Sears to be sorted...and its pretty
obvious their parents are Shopping > Coupons.
#Richard aka cyberkiwi, after applying your code, my sorting for Categories table is very random. output is below
Delaying the calculation of the path for one level, so the final result set has the parent path (ppath) available:
;WITH cte AS (
SELECT 0 AS lvl, id, catName, parent,levels,sort,
CAST('' AS VARCHAR(128)) AS ppath
FROM CategoriesMap WHERE parent =0
UNION ALL
SELECT p.lvl + 1, c.id, c.catName, c.parent,c.levels,c.sort,
CAST(p.ppath + '_' + CAST(p.id AS VARCHAR) AS VARCHAR(128))
FROM CategoriesMap c
INNER JOIN cte p ON p.id = c.parent
)
SELECT
id,
catName,
lvl,
levels,
CAST(ppath + '_' + CAST(id AS VARCHAR) AS VARCHAR(128)) AS path,
parent,
sort
FROM cte
ORDER BY
CASE WHEN sort IS NULL
THEN path
ELSE ppath
END
, sort ;
Not really sure why the above gives error. This will not:
ORDER BY
CASE WHEN sort IS NULL
THEN CAST(ppath + '_' + CAST(id AS VARCHAR) AS VARCHAR(128))
ELSE ppath
END
, sort ;
This SQL Fiddle should give you what you need.
The trick really is when you mix leaves with branches. In my solution, leaves ALWAYS appear before branches, and within the leaves (even when inter-mixed with branches), they are sorted by the sort column of course.
DDL
create table CategoriesMap(
id int, parent int, catname varchar(20), sort int);
insert CategoriesMap select
1, 0, 'Activities', null union all select
2, 0, 'Property', null union all select
3, 2, 'For rent', null union all select
4, 2, 'For sale', null union all select
12, 0, 'Technology', 3 union all select
15, 12, 'Hardware', null union all select
21, 12, 'Phones', null union all select
22, 15, 'Computers', null union all select
18, 12, 'Software', null union all select
19, 18, 'Asp.net', 2 union all select
20, 18, 'SQL', 3 union all select
23, 18, 'Php', 4 union all select
24, 18, 'Classic ASP', 1;
Query
;WITH leaves AS (
SELECT A.id
FROM CategoriesMap A
LEFT JOIN CategoriesMap B ON A.id=B.parent
WHERE B.id is null
)
,cte AS (
SELECT 0 AS lvl, id, catName, parent,sort,
CAST(id AS VARCHAR(MAX)) AS path,
'/'+CAST(id AS VARCHAR(MAX))+'/' AS hier
FROM CategoriesMap
WHERE parent =0
UNION ALL
SELECT p.lvl + 1, c.id, c.catName, c.parent,c.sort,
p.path + '_' + CAST(c.id AS VARCHAR(MAX)),
p.hier + CAST(c.id AS VARCHAR(MAX)) + '/'
FROM CategoriesMap c
JOIN cte p ON p.id = c.parent
)
SELECT c.id,
c.catName,
c.lvl,
--levels,
c.path,
--c.hier,
c.parent,
c.sort
FROM cte c
LEFT JOIN leaves l on l.id=c.id
ORDER BY CASE WHEN l.id is null
then cast(hier as hierarchyid)
else cast(hier as hierarchyid).GetAncestor(1)
END,
CASE WHEN l.id is null then 0 else 1 end,
sort

How to get the deepest levels of a hierarchical sql query

I'm using SQLServer 2008.
Say I have a recursive hierarchy table, SalesRegion, whit SalesRegionId and ParentSalesRegionId. What I need is, given a specific SalesRegion (anywhere in the hierarchy), retrieve ALL the records at the BOTTOM level.
I.E.:
SalesRegion, ParentSalesRegionId
1, null
1-1, 1
1-2, 1
1-1-1, 1-1
1-1-2, 1-1
1-2-1, 1-2
1-2-2, 1-2
1-1-1-1, 1-1-1
1-1-1-2, 1-1-1
1-1-2-1, 1-1-2
1-2-1-1, 1-2-1
(in my table I have sequencial numbers, this dashed numbers are only to be clear)
So, if the user enters 1-1, I need to retrieve al records with SalesRegion 1-1-1-1 or 1-1-1-2 or 1-1-2-1 (and NOT 1-2-2). Similarly, if the user enters 1-1-2-1, I need to retrieve just 1-1-2-1
I have a CTE query that retrieves everything below 1-1, but that includes rows that I don't want:
WITH SaleLocale_CTE AS (
SELECT SL.SaleLocaleId, SL.SaleLocaleName, SL.AccountingLocationID, SL.LocaleTypeId, SL.ParentSaleLocaleId, 1 AS Level /*Added as a workaround*/
FROM SaleLocale SL
WHERE SL.Deleted = 0
AND (#SaleLocaleId IS NULL OR SaleLocaleId = #SaleLocaleId)
UNION ALL
SELECT SL.SaleLocaleId, SL.SaleLocaleName, SL.AccountingLocationID, SL.LocaleTypeId, SL.ParentSaleLocaleId, Level + 1 AS Level
FROM SaleLocale SL
INNER JOIN SaleLocale_CTE SLCTE ON SLCTE.SaleLocaleId = SL.ParentSaleLocaleId
WHERE SL.Deleted = 0
)
SELECT *
FROM SaleLocale_CTE
Thanks in advance!
Alejandro.
I found a quick way to do this, but I'd rather the answer to be in a single query. So if you can think of one, please share! If I like it better, I'll vote for it as the best answer.
I added a "Level" column in my previous query (I'll edit the question so this answer is clear), and used it to get the last level and then delete the ones I don't need.
INSERT INTO #SaleLocales
SELECT *
FROM SaleLocale_GetChilds(#SaleLocaleId)
SELECT #LowestLevel = MAX(Level)
FROM #SaleLocales
DELETE #SaleLocales
WHERE Level <> #LowestLevel
Building off your post:
; WITH CTE AS
(
SELECT *
FROM SaleLocale_GetChilds(#SaleLocaleId)
)
SELECT
FROM CTE a
JOIN
(
SELECT MAX(level) AS level
FROM CTE
) b
ON a.level = b.level
There were a few edits in there. Kept hitting post...
Are you looking for something like this:
declare #SalesRegion as table ( SalesRegion int, ParentSalesRegionId int )
insert into #SalesRegion ( SalesRegion, ParentSalesRegionId ) values
( 1, NULL ), ( 2, 1 ), ( 3, 1 ),
( 4, 3 ), ( 5, 3 ),
( 6, 5 )
; with CTE as (
-- Get the root(s).
select SalesRegion, CAST( SalesRegion as varchar(1024) ) as Path
from #SalesRegion
where ParentSalesRegionId is NULL
union all
-- Add the children one level at a time.
select SR.SalesRegion, CAST( CTE.Path + '-' + cast( SR.SalesRegion as varchar(10) ) as varchar(1024) )
from CTE inner join
#SalesRegion as SR on SR.ParentSalesRegionId = CTE.SalesRegion
)
select *
from CTE
where Path like '1-3%'
I haven't tried this on a serious dataset, so I'm not sure how it'll perform, but I believe it solves your problem:
WITH SaleLocale_CTE AS (
SELECT SL.SaleLocaleId, SL.SaleLocaleName, SL.AccountingLocationID, SL.LocaleTypeId, SL.ParentSaleLocaleId, CASE WHEN EXISTS (SELECT 1 FROM SaleLocal SL2 WHERE SL2.ParentSaleLocaleId = SL.SaleLocaleID) THEN 1 ELSE 0 END as HasChildren
FROM SaleLocale SL
WHERE SL.Deleted = 0
AND (#SaleLocaleId IS NULL OR SaleLocaleId = #SaleLocaleId)
UNION ALL
SELECT SL.SaleLocaleId, SL.SaleLocaleName, SL.AccountingLocationID, SL.LocaleTypeId, SL.ParentSaleLocaleId, CASE WHEN EXISTS (SELECT 1 FROM SaleLocal SL2 WHERE SL2.ParentSaleLocaleId = SL.SaleLocaleID) THEN 1 ELSE 0 END as HasChildren
FROM SaleLocale SL
INNER JOIN SaleLocale_CTE SLCTE ON SLCTE.SaleLocaleId = SL.ParentSaleLocaleId
WHERE SL.Deleted = 0
)
SELECT *
FROM SaleLocale_CTE
WHERE HasChildren = 0

Problem in displaying hierarchical records (SQL Server 2005) [SET BASED]

I can make query to get the Master -Chile records. But how to display them in hierarchial fashion. Any example will help
I am using SQL Server 2005
There are more elgeant ways, but this quick modification will work:
;With bottomupParentChild AS
(
SELECT EmpId AS parents, ReportsTo,CAST(EmpName AS VARCHAR(1000)) AS [Path], 0 AS [Level], EmpName
FROM #Employees
WHERE EmpId = #childNode
UNION ALL
SELECT i.EmpId AS parents, i.ReportsTo, CAST(gp.[Path] + '/' + i.EmpName AS VARCHAR(1000)) AS [Path],
gp.[Level]+1 AS [Level],i.EmpName
FROM #Employees i
JOIN bottomupParentChild gp ON i.EmpId = gp.ReportsTo
)
, cteRows (TotalRows)
as (select count(*) - 1 from bottomupParentChild)
SELECT ABS(Level - TotalRows), REPLICATE(' ', ABS(Level - TotalRows)) + EmpName as [Hierarchy]
FROM bottomupParentChild
cross join cteRows
ORDER BY [Path] desc;
I added a second cte that gets the count of rows from the first cte, and use it to "flip" the hierarchy.