T-SQL ordering data in hierarchy query from parents to children - sql-server

I have hierarchical data with parents and children together with positions.
Sample:
create table groups
(
Child varchar(1),
Parent varchar(1),
Position int
);
insert into groups (Child, Parent, Position)
values ('A', NULL, 1),
('B', 'A', 1),
('C', 'B', 1),
('D', 'B', 2),
('E', NULL, 2),
('F', NULL, 3),
('G', 'F', 1),
('H', 'G', 1),
('I', 'G', 2),
('J', 'F', 2),
('K', 'J', 1),
('L', 'J', 2);
Each group and subgroup has it own position starting with 1, e.g.:
all parents have positions: 1 - x
all children with parent "A" have positions: 1 - x
all children with parent "N" have positions: 1 - x
I need to order the query the way that the children of the parents always come between parents ascending, for example (added levels for better imagination):
Level|Child|Parent|Position
-----+-----+------+---------
3 | A | NULL | 1
2 | B | A | 1
1 | C | B | 1
1 | D | B | 2
3 | E | NULL | 2
3 | F | NULL | 3
2 | G | F | 1
1 | H | G | 1
1 | I | G | 2
2 | J | F | 2
1 | K | J | 1
1 | L | J | 2
This is my current code:
;with cte as
(
select Child, Parent, Position
from groups
union all
select t.Child, t.Parent, t.Position
from groups t
join cte on t.Child = cte.Parent
)
select distinct *
from cte
order by Position
db<>fiddle example
Thank you

Your cte is a little messed up. Here's how I would do it:
;with cte as (
select Child, Parent, Position As ParentPosition, Position, 1 As level
from groups
where parent is null
union all
select t.Child, t.Parent, cte.ParentPosition, t.Position, level + 1
from groups AS t
join cte on t.Parent = cte.Child
)
select *
from cte
order by ParentPosition, level, Position
Your cte's anchor part was all the records in the table, where in fact it should be just the records that has no parents (hence the where parent is null).
Also, I've added a column for the parent's position to enable sorting all the children of the same parent before the next parent, and I've added a level column to help order the children by distance from the main parent.
Results:
Child Parent ParentPosition Position level
A 1 1 1
B A 1 1 2
C B 1 1 3
D B 1 2 3
E 2 2 1
F 3 3 1
G F 3 1 2
J F 3 2 2
K J 3 1 3
H G 3 1 3
I G 3 2 3
L J 3 2 3
Db<>Fiddle

Related

Dynamic T-SQL query for hierarchical data

I am trying to do a dynamic query to go through child - parents table and I have been able to go through top and second level of hierarchical query:
Data:
create table temp
(
Pos int
,Child nvarchar(18)
,Parent nvarchar(18)
,Test int
);
insert into temp (Pos, Child, Parent, Test)
values
(1, 'A', NULL, 1),
(2, 'J', NULL, 10),
(3, 'P', NULL, 16),
(4, 'Y', NULL, 25),
(1, 'B', 'A', 2),
(2, 'E', 'A', 5),
(1, 'C', 'B', 3),
(2, 'D', 'B', 4),
(1, 'F', 'E', 6),
(2, 'G', 'E', 7),
(1, 'H', 'G', 8),
(2, 'I', 'G', 9),
(1, 'K', 'J', 11),
(2, 'L', 'J', 12),
(3, 'M', 'J', 13),
(1, 'N', 'M', 14),
(2, 'O', 'M', 15),
(5, 'Z', NULL, 26),
(1, 'Q', 'P', 17),
(2, 'S', 'P', 19),
(3, 'T', 'P', 20),
(4, 'X', 'P', 24),
(1, 'R', 'Q', 18),
(1, 'U', 'T', 21),
(2, 'V', 'T', 22),
(3, 'W', 'T', 23)
Column Test is only to see at the end if the data are correctly ordered
My code so far:
declare #sql nvarchar(max);
declare #tlp nvarchar(max); --top level parents
declare #i nvarchar(4);
declare #j nvarchar(4);
declare #l nvarchar(4); --level
set #tlp = ';with tlp as (
select ROW_NUMBER() over (order by Pos) as j, * from temp where Parent IS NULL
)';
set #i = 1;
set #j = (select COUNT(*) as j from temp where Parent IS NULL);
set #sql = #tlp;
while #i < #j
begin
set #l = 1;
set #sql += '
select ' + #l + ' as Level, * from tlp where j = ' + #i
set #l = #l + 1
set #sql += '
union all
select ' + #l + ' as Level, ROW_NUMBER() over (order by Pos), * from temp where Parent = (select Child from tlp where j = ' + #i + ')'
set #i = #i + 1
if #i < #j set #sql += '
union all'
end;
exec(#sql);
Output:
level j Pos Child Parent Test
1 1 1 A NULL 1
2 1 1 B A 2
2 2 2 E A 5
1 2 2 J NULL 10
2 1 1 K J 11
2 2 2 L J 12
2 3 3 M J 13
1 3 3 P NULL 16
2 1 1 Q P 17
2 2 2 S P 19
2 3 3 T P 20
2 4 4 X P 24
1 4 4 Y NULL 25
How can I excend the query to dynamicaly go through all the childs? This is the desired output:
Level j Pos Child Parent Test
1 1 1 A NULL 1
2 1 1 B A 2
3 1 1 C B 3
3 2 2 D B 4
2 2 2 E A 5
3 1 1 F E 6
3 2 2 G E 7
4 1 1 H G 8
4 2 2 I G 9
1 2 2 J NULL 10
2 1 1 K J 11
2 2 2 L J 12
2 3 3 M J 13
3 1 1 N M 14
3 2 2 O M 15
1 3 3 P NULL 16
2 1 1 Q P 17
3 1 1 R Q 18
2 2 2 S P 19
2 3 3 T P 20
3 1 1 U T 21
3 2 2 V T 22
3 3 3 W T 23
3 4 4 X P 24
1 4 4 Y NULL 25
1 5 5 Z NULL 26
Here is a visual interpretation what I was trying to achieve:
I don't see the need for dynamic SQL at all. You have hierarchical data that you want to traverse depth-first: in SQL, this is typically done with a recursive query.
To manage the ordering of the rows, you can keep track of the path to each node.
Consider:
with cte as (
select t.*, 1 lvl, cast(child as nvarchar(max)) path
from temp t
where parent is null
union all
select t.*, c.lvl + 1, c.path + '/' + cast(t.child as nvarchar(max))
from cte c
inner join temp t on t.parent = c.child
)
select * from cte order by path
Demo on DB Fiddle:
Pos | Child | Parent | Test | lvl | path
--: | :---- | :----- | ---: | --: | :------
1 | A | null | 1 | 1 | A
1 | B | A | 2 | 2 | A/B
1 | C | B | 3 | 3 | A/B/C
2 | D | B | 4 | 3 | A/B/D
2 | E | A | 5 | 2 | A/E
1 | F | E | 6 | 3 | A/E/F
2 | G | E | 7 | 3 | A/E/G
1 | H | G | 8 | 4 | A/E/G/H
2 | I | G | 9 | 4 | A/E/G/I
2 | J | null | 10 | 1 | J
1 | K | J | 11 | 2 | J/K
2 | L | J | 12 | 2 | J/L
3 | M | J | 13 | 2 | J/M
1 | N | M | 14 | 3 | J/M/N
2 | O | M | 15 | 3 | J/M/O
3 | P | null | 16 | 1 | P
1 | Q | P | 17 | 2 | P/Q
1 | R | Q | 18 | 3 | P/Q/R
2 | S | P | 19 | 2 | P/S
3 | T | P | 20 | 2 | P/T
1 | U | T | 21 | 3 | P/T/U
2 | V | T | 22 | 3 | P/T/V
3 | W | T | 23 | 3 | P/T/W
4 | X | P | 24 | 2 | P/X
4 | Y | null | 25 | 1 | Y
5 | Z | null | 26 | 1 | Z
If a path may have more than 100 nodes, then you need to add option(maxrecursion 0) at the end of the query, otherwise you will hit the maximum level of recursion that SQL Server allows by default.
You could find material on what you are asking by searching material on Recursive Query like articles or older answers.
For creating your Recursive Query, you create a CTE where the first table is your anchor like your first level where there is column Parent is NULL. In the same CTE, you keep adding 1 to the level. Please find the answer in Fiddle
WITH MyCTE AS (
SELECT *, 1 AS Level
FROM temp
WHERE Parent IS NULL
UNION ALL
SELECT t.Pos, t.Child, t.Parent, t.Test, MyCTE.Level+1 AS Level
FROM temp AS t
INNER JOIN MyCTE
ON t.Parent = MyCTE.Child
WHERE t.Parent IS NOT NULL)
SELECT MyCTE.*, CASE WHEN Offsprings.Offspring IS NULL THEN 1 ELSE Offsprings.Offspring END AS Offspring
FROM MyCTE
LEFT JOIN (
SELECT Parent, COUNT(Parent) AS Offspring
FROM temp
GROUP BY Parent)Offsprings
ON MyCTE.Child = Offsprings.Parent
ORDER BY MyCTE.Child
Seems same answer (that I thought of..) posted by other peers with great demo. However, following example and this post may help for simple and better understanding on Recursive CTE
DDL
create table temp
(
recid int identity (1,1)
,Pos_ID int
,Child_Pos nvarchar(50)
,Parent_Pos nvarchar(50)
);
insert into temp (Pos_ID, Child_Pos, Parent_Pos)
values
(1, 'Super Boss', NULL),
(2, 'Boss', 'Super Boss'),
(3, 'Sr. Mangaer 1', 'Boss'),
(3, 'Sr. Mangaer 2', 'Boss'),
(3, 'Sr. Mangaer 3', 'Boss'),
(4, 'Mangaer 1', 'Sr. Mangaer 1'),
(4, 'Mangaer 2', 'Sr. Mangaer 1'),
(4, 'Mangaer 3', 'Sr. Mangaer 2'),
(4, 'Mangaer 4', 'Sr. Mangaer 2'),
(4, 'Mangaer 5', 'Sr. Mangaer 3'),
(4, 'Mangaer 6', 'Sr. Mangaer 3'),
(5, 'Emp 01', 'Mangaer 1'),
(5, 'Emp 02', 'Mangaer 1'),
(5, 'Emp 03', 'Mangaer 2'),
(5, 'Emp 04', 'Mangaer 2'),
(5, 'Emp 05', 'Mangaer 3'),
(5, 'Emp 06', 'Mangaer 3'),
(5, 'Emp 07', 'Mangaer 4'),
(5, 'Emp 08', 'Mangaer 4'),
(5, 'Emp 09', 'Mangaer 5'),
(5, 'Emp 10', 'Mangaer 5'),
(5, 'Emp 11', 'Mangaer 6'),
(5, 'Emp 12', 'Mangaer 6')
go
Recursive CTE Example
with main as (
select Child_Pos, Parent_Pos,Pos_ID, 1 as Reculevel
from temp as t1
--where Parent_Pos is not null
UNION ALL
select t2.Child_Pos, t2.Parent_Pos, t2.Pos_ID, main.Reculevel + 1
from temp as t2
join main on t2.Parent_Pos = main.Child_Pos
)
select * from main
Following Recursive CTE for your Example
with main as (
select Pos, Child, Parent, Test, 1 as RecuLevel
from temp as t1
UNION ALL
select t2.Pos, t2.Child, t2.Parent, t2.Test, RecuLevel + 1
from temp as t2
join main on t2.Parent = main.Child
)
select * from main
--option (maxrecursion 0) -- be cautious enabling this!

T-SQL: UPDATE table according to a column

TLDNR: how do I update a table depending on a column?
Problem situation: the current column SortingNumber is full of bad data.
Solution: reassign new values to SortingNumber based on their Parent. The SortingNumber shall be 1 for the lowest current SortingNumber (by Parent) and be incremented by 1 for every subsequent dataset.
Current data: Desired result:
ID | Parent | SortingNumber >> ID | Parent | SortingNumber
1 | 1 | 3 >> 1 | 1 | 1
2 | 1 | 4 >> 2 | 1 | 2
3 | 1 | 5 >> 3 | 1 | 3
4 | 2 | 8 >> 4 | 2 | 1
5 | 2 | 10 >> 5 | 2 | 2
6 | 2 | 13 >> 6 | 2 | 3
Actual problem: I'm having trouble figuring out how to update the datasets corresponding to their parents.
My script currently updates all the values incrementally and doesn't group it by Parent.
My current solution:
DECLARE #lastSN INTEGER = 0;
WITH toUpdate AS
(
SELECT
T1.*,
-- "calculate" the sorting number from the row above
LAG(T1.SortingNumber + 1, 1, 1) OVER (ORDER BY T1.SortingNumber) AS [newSortNumber]
FROM
T AS T1
INNER JOIN
T AS T2 ON T1.Parent = T2.ID
)
UPDATE toUpdate
SET
#lastSN = CASE WHEN [newSortNumber] = 1 AND #lastSN = 0 THEN 1 ELSE #lastSN + 1 END,
toUpdate.SortingNumber = #lastSN
;
Result is:
ID | Parent | SortingNumber
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 2 | 4
5 | 2 | 5
6 | 2 | 6
I guess my question could be phrased as: how do I update datasets depending on the Parent column?
PS: here is the CREATE statement if you wish to try it out yourself
CREATE TABLE T
(
ID INT IDENTITY(1,1) PRIMARY KEY,
Parent INT FOREIGN KEY REFERENCES T(ID),
SortingNumber INT
);
GO
INSERT INTO T (Parent, SortingNumber)
VALUES (1, 3), (1, 4), (1, 5), (2, 8), (2, 10), (2, 13);
You can employ row_number to achieve this using partitioning by Parent and ordering by SortingNumber.
WITH cte AS (
SELECT
* ,
ROW_NUMBER() OVER (PARTITION BY Parent ORDER BY SortingNumber) AS NewSortingNumber
FROM T
)
UPDATE cte
SET SortingNumber = NewSortingNumber
A window function creates small tables within the table using Parent, so we have two subsets, one for Parent = 1 and the another for Parent = 2. Then it uses ORDER BY to know from which row it should start count (starting from 1). The first row is for Parent = 1 and ID =1 so it gets 1, the next row gets 2 etc. Please look here for more details.
As an alternative you can just rank, ordering by patient then ID:
UPDATE tt
SET sortingnumber = drank from (select *, DENSE_RANK() OVER (order by Parent, ID) as drank from tt ) a where tt.ID=a.id and tt.parent=a.parent
select * from tt

Order parent child records by parent group and children

I need sort query results by specific two related columns. My table is:
Row no | Col 1 | Col 2 | Col 3 | Col 4
1 | 1 | X | 1 | 5
2 | 2 | Y | 1 | 6
3 | 5 | Z | 2 | 7
4 | 6 | T | 2 | 0
5 | 7 | T | 3 | 0
6 | 6 | W | 2 | 0
The values in Col 4 represents the child record linked to Col 1.
So for Row no = 1 the next child record is row 3, where Col 1 holds the value of Col 4 from the first row.
The next child row for row 3 is row 5, based on the link between Col 1 and Col 4.
And I'd like to return this results:
Row no | Col 1 | Col 2 | Col 3 | Col 4
1 | 1 | X | 1 | 5
3 | 5 | Z | 2 | 7
5 | 7 | T | 3 | 0
2 | 2 | Y | 1 | 6
4 | 6 | T | 2 | 0
6 | 6 | W | 2 | 0
So I want the ordering to show a Parent row, followed by it's child rows, before moving on to the next top level Parent row.
You can achieve what you're after with a Recursive CTE to find all the parent records and link them to their child records.
Dummy table setup:
CREATE TABLE #Table1
(
[Row no] INT ,
[Col 1] INT ,
[Col 2] VARCHAR(1) ,
[Col 3] INT ,
[Col 4] INT
);
INSERT INTO #Table1
( [Row no], [Col 1], [Col 2], [Col 3], [Col 4] )
VALUES ( 1, 1, 'X', 1, 5 ),
( 2, 2, 'Y', 1, 6 ),
( 3, 5, 'Z', 2, 7 ),
( 4, 6, 'T', 2, 0 ),
( 5, 7, 'T', 3, 0 ),
( 6, 6, 'W', 2, 0 );
Recursive CTE:
;WITH cte
AS ( SELECT * ,
ROW_NUMBER() OVER ( ORDER BY t1.[Col 1] ) GroupNo
FROM #Table1 t1
WHERE t1.[Col 1] NOT IN ( SELECT [Col 4] FROM #Table1 )
UNION ALL
SELECT t.* ,
cte.GroupNo
FROM #Table1 t
INNER JOIN cte ON cte.[Col 4] = t.[Col 1]
)
SELECT *
FROM cte
ORDER BY cte.GroupNo , cte.[Row no]
DROP TABLE #Table1
This combines 2 queries with a UNION ALL. The first query finds the top level items where the value of [Col 1] does not appear in [Col 4]:
WHERE t1.[Col 1] NOT IN ( SELECT [Col 4] FROM #Table1 )
The second query finds the child records on the first query with this JOIN:
INNER JOIN cte ON cte.[Col 4] = t.[Col 1]
For the ordering, I've used the following to give the the results of the first query a GroupNo, which is used later to order the records:
ROW_NUMBER() OVER ( ORDER BY t1.[Col 1] ) GroupNo

Sorting columns to be multi-row

I have data in SQL Server like this:
floor | Apartment
1 1
1 2
1 3
2 4
2 5
2 6
because one floor has 3 apartments, I want to sort or convert the Apartment column to be a row like the flowing
4 | 5 | 6
1 | 2 | 3
SQL Fiddle
MS SQL Server 2012 Schema Setup:
create table YourTable
(
Floor int,
Apartment int
)
go
insert into YourTable values
( 1, 1),
( 1, 2),
( 1, 3),
( 2, 4),
( 2, 5),
( 2, 6)
Query 1:
select P.Floor,
P.[1] as Room1,
P.[2] as Room2,
P.[3] as Room3
from (
select Floor,
Apartment,
row_number() over(partition by Floor order by Apartment) as rn
from YourTable
) as T
pivot(min(T.Apartment) for T.rn in ([1], [2], [3])) as P
Results:
| FLOOR | ROOM1 | ROOM2 | ROOM3 |
|-------|-------|-------|-------|
| 1 | 1 | 2 | 3 |
| 2 | 4 | 5 | 6 |

Column into rows and make a master table of columns

I need insert columns data to a different table and create master table for columns
Like example : I need to convert table tblcatData into two tables tblCat and tblcatDataNew
tblCatData
Primaykey | A | B | C | D | D | F | G | H | I | J | K | L | M |
--------------------------------------------------------------------
1 | 1 | 2 | 3 | 5 | 5 | 5 | 3 | 3 | 3 | 1 | 4 | 1 | 1 |
2 | 1 | 2 | 5 | 5 | 5 | 5 | 3 | 5 | 3 | 1 | 1 | 5 | 1 |
3 | 5 | 2 | 3 | 5 | 5 | 5 | 5 | 3 | 3 | 1 | 1 | 1 | 4 |
tblCat
PrimaryKey | Category
----------------------------
1 | A
2 | B
3 | C
4 | D
5 | E
6 | F
7 | G
. .
. .
. .
tblCatDataNew
PrimaryKey | FK_CatID | Data |
-----------------------------------
1 | 1 | 1 |
2 | 1 | 1 |
3 | 1 | 5 |
4 | 2 | 2 |
5 | 2 | 2 |
6 | 2 | 2 |
7 | 3 | 3 |
8 | 3 | 5 |
. . .
. . .
. . .
You could try the following scenario:
Create tblCat.
Create tblCatDataNew with the following deviations from the original design:
the FK_CatID column is allowed to accept NULLs temporarily (or maybe permanently, if that was your original intention);
an extra column is added temporarily to receive the category names from the original table.
Unpivot tblCatData and insert the results into tblCatDataNew (the values into Data and the column names, as category names, into the temporary column).
Select all the distinct category names from tblCatDataNew and insert them into tblCat. (That will produce the key values for them.)
Update the foreign keys in tblCatDataNew from tblCat, joining the two tables by category names.
Drop the temporary column from tblCatDataNew.
Set tblCatDataNew.FK_CatID as NOT NULL (that is, if you wanted it to be so).
Here's the entire test script, including creation of the original table (in case someone would like to try it):
BEGIN TRANSACTION
GO
/* prepare the original table, for tests */
WITH data (
Primaykey, A, B, C, D, E, F, G, H, I, J, K, L, M
) AS (
SELECT 1 , 1, 2, 3, 5, 5, 5, 3, 3, 3, 1, 4, 1, 1 UNION ALL
SELECT 2 , 1, 2, 5, 5, 5, 5, 3, 5, 3, 1, 1, 5, 1 UNION ALL
SELECT 3 , 5, 2, 3, 5, 5, 5, 5, 3, 3, 1, 1, 1, 4
)
SELECT * INTO tblCatData FROM data;
GO
/* Step 1 */
CREATE TABLE tblCat (
PrimaryKey int IDENTITY CONSTRAINT PK_tblCat PRIMARY KEY,
Category varchar(50) NOT NULL
);
GO
/* Step 2 */
CREATE TABLE tblCatDataNew (
PrimaryKey int IDENTITY CONSTRAINT PK_tblCatDataNew PRIMARY KEY,
FK_CatID int NULL CONSTRAINT FK_tblCatDataNew_tblCat FOREIGN KEY REFERENCES tblCat (PrimaryKey),
Data int,
Category varchar(50)
);
GO
/* Step 3 */
INSERT INTO tblCatDataNew (
Data,
Category
)
SELECT
Data,
Category
FROM tblCatData
UNPIVOT (
Data for Category IN (A, B, C, D, E, F, G, H, I, J, K, L, M)
) u
ORDER BY
Category,
Primaykey;
GO
/* Step 4 */
INSERT INTO tblCat (Category)
SELECT DISTINCT Category
FROM tblCatDataNew
GO
/* Step 5 */
UPDATE tblCatDataNew
SET FK_CatID = c.PrimaryKey
FROM tblCat c
WHERE tblCatDataNew.Category = c.Category
GO
/* Step 6 */
ALTER TABLE tblCatDataNew
DROP COLUMN Category
GO
/* Step 7 */
ALTER TABLE tblCatDataNew
ALTER COLUMN FK_CatID int NOT NULL
GO
/* view the results */
SELECT * FROM tblCat
SELECT * FROM tblCatDataNew
GO
ROLLBACK TRANSACTION
Note that the UNPIVOT clause is supported in SQL Server starting from the 2005 version. In earlier versions you'd have to use a different method to unpivot data (Step 3), e.g. like this:
INSERT INTO tblCatDataNew (
Data,
Category
)
SELECT
Data = CASE x.CatNum
WHEN 1 THEN A
WHEN 2 THEN B
WHEN 3 THEN C
WHEN 4 THEN D
WHEN 5 THEN E
WHEN 6 THEN F
WHEN 7 THEN G
WHEN 8 THEN H
WHEN 9 THEN I
WHEN 10 THEN J
WHEN 11 THEN K
WHEN 12 THEN L
WHEN 13 THEN M
END,
Category = CASE x.CatNum
WHEN 1 THEN 'A'
WHEN 2 THEN 'B'
WHEN 3 THEN 'C'
WHEN 4 THEN 'D'
WHEN 5 THEN 'E'
WHEN 6 THEN 'F'
WHEN 7 THEN 'G'
WHEN 8 THEN 'H'
WHEN 9 THEN 'I'
WHEN 10 THEN 'J'
WHEN 11 THEN 'K'
WHEN 12 THEN 'L'
WHEN 13 THEN 'M'
END
FROM tblCatData
CROSS JOIN (
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5 UNION ALL
SELECT 6 UNION ALL
SELECT 7 UNION ALL
SELECT 8 UNION ALL
SELECT 9 UNION ALL
SELECT 10 UNION ALL
SELECT 11 UNION ALL
SELECT 12 UNION ALL
SELECT 13
) x (CatNum)
ORDER BY
Category,
Primaykey;
or even like this:
INSERT INTO tblCatDataNew (
Data,
Category
)
SELECT
Data = CASE x.CatNum
WHEN 1 THEN A
WHEN 2 THEN B
WHEN 3 THEN C
WHEN 4 THEN D
WHEN 5 THEN E
WHEN 6 THEN F
WHEN 7 THEN G
WHEN 8 THEN H
WHEN 9 THEN I
WHEN 10 THEN J
WHEN 11 THEN K
WHEN 12 THEN L
WHEN 13 THEN M
END,
Category = x.CatName
FROM tblCatData
CROSS JOIN (
SELECT 1, 'A' UNION ALL
SELECT 2, 'B' UNION ALL
SELECT 3, 'C' UNION ALL
SELECT 4, 'D' UNION ALL
SELECT 5, 'E' UNION ALL
SELECT 6, 'F' UNION ALL
SELECT 7, 'G' UNION ALL
SELECT 8, 'H' UNION ALL
SELECT 9, 'I' UNION ALL
SELECT 10, 'J' UNION ALL
SELECT 11, 'K' UNION ALL
SELECT 12, 'L' UNION ALL
SELECT 13, 'M'
) x (CatNum, CatName)
ORDER BY
Category,
Primaykey;
Here are the results that the above script produced for me:
tblCat:
PrimaryKey Category
----------- --------------------------------------------------
1 A
2 B
3 C
4 D
5 E
6 F
7 G
8 H
9 I
10 J
11 K
12 L
13 M
tblCatDataNew:
PrimaryKey FK_CatID Data
----------- ----------- -----------
1 1 1
2 1 1
3 1 5
4 2 2
5 2 2
6 2 2
7 3 3
8 3 5
9 3 3
10 4 5
11 4 5
12 4 5
13 5 5
14 5 5
15 5 5
16 6 5
17 6 5
18 6 5
19 7 3
20 7 3
21 7 5
22 8 3
23 8 5
24 8 3
25 9 3
26 9 3
27 9 3
28 10 1
29 10 1
30 10 1
31 11 4
32 11 1
33 11 1
34 12 1
35 12 5
36 12 1
37 13 1
38 13 1
39 13 4
You can use union to unpivot the columns, and select ... into to store the result in a new table:
select PrimaryKey
, FK_CatId
, Category
into tblCatDataNew
from (
select PrimaryKey
, 1
, A
from tblCatData
union all
select PrimaryKey
, 2
, B
from tblCatData
union all
...
)

Resources