Sorting tree with other column in SQL Server 2008 - sql

I have a table which implements a tree using hierarchyid column
Sample data:
People \
Girls \1\
Zoey \1\1\
Kate \1\2\
Monica \1\3\
Boys \2\
Mark \2\1\
David \2\2\
This is the order using hierarchyid column as sort column
I would like to sort data using hierarchyid but also using name so it would look like this:
People \
Boys \2\
David \2\2\
Mark \2\1\
Girls \1\
Kate \1\2\
Monica \1\3\
Zoey \1\1\
Is there a simple solution to do this?
Can it be done with a single SQL query?

Rewrite your query as a recursive CTE:
DECLARE #table TABLE (id INT NOT NULL PRIMARY KEY, name NVARCHAR(4000) NOT NULL, path HIERARCHYID)
INSERT
INTO #table
VALUES
(1, 'People', '/'),
(2, 'Girls', '/1/'),
(3, 'Boys', '/2/'),
(4, 'Zoey', '/1/1/'),
(5, 'Kate', '/1/2/'),
(6, 'Monica', '/1/3/'),
(7, 'Mark', '/2/1/'),
(8, 'David', '/2/2/')
;WITH q AS
(
SELECT *, HIERARCHYID::Parse('/') AS newpath
FROM #table
WHERE path = HIERARCHYID::GetRoot()
UNION ALL
SELECT t.*, HIERARCHYID::Parse(q.newpath.ToString() + CAST(ROW_NUMBER() OVER (ORDER BY t.name) AS NVARCHAR(MAX)) + '/')
FROM q
JOIN #table t
ON t.path.IsDescendantOf(q.path) = 1
AND t.path.GetLevel() = q.path.GetLevel() + 1
)
SELECT *
FROM q
ORDER BY
newpath

Related

Using GROUP BY with FOR XML PATH in SQL Server 2016

I am trying to
group by ID and
aggregate multiple comments into a single row
Right now, I can do the no. 2 part for a single ID (ID = 1006), but I would like to aggregate comments for all IDs. I am struggling where and how to add "group by" clause in my query.
Here is the query:
create table Comments (ID int, Comment nvarchar(150), RegionCode int)
insert into Comments values (1006, 'I', 1)
, (1006, 'am', 1)
, (1006, 'good', 1)
, (1006, 'bad', 2)
, (2, 'You', 1)
, (2, 'are', 1)
, (2, 'awesome', 1)
SELECT
SUBSTRING((SELECT Comment
FROM Comments
WHERE ID = 1006 AND RegionCode != 2
FOR XML PATH('')), 1, 999999) AS Comment_Agg
My desired result looks something like this:
FYI, I am using FOR XML PATH here to aggregate multiple comments into a single row because STRING_AGG function is not supported in my version - SQL Server 2016 (v13.x).
Please try the following solution.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID int, Comment nvarchar(150));
INSERT INTO #tbl VALUES
(1006, 'I'),
(1006, 'am'),
(1006, 'good'),
(2, 'You'),
(2, 'are'),
(2, 'awesome');
-- DDL and sample data population, end
DECLARE #separator CHAR(1) = SPACE(1);
SELECT p.ID
, STUFF((SELECT #separator + Comment
FROM #tbl AS c
WHERE c.ID = p.ID
FOR XML PATH('')), 1, LEN(#separator), '') AS Result
FROM #tbl AS p
GROUP BY p.ID
ORDER BY p.ID;
Output
+------+-----------------+
| ID | Result |
+------+-----------------+
| 2 | You are awesome |
| 1006 | I am good |
+------+-----------------+

I need sql query for order by values of given parameters?

I need a query for order by given parameters
My table like this
ID Name Type
1 Argentine Standard
2 Spain Critical
3 France Critical
4 Germany Standard
5 Brazil Standard
6 Italy Standard
I am sending the parameter as Germany,Spain,Brazil,Argentine my output should be
ID Name Type
4 Germany Standard
2 Spain Critical
5 Brazil Standard
1 Argentine Standard
At present i used in query and i got the output in order by id that means it shows the result as in database order but i need to order by query parameter order?
can anyone help me for query?
according to your output I can suggest you this query. You can also alter order by clause as per your requirement:
select id,name,type from stack where name in('Germany','Spain','Brazil','Argentine') order by type,name,id desc
may be you are sending Comma Separated Input then we can convert them into rows and join with the table Data to get Required Output.
declare #t varchar(50) = ' Germany,Spain,Brazil,Argentine'
Declare #tt table (ID INT IDENTITY(1,1),val varchar(10))
insert into #tt (val)
SELECT
LTRIM(RTRIM(m.n.value('.[1]','varchar(8000)'))) AS Certs
FROM
(
SELECT CAST('<XMLRoot><RowData>' + REPLACE(#t,',','</RowData><RowData>') + '</RowData></XMLRoot>' AS XML) AS x
)t
CROSS APPLY x.nodes('/XMLRoot/RowData')m(n)
DECLARE #Table1 TABLE
(ID int, Name varchar(9), Type varchar(8))
;
INSERT INTO #Table1
(ID, Name, Type)
VALUES
(1, 'Argentine', 'Standard'),
(2, 'Spain', 'Critical'),
(3, 'France', 'Critical'),
(4, 'Germany', 'Standard'),
(5, 'Brazil', 'Standard'),
(6, 'Italy', 'Standard')
;
select T.ID,TT.val,T.Type from #Table1 T
INNER JOIN #tt TT
ON T.Name = TT.val
ORDER BY TT.ID

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

Help with recursive CTE query joining to a second table

My objective is to recurse through table tbl and while recursing through that table select a country abbreviation (if it exists) from another table tbl2 and append those results together which are included in the final output.
The example I'll use will come from this post
tbl2 has a Foreign Key 'tbl_id' to tbl and looks like this
INSERT INTO #tbl2( Id, Abbreviation, tbl_id )
VALUES
(100, 'EU', 1)
,(101, 'AS', 2)
,(102, 'DE', 3)
,(103, 'CN', 5)
*Note: not all the countries have abbreviations.
The trick is, I want all the countries in Asia to at least show the abbreviation of Asia which is 'AS' even if a country doesn't have an abbreviation (like India for example). If the country does have an abbreviation the result needs to look like this: China:CN,AS
I've got it partly working using a subquery, but India always returns NULL for the abbreviation. It's acting like if there isn't a full recursive path back to the abbreviation, then it returns null. Maybe the solution is to use a left outer join on the abbreviation table? I've tried for hours many different variations and the subquery is as close as I can get.
WITH abcd
AS (
-- anchor
SELECT id, [Name], ParentID,
CAST(([Name]) AS VARCHAR(1000)) AS "Path"
FROM #tbl
WHERE ParentId IS NULL
UNION ALL
--recursive member
SELECT t.id, t.[Name], t.ParentID,
CAST((a.path + '/' + t.Name + ':' +
(
select t2.abbreviation + ','
from #tbl2
where t.id = t2.id
)) AS VARCHAR(1000)) AS "Path"
FROM #tbl AS t
JOIN abcd AS a
ON t.ParentId = a.id
)
SELECT * FROM abcd
btw, I'm using sql server 2005 if that matters
Try this example, which will give you the output (1 sample row)
id Name ParentID Path abbreviation (No column name)
5 China 2 Asia/China CN,AS Asia/China:CN,AS
The TSQL being
DECLARE #tbl TABLE (
Id INT
,[Name] VARCHAR(20)
,ParentId INT
)
INSERT INTO #tbl( Id, Name, ParentId )
VALUES
(1, 'Europe', NULL)
,(2, 'Asia', NULL)
,(3, 'Germany', 1)
,(4, 'UK', 1)
,(5, 'China', 2)
,(6, 'India', 2)
,(7, 'Scotland', 4)
,(8, 'Edinburgh', 7)
,(9, 'Leith', 8)
;
DECLARE #tbl2 table (id int, abbreviation varchar(10), tbl_id int)
INSERT INTO #tbl2( Id, Abbreviation, tbl_id )
VALUES
(100, 'EU', 1)
,(101, 'AS', 2)
,(102, 'DE', 3)
,(103, 'CN', 5)
;WITH abbr AS (
SELECT a.*, isnull(b.abbreviation,'') abbreviation
FROM #tbl a
left join #tbl2 b on a.Id = b.tbl_id
), abcd AS (
-- anchor
SELECT id, [Name], ParentID,
CAST(([Name]) AS VARCHAR(1000)) [Path],
cast(abbreviation as varchar(max)) abbreviation
FROM abbr
WHERE ParentId IS NULL
UNION ALL
--recursive member
SELECT t.id, t.[Name], t.ParentID,
CAST((a.path + '/' + t.Name) AS VARCHAR(1000)) [Path],
isnull(nullif(t.abbreviation,'')+',', '') + a.abbreviation
FROM abbr AS t
JOIN abcd AS a
ON t.ParentId = a.id
)
SELECT *, [Path] + ':' + abbreviation
FROM abcd

SQL "tree-like" query - most parent group

I'm having some trouble doing a "tree-like" query (what do we call this?) in SQL.
Take a look at my diagram below (table and column names are in danish - sorry about that):
DB diagram http://img197.imageshack.us/img197/8721/44060572.jpg
Using MSSQL Server 2005, the goal is to find the most parent group (Gruppe), for each customer (Kunde).
Each group can have many parent groups and many child groups.
And, I would also like to know how to display the tree like this:
Customer 1
- Parent group 1
- Child group 1
- ChildChild group n
- Child group n
- Parent group n
- ...
- ...
Customer n
- ...
Another question:
How does the query look to get ALL the groups for all the customers? Parent and child groups.
You can use CTE's to construct "the full path" column on the fly
--DROP TABLE Gruppe, Kunde, Gruppe_Gruppe, Kunde_Gruppe
CREATE TABLE Gruppe (
Id INT PRIMARY KEY
, Name VARCHAR(100)
)
CREATE TABLE Kunde (
Id INT PRIMARY KEY
, Name VARCHAR(100)
)
CREATE TABLE Gruppe_Gruppe (
ParentGruppeId INT
, ChildGruppeId INT
)
CREATE TABLE Kunde_Gruppe (
KundeId INT
, GruppeId INT
)
INSERT Gruppe
VALUES (1, 'Group 1'), (2, 'Group 2'), (3, 'Group 3')
, (4, 'Sub-group A'), (5, 'Sub-group B'), (6, 'Sub-group C'), (7, 'Sub-group D')
INSERT Kunde
VALUES (1, 'Kunde 1'), (2, 'Kunde 2'), (3, 'Kunde 3')
INSERT Gruppe_Gruppe
VALUES (1, 4), (1, 5), (1, 7)
, (2, 6), (2, 7)
, (6, 1)
INSERT Kunde_Gruppe
VALUES (1, 1), (1, 2)
, (2, 3), (2, 4)
;WITH CTE
AS (
SELECT CONVERT(VARCHAR(1000), REPLACE(CONVERT(CHAR(5), k.Id), ' ', 'K')) AS TheKey
, k.Name AS Name
FROM Kunde k
UNION ALL
SELECT CONVERT(VARCHAR(1000), REPLACE(CONVERT(CHAR(5), x.KundeId), ' ', 'K')
+ REPLACE(CONVERT(CHAR(5), g.Id), ' ', 'G')) AS TheKey
, g.Name
FROM Gruppe g
JOIN Kunde_Gruppe x
ON g.Id = x.GruppeId
UNION ALL
SELECT CONVERT(VARCHAR(1000), p.TheKey + REPLACE(CONVERT(CHAR(5), g.Id), ' ', 'G')) AS TheKey
, g.Name
FROM Gruppe g
JOIN Gruppe_Gruppe x
ON g.Id = x.ChildGruppeId
JOIN CTE p
ON REPLACE(CONVERT(CHAR(5), x.ParentGruppeId), ' ', 'G') = RIGHT(p.TheKey, 5)
WHERE LEN(p.TheKey) < 32 * 5
)
SELECT *
, LEN(TheKey) / 5 AS Level
FROM CTE c
ORDER BY c.TheKey
Performance might be sub-optimal if you have lots of reads vs rare modifications.
I just can't say it better than Joe Celko. The problem is usually that the models built doesn't lend themselves well to build hierarchies, and that those models have to take in consideration the characteristics of your hierarchy. Is it too deep? Is it too wide? Is it narrow and shallow?
One key to success on wide and shallow trees is to have the full path in the hierarchy in a column, like Celko mentions in the first link.
http://onlamp.com/pub/a/onlamp/2004/08/05/hierarchical_sql.html
http://www.dbmsmag.com/9603d06.html and http://www.dbmsmag.com/9604d06.html
http://www.ibase.ru/devinfo/DBMSTrees/sqltrees.html
I came up with a solution that solves the problem of listing ALL the groups for each customer. Parent and child groups.
What do you think?
WITH GroupTree
AS
(
SELECT kg.KundeId, g.Id GruppeId
FROM ActiveDirectory.Gruppe g
INNER JOIN ActiveDirectory.Kunde_Gruppe kg ON g.Id = kg.GruppeId
AND (EXISTS (SELECT * FROM ActiveDirectory.Gruppe_Gruppe WHERE ParentGruppeId = g.Id)
OR NOT EXISTS (SELECT * FROM ActiveDirectory.Gruppe_Gruppe WHERE ParentGruppeId = g.Id))
UNION ALL
SELECT GroupTree.KundeId, gg.ChildGruppeId
FROM ActiveDirectory.Gruppe_Gruppe gg
INNER JOIN GroupTree ON gg.ParentGruppeId = GroupTree.GruppeId
)
SELECT KundeId, GruppeId
FROM GroupTree
OPTION (MAXRECURSION 32767)
How about something like this:
DECLARE #Customer TABLE(
CustomerID INT IDENTITY(1,1),
CustomerName VARCHAR(MAX)
)
INSERT INTO #Customer SELECT 'Customer1'
INSERT INTO #Customer SELECT 'Customer2'
INSERT INTO #Customer SELECT 'Customer3'
DECLARE #CustomerTreeStructure TABLE(
CustomerID INT,
TreeItemID INT
)
INSERT INTO #CustomerTreeStructure (CustomerID,TreeItemID) SELECT 1, 1
INSERT INTO #CustomerTreeStructure (CustomerID,TreeItemID) SELECT 2, 12
INSERT INTO #CustomerTreeStructure (CustomerID,TreeItemID) SELECT 3, 1
INSERT INTO #CustomerTreeStructure (CustomerID,TreeItemID) SELECT 3, 12
DECLARE #TreeStructure TABLE(
TreeItemID INT IDENTITY(1,1),
TreeItemName VARCHAR(MAX),
TreeParentID INT
)
INSERT INTO #TreeStructure (TreeItemName,TreeParentID) SELECT '001', NULL
INSERT INTO #TreeStructure (TreeItemName,TreeParentID) SELECT '001.001', 1
INSERT INTO #TreeStructure (TreeItemName,TreeParentID) SELECT '001.001.001', 2
INSERT INTO #TreeStructure (TreeItemName,TreeParentID) SELECT '001.001.002', 2
INSERT INTO #TreeStructure (TreeItemName,TreeParentID) SELECT '001.001.003', 2
INSERT INTO #TreeStructure (TreeItemName,TreeParentID) SELECT '001.002', 1
INSERT INTO #TreeStructure (TreeItemName,TreeParentID) SELECT '001.003', 1
INSERT INTO #TreeStructure (TreeItemName,TreeParentID) SELECT '001.003.001', 7
INSERT INTO #TreeStructure (TreeItemName,TreeParentID) SELECT '001.001.002.001', 4
INSERT INTO #TreeStructure (TreeItemName,TreeParentID) SELECT '001.001.002.002', 4
INSERT INTO #TreeStructure (TreeItemName,TreeParentID) SELECT '001.001.002.003', 4
INSERT INTO #TreeStructure (TreeItemName,TreeParentID) SELECT '002', NULL
INSERT INTO #TreeStructure (TreeItemName,TreeParentID) SELECT '002.001', 12
INSERT INTO #TreeStructure (TreeItemName,TreeParentID) SELECT '002.001.001', 13
INSERT INTO #TreeStructure (TreeItemName,TreeParentID) SELECT '002.001.002', 13
;WITH Structure AS (
SELECT TreeItemID,
TreeItemName,
TreeParentID,
REPLICATE('0',5 - LEN(CAST(TreeItemID AS VARCHAR(MAX)))) + CAST(TreeItemID AS VARCHAR(MAX)) + '\\' TreePath
FROM #TreeStructure ts
WHERE ts.TreeParentID IS NULL
UNION ALL
SELECT ts.*,
s.TreePath + REPLICATE('0',5 - LEN(CAST(ts.TreeItemID AS VARCHAR(5)))) + CAST(ts.TreeItemID AS VARCHAR(5)) + '\\' TreePath
FROM #TreeStructure ts INNER JOIN
Structure s ON ts.TreeParentID = s.TreeItemID
)
SELECT c.CustomerName,
Children.TreeItemName,
Children.TreePath
FROM #Customer c INNER JOIN
#CustomerTreeStructure cts ON c.CustomerID = cts.CustomerID INNER JOIN
Structure s ON cts.TreeItemID = s.TreeItemID INNER JOIN
(
SELECT *
FROM Structure
) Children ON Children.TreePath LIKE s.TreePath +'%'
ORDER BY 1,3
OPTION (MAXRECURSION 0)
In T-SQL, you can write a while loop. Untested:
#group = <starting group>
WHILE (EXISTS(SELECT * FROM Gruppe_Gruppe WHERE ChildGruppeId=#group))
BEGIN
SELECT #group=ParentGruppeId FROM Gruppe_Gruppe WHERE ChildGruppeId=#group
END
We use SQL Server 2000 and there is an example of expanding hierarchies using a stack in the SQL Books Online, I have written a number of variants for our ERP system
http://support.microsoft.com/kb/248915
I gather that there is a Native method using CTE within SQL 2005 but I have not used it myself