根据记录数在 SQL 服务器中将单列拆分为多列
Split single column into multiple columns in SQL server based on number of records
我在一个列中有大约 28 条记录(这是动态的,有时是奇数条记录,有时是偶数条记录)。
想根据变量将它们分成多列。每列应有 5 或 6 条记录。
注意:这5或6条记录是一个变量,应该改变。
示例输入 table:
Col1
-----
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
V
W
X
Y
Z
所需的输出 table 将是:
Col1 col2 col3 col4 col5
-------------------------------------
A F K P U
B G L Q V
C H M R W
D I N S X
E J O T Y
Z
这是我尝试过的,这两个不同的查询提供了不同的结果:
select Col1
from Table1 where Col1 is not null
order by [col1] asc
--This provides all records
WITH CTE AS
(
SELECT [Col1],
(ROW_NUMBER() OVER (ORDER BY Col1) -1)%5 AS Col,
(ROW_NUMBER() OVER (ORDER BY Col1) -1)/5 AS Row
FROM Table1 where Col1 is not null
)
SELECT [0], [1], [2], [3], [4]
FROM CTE
PIVOT (MAX([COL1]) FOR Col IN ([0], [1], [2], [3], [4])) AS Pvt
ORDER BY Row
-- 在这种情况下记录丢失(当记录数很高时,因为记录数可以动态增加)
WITH CTE AS
(
SELECT [Col1],
(ROW_NUMBER() OVER (ORDER BY Col1) -1)%5 AS Col,
(ROW_NUMBER() OVER (ORDER BY Col1) -1)/5 AS Row
FROM Table1 where Col1 is not null
)
SELECT [0], [1]
FROM CTE
PIVOT (MAX([COL1]) FOR Col IN ([0], [1])) AS Pvt
ORDER BY Row
我做错了什么?我怎样才能达到预期的输出?谢谢。
您做错的是在 Col
、
中获取五个值
(ROW_NUMBER() OVER (ORDER BY Col1) -1)%5 AS Col,
但只有其中 2 个 PIVOTing。
PIVOT (MAX([COL1]) FOR Col IN ([0], [1])) AS Pvt
因此您忽略了所有值为 2、3 和 4 的 Col
。
根据经验,我能够提出以下数据透视查询:
WITH cte AS (
SELECT Col1, ROW_NUMBER() OVER (ORDER BY Col1) rn
FROM yourTable
)
SELECT
MAX(CASE WHEN (rn-1) / 5 = 0 THEN Col1 END) AS col1,
MAX(CASE WHEN (rn-1) / 5 = 1 THEN Col1 END) AS col2,
MAX(CASE WHEN (rn-1) / 5 = 2 THEN Col1 END) AS col3,
MAX(CASE WHEN (rn-1) / 5 = 3 THEN Col1 END) AS col4,
MAX(CASE WHEN (rn-1) / 5 = 4 THEN Col1 END) AS co15
FROM cte
GROUP BY
(rn-1) % 5;
这里的想法是,每个组(一行)由当前行号以 5 为模的那一点确定。列或数据透视表由当前行号的 5 的倍数决定。
您需要先为每条记录分配一个列,这可以通过NTILE()
:
来完成
WITH Table1 AS
(
SELECT TOP 26 Col1 = CHAR(64 + ROW_NUMBER() OVER(ORDER BY object_id))
FROM sys.all_objects
)
SELECT [Col1],
5 - NTILE(5) OVER(ORDER BY Col1 DESC) AS Col
FROM Table1 ;
给予:
Col1 Col
---------
Z 4
Y 4
X 4
W 4
V 4
U 4
T 3
....
Y 4
Z 4
注意,要从最后一列开始填充,您必须以相反的顺序分配列 (ORDER BY Col1 DESC
),然后从列总数中扣除。
然后您可以通过在您的列中排序来计算您的行:
WITH Table1 AS
(
SELECT TOP 26 Col1 = CHAR(64 + ROW_NUMBER() OVER(ORDER BY object_id))
FROM sys.all_objects
)
SELECT Col1, Col, ROW_NUMBER() OVER(PARTITION BY Col ORDER BY Col1) AS Row
FROM ( SELECT [Col1],
5 - NTILE(5) OVER(ORDER BY Col1 DESC) AS Col
FROM Table1
) c;
给予:
Col1 Col Row
--------------------
A 0 1
B 0 2
C 0 3
D 0 4
E 0 5
F 1 1
G 1 2
然后你可以应用枢轴:
WITH Table1 AS
(
SELECT TOP 26 Col1 = CHAR(64 + ROW_NUMBER() OVER(ORDER BY object_id))
FROM sys.all_objects
), CTE AS
(
SELECT Col1, COL, ROW_NUMBER() OVER(PARTITION BY Col ORDER BY Col1) AS Row
FROM ( SELECT [Col1],
5 - NTILE(5) OVER(ORDER BY Col1 DESC) AS Col
FROM Table1
) c
)
SELECT [0], [1], [2], [3], [4]
FROM CTE
PIVOT (MAX([COL1]) FOR Col IN ([0], [1], [2], [3], [4])) AS Pvt
ORDER BY Row;
给予:
0 1 2 3 4
-------------------------------------
A F K P U
B G L Q V
C H M R W
D I N S X
E J O T Y
NULL NULL NULL NULL Z
附录
我已经设置了一个示例 table,并使用动态 SQL 的过程来简化 re-use 来演示此解决方案,它似乎按我的预期工作。
设置
-- SET UP TABLE AND INSERT RANDOM VALUES
IF OBJECT_ID(N'tempdb..#Table1', 'U') IS NOT NULL
DROP TABLE #Table1;
CREATE TABLE #Table1 (Col1 CHAR(2));
INSERT #Table1 (Col1)
SELECT CONCAT(Letter, Number)
FROM (SELECT TOP 26 Letter = CHAR(64 + ROW_NUMBER() OVER(ORDER BY object_id))
FROM sys.all_objects) l
CROSS JOIN (VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9)) n (Number);
GO
IF OBJECT_ID(N'tempdb..#GenerateMatrix', 'P') IS NOT NULL
DROP PROCEDURE #GenerateMatrix;
GO
CREATE PROCEDURE #GenerateMatrix @Records INT, @Columns INT
AS
BEGIN
-- GENERATE COLUMNS FOR PIVOT AND SELECT
DECLARE @ColSQL NVARCHAR(MAX) =
STUFF((SELECT TOP (@Columns)
CONCAT(',', QUOTENAME(ROW_NUMBER() OVER(ORDER BY Col1) - 1))
FROM #Table1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, '');
-- FOR @Cols = 5 Generates "[0],[1],[2],[3],[4]"
DECLARE @SQL NVARCHAR(MAX) =
CONCAT('SELECT ', @ColSQL, '
FROM (SELECT Col1, Col, Row = ROW_NUMBER() OVER(PARTITION BY Col ORDER BY Col1)
FROM ( SELECT [Col1], Col = @Columns - NTILE(@Columns) OVER(ORDER BY Col1 DESC)
FROM (SELECT TOP (@Records) Col1 FROM #Table1 Col1) t
) c) c
PIVOT (MAX([COL1]) FOR Col IN (', @ColSQL, ')) AS Pvt
ORDER BY Row;');
EXECUTE sp_executesql @SQL, N'@Columns INT, @Records INT', @Columns = @Columns, @Records = @Records;
END
GO
测试 1
EXECUTE #GenerateMatrix @Records = 26, @Columns = 5;
0 1 2 3 4
----------------------------------
A1 A6 B2 B7 C3
A2 A7 B3 B8 C4
A3 A8 B4 B9 C5
A4 A9 B5 C1 C6
A5 B1 B6 C2 C7
NULL NULL NULL NULL C8
测试 2
EXECUTE #GenerateMatrix @Records = 8, @Columns = 4;
0 1 2 3
----------------------------
A1 A3 A5 A7
A2 A4 A6 A8
测试 3
EXECUTE #GenerateMatrix @Records = 40, @Columns = 8;
0 1 2 3 4 5 6 7
-----------------------------------------------------------
A1 A6 B2 B7 C3 C8 D4 D9
A2 A7 B3 B8 C4 C9 D5 E1
A3 A8 B4 B9 C5 D1 D6 E2
A4 A9 B5 C1 C6 D2 D7 E3
A5 B1 B6 C2 C7 D3 D8 E4
测试 4
EXECUTE #GenerateMatrix @Records = 50, @Columns = 6;
0 1 2 3 4 5
---------------------------------------
A1 A9 B8 C7 D6 E6
A2 B1 B9 C8 D7 E7
A3 B2 C1 C9 D8 E8
A4 B3 C2 D1 D9 E9
A5 B4 C3 D2 E1 F1
A6 B5 C4 D3 E2 F2
A7 B6 C5 D4 E3 F3
A8 B7 C6 D5 E4 F4
NULL NULL NULL NULL E5 F5
在任何测试中都没有丢失任何记录,也没有只有空值的行。
我在一个列中有大约 28 条记录(这是动态的,有时是奇数条记录,有时是偶数条记录)。
想根据变量将它们分成多列。每列应有 5 或 6 条记录。
注意:这5或6条记录是一个变量,应该改变。
示例输入 table:
Col1
-----
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
V
W
X
Y
Z
所需的输出 table 将是:
Col1 col2 col3 col4 col5
-------------------------------------
A F K P U
B G L Q V
C H M R W
D I N S X
E J O T Y
Z
这是我尝试过的,这两个不同的查询提供了不同的结果:
select Col1
from Table1 where Col1 is not null
order by [col1] asc
--This provides all records
WITH CTE AS
(
SELECT [Col1],
(ROW_NUMBER() OVER (ORDER BY Col1) -1)%5 AS Col,
(ROW_NUMBER() OVER (ORDER BY Col1) -1)/5 AS Row
FROM Table1 where Col1 is not null
)
SELECT [0], [1], [2], [3], [4]
FROM CTE
PIVOT (MAX([COL1]) FOR Col IN ([0], [1], [2], [3], [4])) AS Pvt
ORDER BY Row
-- 在这种情况下记录丢失(当记录数很高时,因为记录数可以动态增加)
WITH CTE AS
(
SELECT [Col1],
(ROW_NUMBER() OVER (ORDER BY Col1) -1)%5 AS Col,
(ROW_NUMBER() OVER (ORDER BY Col1) -1)/5 AS Row
FROM Table1 where Col1 is not null
)
SELECT [0], [1]
FROM CTE
PIVOT (MAX([COL1]) FOR Col IN ([0], [1])) AS Pvt
ORDER BY Row
我做错了什么?我怎样才能达到预期的输出?谢谢。
您做错的是在 Col
、
(ROW_NUMBER() OVER (ORDER BY Col1) -1)%5 AS Col,
但只有其中 2 个 PIVOTing。
PIVOT (MAX([COL1]) FOR Col IN ([0], [1])) AS Pvt
因此您忽略了所有值为 2、3 和 4 的 Col
。
根据经验,我能够提出以下数据透视查询:
WITH cte AS (
SELECT Col1, ROW_NUMBER() OVER (ORDER BY Col1) rn
FROM yourTable
)
SELECT
MAX(CASE WHEN (rn-1) / 5 = 0 THEN Col1 END) AS col1,
MAX(CASE WHEN (rn-1) / 5 = 1 THEN Col1 END) AS col2,
MAX(CASE WHEN (rn-1) / 5 = 2 THEN Col1 END) AS col3,
MAX(CASE WHEN (rn-1) / 5 = 3 THEN Col1 END) AS col4,
MAX(CASE WHEN (rn-1) / 5 = 4 THEN Col1 END) AS co15
FROM cte
GROUP BY
(rn-1) % 5;
这里的想法是,每个组(一行)由当前行号以 5 为模的那一点确定。列或数据透视表由当前行号的 5 的倍数决定。
您需要先为每条记录分配一个列,这可以通过NTILE()
:
WITH Table1 AS
(
SELECT TOP 26 Col1 = CHAR(64 + ROW_NUMBER() OVER(ORDER BY object_id))
FROM sys.all_objects
)
SELECT [Col1],
5 - NTILE(5) OVER(ORDER BY Col1 DESC) AS Col
FROM Table1 ;
给予:
Col1 Col
---------
Z 4
Y 4
X 4
W 4
V 4
U 4
T 3
....
Y 4
Z 4
注意,要从最后一列开始填充,您必须以相反的顺序分配列 (ORDER BY Col1 DESC
),然后从列总数中扣除。
然后您可以通过在您的列中排序来计算您的行:
WITH Table1 AS
(
SELECT TOP 26 Col1 = CHAR(64 + ROW_NUMBER() OVER(ORDER BY object_id))
FROM sys.all_objects
)
SELECT Col1, Col, ROW_NUMBER() OVER(PARTITION BY Col ORDER BY Col1) AS Row
FROM ( SELECT [Col1],
5 - NTILE(5) OVER(ORDER BY Col1 DESC) AS Col
FROM Table1
) c;
给予:
Col1 Col Row
--------------------
A 0 1
B 0 2
C 0 3
D 0 4
E 0 5
F 1 1
G 1 2
然后你可以应用枢轴:
WITH Table1 AS
(
SELECT TOP 26 Col1 = CHAR(64 + ROW_NUMBER() OVER(ORDER BY object_id))
FROM sys.all_objects
), CTE AS
(
SELECT Col1, COL, ROW_NUMBER() OVER(PARTITION BY Col ORDER BY Col1) AS Row
FROM ( SELECT [Col1],
5 - NTILE(5) OVER(ORDER BY Col1 DESC) AS Col
FROM Table1
) c
)
SELECT [0], [1], [2], [3], [4]
FROM CTE
PIVOT (MAX([COL1]) FOR Col IN ([0], [1], [2], [3], [4])) AS Pvt
ORDER BY Row;
给予:
0 1 2 3 4
-------------------------------------
A F K P U
B G L Q V
C H M R W
D I N S X
E J O T Y
NULL NULL NULL NULL Z
附录
我已经设置了一个示例 table,并使用动态 SQL 的过程来简化 re-use 来演示此解决方案,它似乎按我的预期工作。
设置
-- SET UP TABLE AND INSERT RANDOM VALUES
IF OBJECT_ID(N'tempdb..#Table1', 'U') IS NOT NULL
DROP TABLE #Table1;
CREATE TABLE #Table1 (Col1 CHAR(2));
INSERT #Table1 (Col1)
SELECT CONCAT(Letter, Number)
FROM (SELECT TOP 26 Letter = CHAR(64 + ROW_NUMBER() OVER(ORDER BY object_id))
FROM sys.all_objects) l
CROSS JOIN (VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9)) n (Number);
GO
IF OBJECT_ID(N'tempdb..#GenerateMatrix', 'P') IS NOT NULL
DROP PROCEDURE #GenerateMatrix;
GO
CREATE PROCEDURE #GenerateMatrix @Records INT, @Columns INT
AS
BEGIN
-- GENERATE COLUMNS FOR PIVOT AND SELECT
DECLARE @ColSQL NVARCHAR(MAX) =
STUFF((SELECT TOP (@Columns)
CONCAT(',', QUOTENAME(ROW_NUMBER() OVER(ORDER BY Col1) - 1))
FROM #Table1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, '');
-- FOR @Cols = 5 Generates "[0],[1],[2],[3],[4]"
DECLARE @SQL NVARCHAR(MAX) =
CONCAT('SELECT ', @ColSQL, '
FROM (SELECT Col1, Col, Row = ROW_NUMBER() OVER(PARTITION BY Col ORDER BY Col1)
FROM ( SELECT [Col1], Col = @Columns - NTILE(@Columns) OVER(ORDER BY Col1 DESC)
FROM (SELECT TOP (@Records) Col1 FROM #Table1 Col1) t
) c) c
PIVOT (MAX([COL1]) FOR Col IN (', @ColSQL, ')) AS Pvt
ORDER BY Row;');
EXECUTE sp_executesql @SQL, N'@Columns INT, @Records INT', @Columns = @Columns, @Records = @Records;
END
GO
测试 1
EXECUTE #GenerateMatrix @Records = 26, @Columns = 5;
0 1 2 3 4
----------------------------------
A1 A6 B2 B7 C3
A2 A7 B3 B8 C4
A3 A8 B4 B9 C5
A4 A9 B5 C1 C6
A5 B1 B6 C2 C7
NULL NULL NULL NULL C8
测试 2
EXECUTE #GenerateMatrix @Records = 8, @Columns = 4;
0 1 2 3
----------------------------
A1 A3 A5 A7
A2 A4 A6 A8
测试 3
EXECUTE #GenerateMatrix @Records = 40, @Columns = 8;
0 1 2 3 4 5 6 7
-----------------------------------------------------------
A1 A6 B2 B7 C3 C8 D4 D9
A2 A7 B3 B8 C4 C9 D5 E1
A3 A8 B4 B9 C5 D1 D6 E2
A4 A9 B5 C1 C6 D2 D7 E3
A5 B1 B6 C2 C7 D3 D8 E4
测试 4
EXECUTE #GenerateMatrix @Records = 50, @Columns = 6;
0 1 2 3 4 5
---------------------------------------
A1 A9 B8 C7 D6 E6
A2 B1 B9 C8 D7 E7
A3 B2 C1 C9 D8 E8
A4 B3 C2 D1 D9 E9
A5 B4 C3 D2 E1 F1
A6 B5 C4 D3 E2 F2
A7 B6 C5 D4 E3 F3
A8 B7 C6 D5 E4 F4
NULL NULL NULL NULL E5 F5
在任何测试中都没有丢失任何记录,也没有只有空值的行。