Можно ли объединить значения столбца в строку, используя CTE? - PullRequest
5 голосов
/ 30 июня 2010

Скажем, у меня есть следующая таблица:

id|myId|Name
-------------
1 | 3  |Bob 
2 | 3  |Chet
3 | 3  |Dave
4 | 4  |Jim
5 | 4  |Jose
-------------

Можно ли использовать рекурсивный CTE для генерации следующего вывода:

3 | Bob, Chet, Date
4 | Jim, Jose

Я играл снемного, но не смог заставить его работать.Буду ли я лучше использовать другую технику?

Ответы [ 3 ]

6 голосов
/ 30 июня 2010

Я не рекомендую это, но мне удалось это решить.

Таблица:

CREATE TABLE [dbo].[names](
    [id] [int] NULL,
    [myId] [int] NULL,
    [name] [char](25) NULL
) ON [PRIMARY]

Данные:

INSERT INTO names values (1,3,'Bob')
INSERT INTO names values 2,3,'Chet')
INSERT INTO names values 3,3,'Dave')
INSERT INTO names values 4,4,'Jim')
INSERT INTO names values 5,4,'Jose')
INSERT INTO names values 6,5,'Nick')

Запрос:

WITH CTE (id, myId, Name, NameCount)
     AS (SELECT id,
                myId,
                Cast(Name AS VARCHAR(225)) Name,
                1                          NameCount
         FROM   (SELECT Row_number() OVER (PARTITION BY myId ORDER BY myId) AS id,
                        myId,
                        Name
                 FROM   names) e
         WHERE  id = 1
         UNION ALL
         SELECT e1.id,
                e1.myId,
                Cast(Rtrim(CTE.Name) + ',' + e1.Name AS VARCHAR(225)) AS Name,
                CTE.NameCount + 1                                     NameCount
         FROM   CTE
                INNER JOIN (SELECT Row_number() OVER (PARTITION BY myId ORDER BY myId) AS id,
                                   myId,
                                   Name
                            FROM   names) e1
                  ON e1.id = CTE.id + 1
                     AND e1.myId = CTE.myId)
SELECT myID,
       Name
FROM   (SELECT myID,
               Name,
               (Row_number() OVER (PARTITION BY myId ORDER BY namecount DESC)) AS id
        FROM   CTE) AS p
WHERE  id = 1 

По запросу, вот метод XML:

SELECT myId,
       STUFF((SELECT ',' + rtrim(convert(char(50),Name))
        FROM   namestable b
        WHERE  a.myId = b.myId
        FOR XML PATH('')),1,1,'') Names
FROM   namestable a
GROUP BY myId
2 голосов
/ 30 июня 2010

CTE - это просто прославленная производная таблица с некоторыми дополнительными функциями (такими как рекурсия). Вопрос в том, можете ли вы использовать рекурсию для этого? Возможно, но он использует отвертку, чтобы вбить гвоздь. Хорошая часть работы с XML-путем (видно из первого ответа) состоит в том, что она объединит группирование столбца MyId и конкатенацию строк.

Как бы вы связали список строк, используя CTE? Я не думаю, что это его цель.

1 голос
/ 12 июля 2017

CTE - это просто временно созданная связь (обе таблицы и представления являются отношениями), которая существует только для «жизни» текущего запроса.

Я играл с именами CTE и полемимена.Мне действительно не нравится многократное использование имен полей, таких как id ;Я склонен думать, что это сбивает с толку.И поскольку единственное использование для names.id - это ORDER BY в первом операторе ROW_NUMBER (), я не буду использовать его в дальнейшем.

WITH namesNumbered as (
    select myId, Name,
        ROW_NUMBER() OVER (
            PARTITION BY myId 
            ORDER BY id
        ) as nameNum
    FROM names
)
, namesJoined(myId, Name, nameCount) as (
    SELECT myId,
        Cast(Name AS VARCHAR(225)),
        1
    FROM namesNumbered nn1
    WHERE nameNum = 1
    UNION ALL
    SELECT nn2.myId,
        Cast(
            Rtrim(nc.Name) + ',' + nn2.Name
            AS VARCHAR(225)
        ),
        nn.nameNum
    FROM namesJoined nj
    INNER JOIN namesNumbered nn2 ON nn2.myId = nj.myId
        and nn2.nameNum = nj.nameCount + 1
)
SELECT myId, Name
FROM (
    SELECT myID, Name,
        ROW_NUMBER() OVER (
            PARTITION BY myId
            ORDER BY nameCount DESC
        ) AS finalSort
    FROM namesJoined
) AS tmp
WHERE finalSort = 1

Первый CTE, namesNumbered , возвращает два поля, которые нас интересуют, и значение сортировки;мы не можем просто использовать names.id для этого, потому что нам нужно, чтобы каждое значение myId имело значения 1, 2, .... names.id будет иметь 1, 2 ... для myId = 1, но будет иметь более высокое начальное значение для последующих значений myId .

Второй CTE, namesJoined , должен иметь имена полей, указанные в сигнатуре CTE, поскольку он будет рекурсивным.Базовый случай (часть перед UNION ALL) дает нам записи, где nameNum = 1. Мы должны CAST () поле Name , поскольку оно будет увеличиваться при последующих проходах;нам нужно убедиться, что мы CAST () достаточно велики для обработки любого из выходных данных;мы всегда можем TRIM () позже, если это необходимо.Нам не нужно указывать псевдонимы для полей, потому что подпись CTE обеспечивает их.Рекурсивный регистр (после UNION ALL) объединяет текущий CTE с предыдущим, гарантируя, что последующие проходы будут использовать все более высокие значения nameNum .Нам нужно TRIM () для предыдущих итераций Name , затем добавить запятую и новое Name .Результатом будет, неявно, CAST () с более широким полем.

В конечном запросе будут получены только те поля, которые нам нужны ( myId , Name ) ив подзапросе многократно сортирует записи так, чтобы наибольшее значение namesJoined.nameCount получило 1 как значение finalSort .Затем мы сообщаем предложению WHERE, что нужно предоставить нам только одну эту запись (для каждого значения myId ).

Да, я присвоил псевдониму подзапрос как tmp , то естьпримерно настолько общий, насколько вы можете получить.Большинство механизмов SQL требуют, чтобы вы указали псевдониму подзапроса, даже если это единственное отношение, видимое в этот момент.

...