SQL - Ordering by multiple criteria - sql

I have a table of categories. Each category can either be a root level category (parent is NULL), or have a parent which is a root level category. There can't be more than one level of nesting.
I have the following table structure:
Categories Table Structure http://img16.imageshack.us/img16/8569/categoriesi.png
Is there any way I could use a query which produced the following output:
Free Stuff
Hardware
Movies
CatA
CatB
CatC
Software
Apples
CatD
CatE
So the results are ordered by top level category, then after each top level category, subcategories of that category are listed?
It's not really ordering by Parent or Name, but a combo of the two. I'm using SQL Server.

It seems to me like you are looking to flatten and order your hierarchy, the cheapest way to get this ordering would be to store an additional column in the table that has the full path.
So for example:
Name | Full Path
Free Stuff | Free Stuff
aa2 | Free Stuff - aa2
Once you store the full path, you can order on it.
If you only have a depth of one you can auto generate a string to this effect with a single subquery (and order on it), but this solution does not work that easily when it gets deep.
Another option, is to move this all over to a temp table and calculate the full path there, on demand. But it is fairly expensive.

You could make the table look at itself, ordering by the parent Name then the child Name.
select categories.Name AS DisplayName
from categories LEFT OUTER JOIN
categories AS parentTable ON categories.Parent = parentTable.ID
order by parentTable.Name, DisplayName

Ok, here we go :
with foo as
(
select 1 as id, null as parent, 'CatA' as cat from dual
union select 2, null, 'CatB' from dual
union select 3, null, 'CatC' from dual
union select 4, 1, 'SubCatA_1' from dual
union select 5, 1, 'SubCatA_2' from dual
union select 6, 2, 'SubCatB_1' from dual
union select 7, 2, 'SubCatB_2' from dual
)
select child.cat
from foo parent right outer join foo child on parent.id = child.parent
order by case when parent.id is not null then parent.cat else child.cat end,
case when parent.id is not null then 1 else 0 end
Result :
CatA
SubCatA_1
SubCatA_2
CatB
SubCatB_1
SubCatB_2
CatC
Edit - Solution change inspire from van's order by ! Much simpler that way.

Not entirely sure of your questions but it sounds like PARTITION BY might be useful for you. There's a good introductory post on PARTITION BY here.

Here you have a complete working example using a resursive common table expression.
DECLARE #categories TABLE
(
ID INT NOT NULL,
[Name] VARCHAR(50),
Parent INT NULL
);
INSERT INTO #categories VALUES (4, 'Free Stuff', NULL);
INSERT INTO #categories VALUES (1, 'Hardware', NULL);
INSERT INTO #categories VALUES (3, 'Movies', NULL);
INSERT INTO #categories VALUES (2, 'Software', NULL);
INSERT INTO #categories VALUES (10, 'a', 0);
INSERT INTO #categories VALUES (12, 'apples', 2);
INSERT INTO #categories VALUES (8, 'catD', 2);
INSERT INTO #categories VALUES (9, 'catE', 2);
INSERT INTO #categories VALUES (5, 'catA', 3);
INSERT INTO #categories VALUES (6, 'catB', 3);
INSERT INTO #categories VALUES (7, 'catC', 3);
INSERT INTO #categories VALUES (11, 'aa2', 4);
WITH categories(ID, Name, Parent, HierarchicalName)
AS
(
SELECT
c.ID
, c.[Name]
, c.Parent
, CAST(c.[Name] AS VARCHAR(200)) AS HierarchicalName
FROM #categories c
WHERE c.Parent IS NULL
UNION ALL
SELECT
c.ID
, c.[Name]
, c.Parent
, CAST(pc.HierarchicalName + c.[Name] AS VARCHAR(200))
FROM #categories c
JOIN categories pc ON c.Parent = pc.ID
)
SELECT c.*
FROM categories c
ORDER BY c.HierarchicalName

SELECT
ID,
Name,
Parent,
RIGHT(
'000000000000000' +
CASE WHEN Parent IS NULL
THEN CONVERT(VARCHAR, Id)
ELSE CONVERT(VARCHAR, Parent)
END, 15
)
+ '_' + CASE WHEN Parent IS NULL THEN '0' ELSE '1' END
+ '_' + Name
FROM
categories
ORDER BY
4
The long padding is to account for the fact that SQL Server's INT data type goes from 2,147,483,648 through 2,147,483,647.
You can ORDER BY the expression directly, no need to use ORDER BY 4. It was just to show what it is sorting on.
It is worth noting that this expression cannot use any index. This means sorting a large table will be slow.

Related

Most efficient SQL to get the first row in group by, with an ordinal position column (custom sort order)

Using SQL, how to get first row in group by, with an ordinal position column (custom sort order)
Background:
Using SQLServer 2019, though cross platform ANSI SQL solution would be better. CANNOT use TOP clause (as must run cross db platform).
A parent table (tb_book) has many child categories (tb_linked_categories), sorted using an ordinal_position column
Each row can duplicate the ordinal_position (values only need be >0)
Result: for every parent, get lowest ordinal_position, get first category_id. This becomes the default category. See "Requirements" section
Data and DDL
Created a SQLFiddle to best demonstrate the issue:
SQL Fiddle link
DDL for table tb_linked_categories
CREATE TABLE [tb_linked_categories]
(
[notes] NVARCHAR(200),
[id] BIGINT NOT NULL CONSTRAINT [pk_tb_linked_categories] PRIMARY KEY,
[ordinal_position] INT DEFAULT 1 NOT NULL,
[category_id] BIGINT NOT NULL,
[parent_id] INT NOT NULL
)
Data for table tb_linked_categories
id
ordinal_position
parent_id
category_id
notes
1
2
1
3
null
2
2
1
2
expected, parent_id:1
3
2
1
4
null
4
3
1
1
null
5
4
1
5
null
6
8
2
9
expected, parent_id:2
7
9
2
10
null
8
10
2
7
null
Requirements:
For each book, get the first category_id. This means get the lowest ordinal_position, if there are 3 rows with the same ordinal_position, then get the first (or the lowest numbered category_id is ok too).
Expected result:
parent_id
category_first_item_id
ordinal_position
category_count
1
2
2
3
2
9
8
1
Problems to solve
A few problems to solve:
Can a single SQL Query suffice, with no need for sub-queries or CTE?
What is the most efficient solution query?
Possible solution using a CTE
Here is a working solution using a CTE that is referenced twice
;
WITH
[cte] AS (
SELECT
'group by parent, ordinal position' [cte_dev_message]
, [t].[parent_id]
, [t].[ordinal_position]
, MIN([t].[category_id]) [category_first_item_id]
, COUNT([t].[id]) [category_count]
, STRING_AGG([t].[category_id] ,',') [category_ids_csv]
FROM
[tb_linked_categories] [t]
GROUP BY [t].[parent_id], [t].[ordinal_position] )
SELECT
--'get min ordinal position for each parent' [outer_dev_message]
cte1.[parent_id], [category_first_item_id], [ordinal_position], [category_count]
FROM
[cte] [cte1]
INNER JOIN (
SELECT [parent_id], MIN([ordinal_position]) [min_ordinal_position]
FROM [cte]
GROUP BY [parent_id] ) [mins] ON [cte1].[parent_id] = [mins].[parent_id]
WHERE
[mins].[min_ordinal_position] = [cte1].[ordinal_position]
ORDER BY [cte1].[parent_id];
Full source SQL
Following:
CREATE TABLE [tb_linked_categories]
(
[notes] NVARCHAR(200),
[id] BIGINT NOT NULL CONSTRAINT [pk_zxvshd_example1_with_ordinal_index] PRIMARY KEY,
[ordinal_position] INT DEFAULT 1 NOT NULL,
[category_id] BIGINT NOT NULL,
[parent_id] INT NOT NULL
)
GO
INSERT INTO tb_linked_categories (id, notes, ordinal_position, category_id, parent_id) VALUES (1, null, 2, 3, 1);
INSERT INTO tb_linked_categories (id, notes, ordinal_position, category_id, parent_id) VALUES (2, N'expected first', 2, 2, 1);
INSERT INTO tb_linked_categories (id, notes, ordinal_position, category_id, parent_id) VALUES (3, null, 2, 4, 1);
INSERT INTO tb_linked_categories (id, notes, ordinal_position, category_id, parent_id) VALUES (4, null, 3, 1, 1);
INSERT INTO tb_linked_categories (id, notes, ordinal_position, category_id, parent_id) VALUES (5, null, 4, 5, 1);
INSERT INTO tb_linked_categories (id, notes, ordinal_position, category_id, parent_id) VALUES (6, N'expected first', 8, 9, 2);
INSERT INTO tb_linked_categories (id, notes, ordinal_position, category_id, parent_id) VALUES (7, null, 9, 10, 2);
INSERT INTO tb_linked_categories (id, notes, ordinal_position, category_id, parent_id) VALUES (8, null, 10, 7, 2);
;
WITH
[cte] AS (
SELECT
'group by parent, ordinal position' [cte_dev_message]
, [t].[parent_id]
, [t].[ordinal_position]
, MIN([t].[category_id]) [category_first_item_id]
, COUNT([t].[id]) [category_count]
, STRING_AGG([t].[category_id] ,',') [category_ids_csv]
FROM
[tb_linked_categories] [t]
GROUP BY [t].[parent_id], [t].[ordinal_position] )
SELECT
--'get min ordinal position for each parent' [outer_dev_message]
cte1.[parent_id], [category_first_item_id], [ordinal_position], [category_count]
FROM
[cte] [cte1]
INNER JOIN (
SELECT [parent_id], MIN([ordinal_position]) [min_ordinal_position]
FROM [cte]
GROUP BY [parent_id] ) [mins] ON [cte1].[parent_id] = [mins].[parent_id]
WHERE
[mins].[min_ordinal_position] = [cte1].[ordinal_position]
ORDER BY [cte1].[parent_id];
One approach starts from the tb_book table, and then uses a lateral join to efficiently pick the relevant child. Assuming that column tb_book(id) is referenced in tb_linked_categories(parent_id):
select b.*, c.*
from tb_book b
outer apply (
select top (1) c.*
from tb_linked_categories c
where c.parent_id = b.id
order by c.ordinal_position
) c
Here, the database typically executes the subquery once for each book. With an index on tb_linked_categories(parent_id, ordinal_position), this should be fast, and potentially faster than other solutions (aggregation, filtering with a subquery or with row_number()), which require scanning the whole child table.
Here is an updated version of your fiddle that illustrates the concept.

Get parents based on child id SQL

I have the following scenario in a Microsoft SQL environment:
CREATE TABLE grps
(
[id] varchar(50),
[parentid] varchar(50),
[value] varchar(50)
);
INSERT INTO grps
([id], [parentid], [value])
VALUES
('-5001', '0', null),
('-5002', '-5001', null),
('-5003', '-5002', '50'),
('-5004', '-5003', null),
('-5005', '0', null),
('-5006', '0', null),
('-5007', '0', null),
('-5008', '-5006', null);
I'm trying to get parents based on the id of a child. If the id queried is the last parent then it should only return the last item.
Examples:
If I query: id = '-5004' it should return ('-5004', '-5003', null),
('-5003', '-5002', '50'),
('-5002', '-5001', null),
('-5001', '0', null)
If I query id = '-5007' it should return ('-5007', '0', null)
It would be awesome if it could list the id queried first and the rest in an orderly fashion up the "tree".
I've tried several different approaches with CTE's but with no luck unfortunately. So I'm looking for some help or ideas here.
Thanks in advance.
You were on the right track with CTE's. It can be done by using recursive CTE! Here is how the recursive CTE looks like:
DECLARE #ID varchar(50) = '5004';
WITH CTE AS
(
--This is called once to get the minimum and maximum values
SELECT id, parentid, value
FROM grps
WHERE id= #ID
UNION ALL
--This is called multiple times until the condition is met
SELECT g.id, g.parentid, g.value
FROM CTE c, grps g
WHERE g.id= c.parentid
--If you don't like commas between tables then you can replace the 2nd select
--statement with this:
--SELECT g.id, g.parentid, g.value
--FROM CTE c
--INNER JOIN grps g ON g.id= c.parentid
--This can also be written with CROSS JOINS!
--Even though it looks more like another way of writing INNER JOINs.
--SELECT g.id, g.parentid, g.value
--FROM CTE c
--CROSS JOIN grps g
--WHERE g.id = c.parentid
)
SELECT * FROM CTE
Beware that the maximum recursion is 100 unless you add option (maxrecursion 0) to the end of the last select statement. The 0 means infinite but you can also set it to any value you want.
Enjoy!
I'm trying my best to give hierarchyid some love in the world. First, the setup:
CREATE TABLE grps
(
[id] varchar(50),
[parentid] varchar(50),
[value] varchar(50),
h HIERARCHYID NULL
);
SELECT * FROM grps
INSERT INTO grps
([id], [parentid], [value])
VALUES
('-5001', '0', null),
('-5002', '-5001', null),
('-5003', '-5002', '50'),
('-5004', '-5003', null),
('-5005', '0', null),
('-5006', '0', null),
('-5007', '0', null),
('-5008', '-5006', null);
WITH cte AS (
SELECT id ,
parentid ,
value ,
CAST('/' + id + '/' AS nvarchar(max)) AS h
FROM grps
WHERE parentid = 0
UNION ALL
SELECT child.id ,
child.parentid ,
child.value ,
CAST(parent.h + child.id + '/' AS NVARCHAR(MAX)) AS h
FROM cte AS [parent]
JOIN grps AS [child]
ON child.parentid = parent.id
)
UPDATE g
SET h = c.h
FROM grps AS g
JOIN cte AS c
ON c.id = g.id
All I'm doing here is adding a hierarchyid column to your table definition and calculating the value for it. To determine answer your original problem, now it looks something like this:
SELECT g.id ,
g.parentid ,
g.value ,
g.h.ToString()
FROM dbo.grps AS g
JOIN grps AS c
ON c.h.IsDescendantOf(g.h) = 1
WHERE c.id = '-5004'
To make this more performant, you should index both the id and h columns independently (that is, in separate indexes).
Also, a couple of notes
Having the id columns be varchar when the data looks numeric is fishy at best, but more importantly it's inefficient. If it were me, I'd use an int. But perhaps your actual data is messier (i.e you have ids like 'A1234').
I'd also use NULL instead of 0 for the parentid to represent top-level (i.e. those with no parent) members. But that's more of a personal choice rather than one that has any real performance implications.

T-SQL - Concatenation of names on TWO tables/orphans

I'm prepared to be crucified for asking my first question on SO and what is a potentially duplicate question, but I cannot find it for the life of me.
I have three tables, a product table, a linking table, and a child table with names. Preloaded on SQLFiddle >> if I still have your attention.
CREATE TABLE Product (iProductID int NOT NULL PRIMARY KEY
, sProductName varchar(50) NOT NULL
, iPartGroupID int NOT NULL)
INSERT INTO Product VALUES
(10001, 'Avionic Tackle', '1'),
(10002, 'Eigenspout', '2'),
(10003, 'Impulse Polycatalyst', '3'),
(10004, 'O-webbing', '2'),
(10005, 'Ultraservo', '3'),
(10006, 'Yttrium Coil', '5')
CREATE TABLE PartGroup (iPartGroupID int NOT NULL
, iChildID int NOT NULL)
INSERT INTO PartGroup VALUES
(1, 1),
(2, 2),
(3, 1),
(3, 2),
(3, 3),
(3, 4),
(4, 5),
(4, 6),
(5, 1)
CREATE TABLE PartNames (iChildID int NOT NULL PRIMARY KEY
, sPartNameText varchar(50) NOT NULL)
INSERT INTO PartNames VALUES
(1, 'Bulbcap Lube'),
(2, 'Chromium Deltaquartz'),
(3, 'Dilation Gyrosphere'),
(4, 'Fliphose'),
(5, 'G-tightener Bypass'),
(6, 'Heisenberg Shuttle')
I am trying to find out how to list all the part groups (that may or may not belong to a product), and translate their child names. That is, how do I use only the linking table and child name table to list all the translated elements of the linking table. I am trying to find orphans.
I have two queries:
SELECT P.iPartGroupID
,STUFF(
(SELECT
CONCAT(', ', PN.sPartNameText)
FROM PartGroup PG
INNER JOIN PartNames PN ON PN.iChildID = PG.iChildID
WHERE PG.iPartGroupID = P.iPartGroupID
FOR XML PATH(''), TYPE
).value('.', 'VARCHAR(MAX)')
, 1, 2, ''
) AS [Child Elements]
FROM Product P
GROUP BY P.iPartGroupID
This lists all the part groups that belong to a product, and their child elements by name. iPartGroupID = 4 is not here.
I also have:
SELECT PG.iPartGroupID
,STUFF(
(SELECT
CONCAT(', ', PGList.iChildID)
FROM PartGroup PGList
WHERE PGList.iPartGroupID = PG.iPartGroupID
FOR XML PATH(''), TYPE
).value('.', 'VARCHAR(MAX)')
, 1, 2, ''
) AS [Child Elements]
FROM PartGroup PG
GROUP BY PG.iPartGroupID
This lists all the part groups, and their child elements by code. iPartGroupID = 4 is covered here, but the names aren't translated.
What query can I use to list the orphan part groups (and also the orphan parts):
4 G-tightener Bypass, Heisenberg Shuttle
Ideally it is included in a list of all the other part groups, but if not, I can union the results.
Every other SO question I've looked up uses either 3 tables, or only 1 table, self joining with aliases. Does anyone have any ideas?
No XML in the part names, no particular preference for CONCAT or SELECT '+'.
I would link to other posts, but I can't without points :(
I'm not entirely sure what do you mean, exactly, when you use the word "translate". And your required output seems to contradict your sample data (if I'm not lost something).
Nevertheless, try this query, maybe it's what you need:
select sq.iPartGroupID, cast((
select pn.sPartNameText + ',' as [data()] from #PartNames pn
inner join #PartGroup p on pn.iChildID = p.iChildID
where p.iPartGroupID = sq.iPartGroupID
order by pn.iChildID
for xml path('')
) as varchar(max)) as [GroupList]
from (select distinct pg.iPartGroupID from #PartGroup pg) sq
left join #Product pr on sq.iPartGroupID = pr.iPartGroupID
where pr.iProductID is null;
Following way you can use to get the answer you want
SELECT pg.iPartGroupID,
CASE COUNT(pg.iPartGroupID)
WHEN 1 THEN (
SELECT pn2.sPartNameText
FROM PartNames pn2
WHERE pn2.iChildID = pg.iPartGroupID
)
ELSE (
SELECT CASE ROW_NUMBER() OVER(ORDER BY(SELECT 1))
WHEN 1 THEN ''
ELSE ','
END + pn2.sPartNameText
FROM PartNames pn2
INNER JOIN PartGroup pg2
ON pg2.iChildID = pn2.iChildID
WHERE pg2.iPartGroupID = pg.iPartGroupID
FOR XML PATH('')
)
END
FROM PartGroup pg
GROUP BY
pg.iPartGroupID

SQL Building Pathway using With Union in SQL Server

This is SQL Server Question
I have a set of categories, and their relationship results in nested categories.
I want to build a pathway keeping the relationship and build the SEF urls. Here is what I am looking for:
Category table:
ID, Name
1, Root
2, Cat1
3, Cat2
4, Cat1.1
5, Cat1.2
6, Cat2.1
7, Cat2,2
CategoryChild table: ParentCategoryID, ChildCategoryID
1, 2
1, 3
2, 4
2, 5
3, 6
3, 7
It is an unlimited nested structure. Here is what I am doing (I know its wrong but want something like this):
WITH MenuItems
AS (
SELECT
CAST((ItemPath) AS VARCHAR(1000)) AS 'ItemPath',
CategoryID, Category, ChildID
FROM #Mapping
WHERE CategoryID = 1
UNION ALL
SELECT
CAST((items.ItemPath + '-/' + MenuItem.Category) AS VARCHAR(1000)) AS 'ItemPath',
MenuItem.CategoryID, MenuItem.Category, MenuItem.ChildID
FROM #Mapping AS MenuItem
JOIN MenuItems AS items
ON items.ChildID = MenuItem.CategoryID
)
select * from MenuItems
It gives me something like this:
root--------|1---|root---|2
root--------|1---|root---|3
root/Cat2---|3---|Cat2---|6
root/Cat2---|3---|Cat2---|7
root/Cat1---|2---|Cat1---|4
root/Cat1---|2---|Cat1---|5
So ideally the path should be like this:
root/parent/child (and so on)!
I'm not sure if this is what you're looking for but I've played with recursive cte's in the past and so this might be helpful in building the items path.
NOTE: I've included additional information like the Root Id and Level for each item, so that you can change the ordering of the output.
declare #Category table (Id int, Name varchar(10))
insert into #Category values (1, 'Root'),(2, 'Cat1'), (3, 'Cat2'), (4, 'Cat1.1'), (5, 'Cat1.2'), (6, 'Cat2.1'), (7, 'Cat2.2')
declare #CategoryChild table (ParentCategoryID int, ChildCategoryID int)
insert into #CategoryChild values (1, 2), (1, 3), (2, 4), (2, 5), (3, 6), (3, 7)
;with cte as
(
-- root part
select
ccParent.ChildCategoryID Id,
ccParent.ParentCategoryID ParentId,
c.Name Name,
CAST(parentCategory.Name + '/' + c.Name as varchar(1000)) as Path,
ccParent.ChildCategoryID Root,
0 as Level
from
#CategoryChild ccParent
inner join
#Category c on c.Id = ccParent.ChildCategoryID
inner join
#Category parentCategory on parentCategory.Id = ccParent.ParentCategoryID
where
ccParent.ParentCategoryID = 1
union all
-- recursive part
select
ccChild.ChildCategoryID Id,
ccChild.ParentCategoryID ParentId,
c.Name Name,
CAST((cte.Path + '/' + c.Name) as varchar(1000)) as Path,
cte.Root Root,
cte.Level + 1 as Level
from
#CategoryChild ccChild
inner join
#Category c on c.Id = ccChild.ChildCategoryID
inner join
cte on cte.Id = ccChild.ParentCategoryID
)
select cte.Path
from cte
order by cte.Root, cte.Level
Running the above within my environment gives the following results
Root/Cat1
Root/Cat1/Cat1.1
Root/Cat1/Cat1.2
Root/Cat2
Root/Cat2/Cat2.1
Root/Cat2/Cat2.2
If you were looking to include the Root category in your result set as a standalone item then you can change the first part of the cte to hard code the select of the root item.
;with cte as
(
-- root part
select
c.Id Id,
null ParentId,
c.Name Name,
CAST(c.Name as varchar(1000)) as Path,
c.Id Root,
0 as Level
from
#Category c
where
c.Name = 'Root'
union all
... same as before
Giving the follow
Root
Root/Cat1
Root/Cat1/Cat1.1
Root/Cat1/Cat1.2
Root/Cat2
Root/Cat2/Cat2.1
Root/Cat2/Cat2.2

Sql HierarchyId How do I get the last descendants?

Using t-sql hierarchy Id how do I get all of the rows that have no children (that is the last decendants)?
Say my table is structured like this:
Id,
Name,
HierarchyId
And has these rows:
1, Craig, /
2, Steve, /1/
3, John, /1/1/
4, Sam, /2/
5, Matt, /2/1/
6, Chris, /2/1/1/
What query would give me John and Chris?
Perhaps there are better ways but this seams to do the job.
declare #T table
(
ID int,
Name varchar(10),
HID HierarchyID
)
insert into #T values
(1, 'Craig', '/'),
(2, 'Steve', '/1/'),
(3, 'John', '/1/1/'),
(4, 'Sam', '/2/'),
(5, 'Matt', '/2/1/'),
(6, 'Chris', '/2/1/1/')
select *
from #T
where HID.GetDescendant(null, null) not in (select HID
from #T)
Result:
ID Name HID
----------- ---------- ---------------------
3 John 0x5AC0
6 Chris 0x6AD6
Update 2012-05-22
Query above will fail if node numbers is not in an unbroken sequence. Here is another version that should take care of that.
declare #T table
(
ID int,
Name varchar(10),
HID HierarchyID
)
insert into #T values
(1, 'Craig', '/'),
(2, 'Steve', '/1/'),
(3, 'John', '/1/1/'),
(4, 'Sam', '/2/'),
(5, 'Matt', '/2/1/'),
(6, 'Chris', '/2/1/2/') -- HID for this row is changed compared to above query
select *
from #T
where HID not in (select HID.GetAncestor(1)
from #T
where HID.GetAncestor(1) is not null)
Since you only need leafs and you don't need to get them from a specific ancestor, a simple non-recursive query like this should do the job:
SELECT * FROM YOUR_TABLE PARENT
WHERE
NOT EXISTS (
SELECT * FROM YOUR_TABLE CHILD
WHERE CHILD.HierarchyId = PARENT.Id
)
In plain English: select every row without a child row.
This assumes your HierarchyId is a FOREIGN KEY towards the Id, not the whole "path" as presented in your example. If it isn't, this is probably the first thing you should fix in your database model.
--- EDIT ---
OK, here is the MS SQL Server-specific query that actually works:
SELECT * FROM YOUR_TABLE PARENT
WHERE
NOT EXISTS (
SELECT * FROM YOUR_TABLE CHILD
WHERE
CHILD.Id <> PARENT.Id
AND CHILD.HierarchyId.IsDescendantOf(PARENT.HierarchyId) = 1
)
Note that the IsDescendantOf considers any row a descendant of itself, so we also need the CHILD.Id <> PARENT.Id in the condition.
Hi I use this one and works perfectly for me.
CREATE TABLE [dbo].[Test]([Id] [hierarchyid] NOT NULL, [Name] [nvarchar](50) NULL)
DECLARE #Parent AS HierarchyID = CAST('/2/1/' AS HierarchyID) -- Get Current Parent
DECLARE #Last AS HierarchyID
SELECT #Last = MAX(Id) FROM Test WHERE Id.GetAncestor(1) = #Parent -- Find Last Id for this Parent
INSERT INTO Test(Id,Name) VALUES(#Parent.GetDescendant(#Last, NULL),'Sydney') -- Insert after Last Id