CONCAT (столбец) OVER (PARTITION BY...)? Групповые конкатенации строк без группировки самого результата

Мне нужен способ сделать конкатенацию всех строк (для каждой группы) в виде оконной функции, например, как вы можете сделать COUNT(*) OVER(PARTITION BY...), и подсчет совокупности всех строк на группу будет повторяться через каждую конкретную группу. Мне нужно нечто подобное, но строка конкатенации всех значений для каждой группы повторяется в каждой группе.

Вот некоторые примеры данных и мой желаемый результат, чтобы лучше проиллюстрировать мою проблему:

grp  |  val
------------
1    |  a
1    |  b
1    |  c
1    |  d
2    |  x
2    |  y
2    |  z

И вот что мне нужно (желаемый результат):

grp  |   val  |  groupcnct
---------------------------------
1    |   a    |  abcd
1    |   b    |  abcd
1    |   c    |  abcd
1    |   d    |  abcd
2    |   x    |  xyz
2    |   y    |  xyz
2    |   z    |  xyz

Вот очень сложная часть этой проблемы:

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

Я полностью понимаю, что можно сделать что-то вроде:

SELECT      a.*, b.groupcnct
FROM        tbl a
CROSS APPLY (
            SELECT STUFF((
                        SELECT '' + aa.val 
                        FROM   tbl aa
                        WHERE  aa.grp = a.grp
                        FOR XML PATH('')
                   ), 1, 0, '') AS groupcnct
            ) b

Но, как вы можете видеть, это ссылается на tbl два раза в запросе.

Я могу ссылаться только на tbl один раз, поэтому мне интересно, возможно ли оконтирование групповой конкатенации (я немного новичок в TSQL, поскольку я исхожу из фона MySQL, поэтому не уверен, что что-то подобное можно сделать).


Создать таблицу:

CREATE TABLE tbl
    (grp int, val varchar(1));

INSERT INTO tbl
    (grp, val)
VALUES
    (1, 'a'),
    (1, 'b'),
    (1, 'c'),
    (1, 'd'),
    (2, 'x'),
    (2, 'y'),
    (2, 'z');

Ответ 1

Я попытался использовать чистый подход CTE: Каков наилучший способ формирования строкового значения, используя столбец из таблицы с строками с одинаковым идентификатором? Думая, что это быстрее

Но в этом тесте указано, что лучше использовать подзапрос (или CROSS APPLY) результатов от XML PATH, поскольку они быстрее: Каков наилучший способ формирования строкового значения используя столбец из таблицы со строками с одинаковым идентификатором?

Ответ 2

DECLARE @tbl TABLE
(
    grp INT
    ,val VARCHAR(1)
); 

BEGIN
 INSERT INTO @tbl(grp, val)
VALUES
    (1, 'a'),
    (1, 'b'),
    (1, 'c'),
    (1, 'd'),
    (2, 'x'),
    (2, 'y'),
    (2, 'z');
END;
----------- Your Required Query
SELECT  ST2.grp, 
SUBSTRING(
            (
                SELECT ','+ST1.val  AS [text()]
                FROM @tbl ST1
                WHERE ST1.grp = ST2.grp
                ORDER BY ST1.grp
                For XML PATH ('')
            ), 2, 1000
       ) groupcnct
FROM @tbl ST2

Ответ 3

Можно ли вместо этого просто поставить свои вещи в выбор или вы столкнулись с той же проблемой? (я заменил "tbl" на "TEMP.TEMP123")

Select 
A.*
, [GROUPCNT] = STUFF((
                        SELECT '' + aa.val 
                        FROM  TEMP.TEMP123 AA
                        WHERE  aa.grp = a.grp
                        FOR XML PATH('')
                   ), 1, 0, '') 


 from TEMP.TEMP123 A

Это сработало для меня - хотел посмотреть, сработало ли это и для вас.

Ответ 4

В sql 2017 вы можете использовать функцию STRING_AGG:

SELECT STRING_AGG(T.val, ',') AS val
    , T.grp
FROM @tbl AS T
GROUP BY T.grp

Ответ 5

Я знаю, что этот пост старый, но на всякий случай кому-то все еще интересно, вы можете создать скалярную функцию, которая объединяет значения строк.

IF OBJECT_ID('dbo.fnConcatRowsPerGroup','FN') IS NOT NULL
DROP FUNCTION dbo.fnConcatRowsPerGroup
GO
CREATE FUNCTION dbo.fnConcatRowsPerGroup 
    (@grp as int) RETURNS VARCHAR(MAX)
AS 
BEGIN
    DECLARE @val AS VARCHAR(MAX)

    SELECT @val = COALESCE(@val,'')+val
    FROM tbl
    WHERE grp = @grp

    RETURN @val;
END
GO


select *, dbo.fnConcatRowsPerGroup(grp)
from tbl

Вот набор результатов, полученный при запросе таблицы-примера:

grp  |   val  |  (No column name)
---------------------------------
1    |   a    |  abcd
1    |   b    |  abcd
1    |   c    |  abcd
1    |   d    |  abcd
2    |   x    |  xyz
2    |   y    |  xyz
2    |   z    |  xyz