Разделить один столбец на несколько столбцов в SQL-сервере в зависимости от количества записей - PullRequest
1 голос
/ 20 июня 2019

У меня есть около 28 записей (которые являются динамическими, то есть иногда нечетным числом записей и иногда четными) в одном столбце.

Хотелось бы разбить их на несколько столбцов на основе переменной. В каждом столбце должно быть 5 или 6 записей.

Примечание. Эти 5 или 6 записей являются переменными и должны быть изменены.

Пример таблицы ввода:

           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

Требуемая таблица вывода будет:

            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

Что я делаю не так? Как я могу достичь желаемого результата? Спасибо.

Ответы [ 3 ]

3 голосов
/ 20 июня 2019

Вам необходимо сначала выделить столбец для каждой записи, что можно сделать с помощью 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

ДОПОЛНЕНИЕ

Я настроил образец таблицы и использовал процедуру с динамическим SQL для простоты повторного использования, чтобы продемонстрировать это решение, похоже, оно работает для меня как ожидалось.

SET UP

-- 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

В любом из тестов отсутствуют пропущенные записи и нет строк с только нулевыми значениями.

Пример на DB Fiddle

1 голос
/ 20 июня 2019

Опытным путем мне удалось придумать следующий сводный запрос:

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;

enter image description here

Демо

Идея в том, что каждая группа (строка) определяется тем, в какой точке модуля 5 находится номер текущей строки. Столбец, или пивот, определяется числом, кратным 5, текущему номеру строки.

0 голосов
/ 20 июня 2019

То, что вы делаете неправильно, получает пять значений в Col,

(ROW_NUMBER() OVER (ORDER BY Col1) -1)%5 AS Col,

, но только PIVOTing для 2 из них.

 PIVOT (MAX([COL1]) FOR Col IN ([0], [1])) AS Pvt

То есть вы игнорируете все Col со значениями 2, 3 и 4.

...