CONCAT(column) OVER(PARTITION BY…)? Group-concaten

2020-07-06 03:25发布

I need a way to make a concatenation of all rows (per group) in a kind of window function like how you can do COUNT(*) OVER(PARTITION BY...) and the aggregate count of all rows per group will repeat across each particular group. I need something similar but a string concatenation of all values per group repeated across each group.

Here is some example data and my desired result to better illustrate my problem:

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

And here is what I need (the desired result):

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

Here is the really tricky part of this problem:

My particular situation prevents me from being able to reference the same table twice (I'm actually doing this within a recursive CTE, so I can't do a self-join of the CTE or it will throw an error).

I'm fully aware that one can do something like:

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

But as you can see, that is referencing tbl two times in the query.

I can only reference tbl once, hence why I'm wondering if windowing the group-concatenation is possible (I'm a bit new to TSQL since I come from a MySQL background, so not sure if something like that can be done).


Create Table:

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');

5条回答
干净又极端
2楼-- · 2020-07-06 03:39

I know this post is old, but just in case, someone is still wondering, you can create scalar function that concatenates row values.

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

Here is the result set I got from querying a sample table:

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
查看更多
何必那么认真
3楼-- · 2020-07-06 03:44

In sql 2017 you can use STRING_AGG function:

SELECT STRING_AGG(T.val, ',') AS val
    , T.grp
FROM @tbl AS T
GROUP BY T.grp
查看更多
Deceive 欺骗
4楼-- · 2020-07-06 03:45

I tried using pure CTE approach: Which is the best way to form the string value using column from a Table with rows having same ID? Thinking it is faster

But the benchmark tells otherwise, it's better to use subquery(or CROSS APPLY) results from XML PATH as they are faster: Which is the best way to form the string value using column from a Table with rows having same ID?

查看更多
爷的心禁止访问
5楼-- · 2020-07-06 03:46

Is it possible for you to just put your stuff in the select instead or do you run into the same issue? (i replaced 'tbl' with '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

This worked for me -- wanted to see if this worked for you too.

查看更多
不美不萌又怎样
6楼-- · 2020-07-06 03:53
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
查看更多
登录 后发表回答