SQL Server to combine multi rows into single row where col0=col1 - sql-server

my table :
previousid|CurrentID|Data
| 1 | 2 | Data 1
| 2 | 3 | Data 2
| 3 | 4 | Data 3
| 4 | 5 | Data 4
Result i look for :
Select .... where PreviousID=1 :
|Col0|Col1|Col2 |Col3|Col 4|Col5| Col6 | Col7 | Col8
|1 |2 |Data 1|3 |Data 2| 4 | data 3| 5 | data 4
Select .....where PreviousID=2
|Col0|Col1|Col2 |Col3|Col 4|Col5| Col6 |
|2 |3 |Data 2|4 |Data 3| 5 | data 4|
i tried to create some SQL server query to get result with no luck, please help me guys

We can do this in a few steps:
Declare and set a variable to use for our root node, and create a temporary table to store the results from our recusive query:
Insert the results from the recusive query into the temporary table
Generate and execute dynamic sql to pivot() the temporary table.
(alternate) Generate and execute dynamic sql to use conditional aggregation instead of pivot():
rextester demo: http://rextester.com/MRFZC75180
test setup:
create table t (PreviousID int, CurrentID int, Data varchar(32));
insert into t values
(1,2,'Data 1'),(2,3,'Data 2'),(3,4,'Data 3'),(4,5,'Data 4');
Declare and set a variable to use for our root node, and create a temporary table to store the results from our recusive query:
declare #PreviousId int = 2;
create table #temp (PreviousID int
, Level int
, Col varchar(32)
, Value varchar(32)
, rn int
);
Insert the results from the recusive query into the temporary table
;with cte as (
select PreviousID, CurrentID, Data, level = 0
from t
where previousId = #PreviousId
union all
select c.PreviousID, c.CurrentID, c.Data, level = p.level +1
from t c
inner join cte as p
on c.PreviousID = p.CurrentID
)
insert into #temp
select p.PreviousId, t.level, x.col, x.value
, rn = row_number() over (order by t.level, x.col)
from cte t
cross apply (
select top 1
PreviousId
from cte i
order by level
) as p (PreviousId)
cross apply (
values ('CurrentId',convert(varchar(32),CurrentId)),('Data',Data)
) as x (col,value);
results so far:
+------------+-------+-----------+--------+----+
| PreviousID | Level | Col | Value | rn |
+------------+-------+-----------+--------+----+
| 2 | 0 | CurrentId | 3 | 1 |
| 2 | 0 | Data | Data 2 | 2 |
| 2 | 1 | CurrentId | 4 | 3 |
| 2 | 1 | Data | Data 3 | 4 |
| 2 | 2 | CurrentId | 5 | 5 |
| 2 | 2 | Data | Data 4 | 6 |
+------------+-------+-----------+--------+----+
Generate and execute dynamic sql to pivot() the temporary table.
/* pivot */
declare #cols nvarchar(max);
declare #sql nvarchar(max);
select #cols = stuff((
select
', Col'+convert(nvarchar(10),rn)
from #temp
order by 1
for xml path (''), type).value('.','nvarchar(max)')
,1,1,'')
select #sql ='
select Col0=PreviousID, ' + #cols +'
from (
select PreviousID, Value, rn= ''Col''+convert(nvarchar(10),rn)
from #temp
) as t
pivot (max([Value]) for [rn] in (' + #cols +')) p'
select #sql as CodeGenerated;
exec sp_executesql #sql;
code generated:
select Col0=PreviousID, Col1, Col2, Col3, Col4, Col5, Col6
from (
select PreviousID, Value, rn= 'Col'+convert(nvarchar(10),rn)
from #temp
) as t
pivot (max([Value]) for [rn] in ( Col1, Col2, Col3, Col4, Col5, Col6)) p
returns:
+------+------+--------+------+--------+------+--------+
| Col0 | Col1 | Col2 | Col3 | Col4 | Col5 | Col6 |
+------+------+--------+------+--------+------+--------+
| 2 | 3 | Data 2 | 4 | Data 3 | 5 | Data 4 |
+------+------+--------+------+--------+------+--------+
(alternate) Generate and execute dynamic sql to use conditional aggregation instead of pivot():
/* conditional aggregation */
--declare #cols nvarchar(max);
--declare #sql nvarchar(max);
select #cols = stuff((
select
char(10)+' , '
+ 'Col'+convert(nvarchar(10),rn)
+' = max(case when rn = '+convert(nvarchar(10),rn)+' then Value end)'
from #temp
order by 1
for xml path (''), type).value('.','nvarchar(max)')
,1,0,'')
select #sql ='
select Col0 = PreviousID'+#cols+'
from #temp
group by PreviousID'
select #sql as CodeGenerated;
exec sp_executesql #sql;
code generated:
select Col0 = PreviousID
, Col1 = max(case when rn = 1 then Value end)
, Col2 = max(case when rn = 2 then Value end)
, Col3 = max(case when rn = 3 then Value end)
, Col4 = max(case when rn = 4 then Value end)
, Col5 = max(case when rn = 5 then Value end)
, Col6 = max(case when rn = 6 then Value end)
from #temp
group by PreviousID
returns:
+------+------+--------+------+--------+------+--------+
| Col0 | Col1 | Col2 | Col3 | Col4 | Col5 | Col6 |
+------+------+--------+------+--------+------+--------+
| 2 | 3 | Data 2 | 4 | Data 3 | 5 | Data 4 |
+------+------+--------+------+--------+------+--------+

I think try with concatenating column and give them alias.
For better understanding go through this link https://www.mssqltips.com/sqlservertip/2985/concatenate-sql-server-columns-into-a-string-with-concat/

Related

How to append metadata to a grouped selection by primary key

I have a list of favorites like:
Sample Data
| key | item_id | list_name | customer_id |meta |
|-----|---------|-----------|--------------|---------------|
| 1 | A-11 | aa11 | 001 | unique-data-1 |
| 2 | A-11 | bb22 | 001 | unique-data-2 |
| 3 | A-26 | cc33 | 001 | unique-data-3 |
| 4 | A-28 | aa11 | 002 | unique-data-4 |
| 5 | J-52 | aa11 | 001 | unique-data-5 |
| 6 | X-53 | aa11 | 001 | unique-data-6 |
Desired Output
for #item_id nvarchar(20) = 'A-11'
| key | isFavorited | list_name | meta |
|-----|-------------|-----------|---------------|
| 1 | Y | aa11 | unique-data-1 |
| 2 | Y | bb22 | unique-data-2 |
| 3 | N | cc33 | unique-data-3 |
And would like to return a selection of all available lists, as well as whether or not a particular item is part of that list, with its meta data.
declare #item_id nvarchar(20) = 'A-11'
declare #customer_id nvarchar(20) = 001
select
[key],
[isFavorited] = max(case when [item_id] = #item_id then 'Y' else 'N' end)
[list_name]
[meta]
from favorites
where customer_id = #customer_id
group by [list_name], [key], [meta]
Issues when trying various methods:
The issue I'm having is that since the meta is unique the group by destroys the uniqueness of the select
A cross apply like the following doesn't apply the correct meta based on a matching key.
cross apply (
select top 1
[meta]
from favorites
where customer_id = #customer_id
)
When selecting by row number, the actual key to join back to is lost, so I'm unable to join the meta.
"noRow" = row_number() over(order by h_po_no asc)
I'd like to
Pass in an item_id and customer_id
Return all lists for that customer
Get favorite status for each list of passed in item_id
An item is flagged favorite if it matches both list_name and item_id for a given customer_id
Get row primary key and meta data
How can I return a distinct selection of list_name, isFavorite status, key, and it's meta?
To return the desired output you don't need any aggregation at all. A simple case expression and a where clause will accomplish this.
declare #Favorites table
(
MyKey int
, item_id varchar(10)
, list_name varchar(10)
, customer_id varchar(10)
, meta varchar(20)
)
insert #Favorites values
(1, 'A-1', 'list-1', '001', 'unique-data-1')
, (2, 'A-1', 'list-2', '001', 'unique-data-2')
, (3, 'A-2', 'list-3', '001', 'unique-data-3')
, (4, 'A-2', 'list-1', '002', 'unique-data-1')
select *
from #Favorites
declare #item_id nvarchar(20) = 'A-1'
, #customer_id nvarchar(20) = '001'
select f.MyKey
, isFavorited = case when f.item_id = #item_id then 'Y' else 'N' end
, listName = f.list_name
, f.meta
from favorites f
where f.customer_id = #customer_id
order by f.MyKey
Test Parameters
declare #item_id nvarchar(20) = 'A-11'
declare #customer_id nvarchar(20) = 001
Solution
drop table if exists #tmp
;with cte as (
select
[key],
[list_name],
[rn] = row_number() over (partition by list_name order by list_name desc)
from favorites
where customer_id = #customer_id
group by [list_name], [key], [meta]
)
select *
into #tmp
from cte
where [rn] = 1
select
[i],
[json],
#t.[tmp]
from #tmp #t
inner join (
select
[isFavorited] = max(case when [item_id] = #item_id then 'Y' else 'N' end)
[list_name]
from favorites
where customer_id = #customer_id
group by [list_name]
) j
on j.list_name = #t.list_name

SQL Server split field and create addition rows with values from main row

I have a table with rows and in one field there are values like this A,B,C
Table 'Mytable':
|ID | Date | MyValue | SplitID |
|1 | 2019-12-17 | A | |
|2 | 2019-12-15 | A,B | |
|3 | 2019-12-16 | B,C | |
Result should be:
|1 | 2019-12-17 | A | 1 |
|2 | 2019-12-15 | A | 2 |
|4 | 2019-12-15 | B | 2 |
|3 | 2019-12-16 | B | 3 |
|5 | 2019-12-16 | C | 3 |
(Sorry, I could not find HOW to format a table in the Stackoverflow help)
I tried a inline table function which splits the Field Myvalue into more lines but could not pass my rows with
charindex(',',[MyValue])>0
from MyTable as input lines.
The code is this:
ALTER function [dbo].[fncSplitString](#input Varchar(max), #Splitter Varchar(99), #ID int)
returns table as
Return
with tmp (DataItem, ix, ID) as
( select LTRIM(#input) , CHARINDEX('',#Input), #ID --Recu. start, ignored val to get the types right
union all
select LTRIM(Substring(#input, ix+1,ix2-ix-1)), ix2, #ID
from (Select *, CHARINDEX(#Splitter,#Input+#Splitter,ix+1) ix2 from tmp) x where ix2<>0
) select DataItem,ID from tmp where ix<>0
Thanks for help
Michael
You can try the following query.
Create table #Temp(
Id int,
DateField Date,
MyValue Varchar(10),
SplitID int
)
CREATE FUNCTION [dbo].[SplitPra] (#Value VARCHAR(MAX), #delimiter CHAR)
RETURNS #DataResult TABLE([Position] TINYINT IDENTITY(1,1),[Value] NVARCHAR(128))
AS
BEGIN
DECLARE #XML xml = N'<r><![CDATA[' + REPLACE(#Value, #delimiter, ']]></r><r><![CDATA[') + ']]></r>'
INSERT INTO #DataResult ([Value])
SELECT RTRIM(LTRIM(T.c.value('.', 'NVARCHAR(128)')))
FROM #xml.nodes('//r') T(c)
RETURN
END
insert into #Temp Values(1, '2019-12-17', 'A', NULL),(2, '2019-12-15', 'A,B', NULL), (3, '2019-12-16', 'B,C', NULL)
Select
#Temp.Id, DateField, b.Value as MyValue, b.Id as SplitValue
from #Temp inner join (
select
Id, f.*
from
#Temp u
cross apply [dbo].[SplitPra](u.MyValue, ',') f
)b on #Temp.Id = b.Id
Drop table #Temp
This will give an output as shown below.
Id DateField MyValue SplitValue
---------------------------------
1 2019-12-17 A 1
2 2019-12-15 A 2
2 2019-12-15 B 2
3 2019-12-16 B 3
3 2019-12-16 C 3
You can find the live demo here.
I found this solution, i hope it will work for you. But i didn't use your function to solve this problem. Instead of that, i used cross apply function.You can find the query below:
-- Creating Test Table
CREATE TABLE #Test
(
ID int,
Date date,
MyValue nvarchar(max),
SplitID int
);
GO
-- Inserting data into test table
INSERT INTO #Test VALUES (1, '2019-12-17', 'A', NULL);
INSERT INTO #Test VALUES (2, '2019-12-15', 'A,B', NULL);
INSERT INTO #Test VALUES (3, '2019-12-16', 'B,C', NULL);
GO
-- Select query
SELECT
*,
(SELECT ID FROM test t1 WHERE t.Date = t1.date) AS SplitID
FROM
(
SELECT
ROW_NUMBER() OVER (ORDER BY ID) AS ID,
Date,
substring(A.value,1,
CASE WHEN charindex(',',rtrim(ltrim(A.value))) = 0 then LEN(A.value)
ELSE charindex(',',rtrim(ltrim(A.value))) -1 end) as MyValue
FROM Test
CROSS APPLY string_split (MyValue, ',') A) AS T
ORDER BY MyValue ASC;
And the result must be like that:
ID Date MyValue SplitID
1 2019-12-17 A 1
2 2019-12-15 A 2
3 2019-12-15 B 2
4 2019-12-16 B 3
5 2019-12-16 C 3

how to copy TreeView's rows from the same table and update with different ID column and copy the parent_id

I want copy the table and put different value on column Type= B and auto_increment id and copy the parent id
Table = Menu
Id | parent_id | order | section | name | url | type
100 | NULL | 7 | web | Tasks | ~/en/Tasks | A
102 | 100 | 1 | web | Pages | ~/en/Pages | A
103 | 100 | 4 | web | Category | ~/en/Category | A
104 | NULL | 3 | web | DLM | ~/en/DLM | A
105 | 104 | 6 | web | ONS | ~/en/ONS | A
106 | 104 | 2 | web | HBO | ~/en/HBO | A
107 | NULL | 7 | web | Tasks | ~/en/Tasks | B
108 | 107 | 1 | web | Pages | ~/en/Pages | B
109 | 107 | 4 | web | Category | ~/en/Category | B
110 | NULL | 3 | web | DLM | ~/en/DLM | B
111 | 110 | 6 | web | ONS | ~/en/ONS | B
112 | 110 | 2 | web | HBO | ~/en/HBO | B
This probably isn't the most efficient, but it gets the job done. It assumes that name is unique. I left out columns unnecessary to the example. Also, you can't put a variable into the identity clause, so that needs to be wrapped in an EXEC
IF OBJECT_ID (N'paths', N'U') IS NOT NULL
DROP TABLE paths
IF OBJECT_ID (N'new_paths', N'U') IS NOT NULL
DROP TABLE new_paths
CREATE TABLE paths (
id INT,
parent_id INT,
name NVARCHAR(20)
)
INSERT INTO dbo.paths
(id,parent_id,name)
VALUES
(100, NULL, 'Tasks'),
(102, 100, 'Pages'),
(103, 100, 'Category'),
(104, NULL, 'DLM'),
(105, 104, 'ONS'),
(106, 104, 'HBO')
DECLARE #start_value INT
SET #start_value = (SELECT MAX(id) FROM paths) + 1
DECLARE #sql nvarchar(1000)
SET #sql = N'
CREATE TABLE new_paths (
id INT IDENTITY(' + CAST(#start_value AS nvarchar) + ',1),
parent_id INT,
name NVARCHAR(20)
)
'
EXEC sp_executesql #stmt = #sql
INSERT INTO new_paths (parent_id,name)
SELECT Parent_id, name FROM dbo.paths
;WITH mappings AS (
SELECT n.*, p.id AS old_id
FROM new_paths n
INNER JOIN paths p
ON p.name = n.name
)
UPDATE n
SET n.parent_id = m.id
FROM new_paths n
INNER JOIN mappings m
ON m.old_id = n.parent_id
--SELECT * FROM new_paths
Please see below approach to resolve an issue, ask questions in the comments if something is unclear, I have added some explanation in code comments
EDITED, to manage GUID (as per comment)
-- declare table var
declare #table table ([Increment] int identity(1,1), Id uniqueidentifier, [parent_id] nvarchar(50), [order] int, [section] nvarchar(50), [name] nvarchar(50), [url] nvarchar(50), [type] nvarchar(50))
-- insert values into this table
insert into #table
select [Id],
[parent_id],
[order],
[section],
[name],
[url],
'B'
from your_table
where [type] = 'A'
-- loop your temp table
declare #max_temp int = (select max(Increment) from #table)
declare #curr int = 1
declare #parent_value uniqueidentifier = null
while (#curr <= #max_temp)
begin
-- do diffrent inserts depend on parent_id value
if (select parent_id from #table) = null
begin
-- set below var, it will be used in next insert where parent_id is not null
set #parent_value = (select Id from #table where Increment = #curr)
insert into your_table ([parent_id], [order], [section], [name], [url], [type])
select
[parent_id],
[order],
[section],
[name],
[url],
[type]
from #table
where Id = #curr
end
else
begin
insert into your_table ([parent_id], [order], [section], [name], [url], [type])
select
isnull(#parent_value, [parent_id]),
[order],
[section],
[name],
[url],
[type]
from #table
where Id = #curr
end
-- update current
set #curr = #curr + 1
end

Pair Row data into column data

I have a query that returns data in the form
[SetID],[COLID],[DataValue],[Order].
1,1,'Value1',1
1,1,'Value2',2
1,1,'Value3',3
1,2,'value4',1
1,2,'Value5',2
1,2,'Value6',3
How to make it return the data like
[SetID],'Value1','Value4',1
[SetID],'Value2','Value5',2
[SetID],'Value3','Value6',3
So that the data tagged with each columnid is back into a column.
The answer appears to be some sort of pivot, but I'm not looking for a straight conversion, or a one-row answer.
To be able to put each data value into a separate column, you need to know the number of DataValues and the ColId of each. You can't dynamically return variable number of columns (unless you use dynamic SQL to construct the query on the fly), moreover each row needs to have the same number of columns, otherwise the table structure doesn't make sense.
Here's an example that you can quickly test, it uses an in-memory table.
Given the table MyTable:
declare #MyTable table (SetId int, ColId int, DataValue varchar(20), [Order] int);
insert into #MyTable values (1,1,'Value1',1)
insert into #MyTable values (1,1,'Value2',2)
insert into #MyTable values (1,1,'Value3',3)
insert into #MyTable values (1,2,'Value4',1)
insert into #MyTable values (1,2,'Value5',2)
insert into #MyTable values (1,2,'Value6',3)
This query will return the values in separate columns:
select mt.SetId,
( select top 1 m1.DataValue from #MyTable m1
where m1.SetId = mt.SetId and
m1.[Order] = mt.[Order] and
m1.ColId = 1 -- hard-coded ColId, you need to know its value
) 'val1',
( select top 1 m1.DataValue from #MyTable m1
where m1.SetId = mt.SetId and
m1.[Order] = mt.[Order] and
m1.ColId = 2 -- same as above
) 'val2', mt.[Order]
from #MyTable mt
group by mt.SetId, mt.[Order]
Result:
+-----------------------------------+
| SetId | Val1 | Val2 | Order |
|-------+---------+--------+--------|
| 1 | Value1 | Value4 | 1 |
|-------+---------+--------+--------|
| 1 | Value2 | Value5 | 2 |
|-------+---------+--------+--------|
| 1 | Value3 | Value6 | 3 |
+-----------------------------------+
However, I would recommend stuffing all the data values into one column (separated by a delimiter), because this will work with any number of data values. Here's the query:
select mt.SetId,
stuff(( select ', '+ m2.DataValue
from #MyTable m2
where mt.SetId = m2.SetId and
mt.[Order] = m2.[Order]
FOR XML PATH('')) , 1, 2, '') 'DataValues',
mt.[Order]
from #MyTable mt
group by mt.SetId, mt.[Order]
Result
+----------------------------------+
| SetId | DataValues | Order |
|-------+------------------+-------|
| 1 | Value1, Value4 | 1 |
|-------+------------------+-------|
| 1 | Value2, Value5 | 2 |
|-------+------------------+-------|
| 1 | Value3, Value6 | 3 |
+-------+------------------+-------+

SQL Server stored procedure - how to sort the records with tree format?

I have a table with these records:
+----+-------------+---------+
| ID | Name | ParentID|
+----+-------------+---------+
| 1 | Item 1 | -1 |
| 2 | Item 2 | -1 |
| 3 | Item 1.1 | 1 |
| 4 | Item 1.2 | 1 |
| 5 | Item 2.1 | 2 |
| 6 | Item 1.1.1 | 3 |
| 7 | Item 1.2.1 | 4 |
| 8 | Item 2.2 | 2 |
| 9 | Item 1.1.1.1| 6 |
+----+-------------+---------+
I want select the records with tree format.
How to get result like below table with a stored procedure? The values of Name column are temporary, it could be ANY WORD.
+----+-------------+---------+
| ID | Name | ParentID|
+----+-------------+---------+
| 1 | Item 1 | -1 |
| 3 | Item 1.1 | 1 |
| 6 | Item 1.1.1 | 3 |
| 9 | Item 1.1.1.1| 6 |
| 4 | Item 1.2 | 1 |
| 7 | Item 1.2.1 | 4 |
| 2 | Item 2 | -1 |
| 5 | Item 2.1 | 2 |
| 8 | Item 2.2 | 2 |
+----+-------------+---------+
Sorry, I'm beginner to stored procedures. So I don't know how to get result like above table.
Thank you for reading
I think parent nodes must be before its child nodes.
Here is my query
DECLARE #SampleData AS TABLE( ID int, Name varchar(20) , ParentID int)
INSERT INTO #SampleData VALUES ( 1 ,' Item 1', -1 )
INSERT INTO #SampleData VALUES ( 2 ,' Item 2' , -1 )
INSERT INTO #SampleData VALUES ( 3 ,' Item 1.1' , 1 )
INSERT INTO #SampleData VALUES ( 4 ,' Item 1.2' , 1 )
INSERT INTO #SampleData VALUES ( 5 ,' Item 2.1' , 2 )
INSERT INTO #SampleData VALUES ( 6 ,' Item 1.1.1' , 4 )
INSERT INTO #SampleData VALUES ( 7 ,' Item 1.2.1' , 6 )
INSERT INTO #SampleData VALUES ( 8 ,' Item 2.2' , 2 )
;with cte as (
select t.Id, t.Name, t.ParentID, 1 as lev, t.Id AS RootId
from #SampleDAta t
where t.ParentID = -1
union all
select t.ID, t.Name,t.ParentID,cte.lev +1, cte.RootId
from cte
INNER JOIN #SampleDAta t on t.ParentID = cte.Id
)
SELECT c.Id, c.Name, c.ParentID FROM cte c
ORDER BY c.RootId, c.lev
OPTION (MAXRECURSION 0)
Result:
If you want to sort like a depth-search-tree, I can do it by a function
CREATE TABLE SampleData ( ID int, Name varchar(20) , ParentID int)
INSERT INTO SampleData VALUES ( 1 ,' Item 1', -1 )
INSERT INTO SampleData VALUES ( 2 ,' Item 2' , -1 )
INSERT INTO SampleData VALUES ( 3 ,' Item 1.1' , 1 )
INSERT INTO SampleData VALUES ( 4 ,' Item 1.2' , 1 )
INSERT INTO SampleData VALUES ( 5 ,' Item 2.1' , 2 )
INSERT INTO SampleData VALUES ( 6 ,' Item 1.1.1' , 3 )
INSERT INTO SampleData VALUES ( 7 ,' Item 1.2.1' , 4 )
INSERT INTO SampleData VALUES ( 8 ,' Item 2.2' , 2 )
Now Create function
CREATE FUNCTION DisplayTree
(
#RootId int
)
RETURNS
#result TABLE (
ID int, Name varchar(20) , ParentID int
)
AS
BEGIN
DECLARE #Temp AS TABLE
(
ID int, Name varchar(20) , ParentID int
)
INSERT INTO #Temp SELECT * FROM SampleData WHERE ParentID = #RootId
WHILE(EXISTS(SELECT 1 FROM #Temp t))
BEGIN
DECLARE #CurrentRootId int
SELECT TOP 1 #CurrentRootId = t.ID FROM #Temp t ORDER BY t.ID ASC
INSERT INTO #result SELECT * FROM #Temp t WHERE t.ID = #CurrentRootId
DELETE FROM #Temp WHERE ID = #CurrentRootId
INSERT INTO #result SELECT * FROM dbo.DisplayTree(#CurrentRootId)
END
RETURN ;
END
GO
AND Execute function
SELECT * FROM dbo.DisplayTree(-1)
You need to split the columns to N number of columns and then need to use Order By those columns.
Schema:
CREATE TABLE #TAB(ID INT, Name VARCHAR(20),ParentID INT)
INSERT INTO #TAB
SELECT 1, 'Item 1', -1
UNION ALL
SELECT 2, 'Item 2' , -1
UNION ALL
SELECT 3, 'Item 1.1' , 1
UNION ALL
SELECT 4, 'Item 1.2' , 1
UNION ALL
SELECT 5, 'Item 2.1' , 2
UNION ALL
SELECT 6, 'Item 1.1.1', 4
UNION ALL
SELECT 7, 'Item 1.2.1', 6
UNION ALL
SELECT 8, 'Item 2.2', 2
I converted Name column to XML and then I made columns from Tags using XML method value
;WITH CTE AS
(
SELECT
ID,
Name,
ParentID,
CAST('<M>'+REPLACE (REPLACE(Name,' ','.'),'.','</M><M>')+'</M>' AS XML)
AS XML_SPLT
FROM #TAB
)
SELECT
ID,
Name,
ParentID,
XML_SPLT.value('/M[1]', 'varchar(50)') As P0,
XML_SPLT.value('/M[2]', 'int') As P1,
XML_SPLT.value('/M[3]', 'int') As P2,
XML_SPLT.value('/M[4]', 'int') As P3
FROM CTE
ORDER BY P0,P1,P2,P3
GO
This will give you the order as you are expecting.
+----+------------+----------+------+----+------+------+
| ID | Name | ParentID | P0 | P1 | P2 | P3 |
+----+------------+----------+------+----+------+------+
| 1 | Item 1 | -1 | Item | 1 | NULL | NULL |
| 3 | Item 1.1 | 1 | Item | 1 | 1 | NULL |
| 6 | Item 1.1.1 | 4 | Item | 1 | 1 | 1 |
| 4 | Item 1.2 | 1 | Item | 1 | 2 | NULL |
| 7 | Item 1.2.1 | 6 | Item | 1 | 2 | 1 |
| 2 | Item 2 | -1 | Item | 2 | NULL | NULL |
| 5 | Item 2.1 | 2 | Item | 2 | 1 | NULL |
| 8 | Item 2.2 | 2 | Item | 2 | 2 | NULL |
+----+------------+----------+------+----+------+------+

Resources