SQL 服务器将三个不同的列连接成逗号分隔的无重复值
SQL Server Concatenate three different columns into a Comma-Separated without repeated values
下一个table是我在SQL服务器的问题的简化:
ID COLUMN_A COLUMN_B COLUMN_C
-------------------------------------
1 A B C
1 A B D
1 B C D
我想得到一个组,其中的列由逗号连接且没有重复值。我尝试使用 STRING_AGG() 但它 returns:
ID COLUMN_A COLUMN_B COLUMN_C
-------------------------------------
1 A, A, B B, B, C C, D, D
这是我做过的查询:
SELECT ID, STRING_AGG(COLUMN_A, ', ') AS COL_A, STRING_AGG(COLUMN_B, ', ') AS COL_B,
STRING_AGG(COLUMN_C, ', ') AS COL_C
FROM MYTABLE
GROUP BY ID;
我想要下一个结果:
ID COLUMN_A COLUMN_B COLUMN_C
-------------------------------------
1 A, B B, C C, D
谢谢!
不幸的是,string_agg(distinct)
不起作用(还)。但是你可以做一些更复杂的事情:
SELECT ID,
STRING_AGG(CASE WHEN seqnum_a = 1 THEN COLUMN_A, END ', ') AS COLUMN_A,
STRING_AGG(CASE WHEN seqnum_b = 1 THEN COLUMN_B, END ', ') AS COLUMN_B,
STRING_AGG(CASE WHEN seqnum_c = 1 THEN COLUMN_C, END ', ') AS COLUMN_C
FROM (SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY ID, COLUMN_A ORDER BY ID) as seqnum_a,
ROW_NUMBER() OVER (PARTITION BY ID, COLUMN_B ORDER BY ID) as seqnum_b,
ROW_NUMBER() OVER (PARTITION BY ID, COLUMN_C ORDER BY ID) as seqnum_c
FROM MYTABLE t
) t
GROUP BY ID;
因此,虽然 STRING_AGG()
不会删除重复项,但它会忽略 NULL
个值。
这是 XML 和基于 XQuery 的解决方案。
SQL
-- DDL and sample data population, start
DECLARE @tbl TABLE (ID INT, COLUMN_A CHAR(1), COLUMN_B CHAR(1), COLUMN_C CHAR(1));
INSERT INTO @tbl (ID, COLUMN_A, COLUMN_B, COLUMN_C)
VALUES
(1,'A','B','C'),
(1,'A','B','D'),
(1,'B','C','D');
-- DDL and sample data population, end
DECLARE @separator CHAR(1) = ',';
;WITH rs AS
(
SELECT ID
, CAST('<root><r><![CDATA[' +
REPLACE(STRING_AGG(COLUMN_A, ','), @separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML) AS COL_A
, CAST('<root><r><![CDATA[' +
REPLACE(STRING_AGG(COLUMN_B, ','), @separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML) AS COL_B
, CAST('<root><r><![CDATA[' +
REPLACE(STRING_AGG(COLUMN_c, ','), @separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML) AS COL_C
FROM @tbl
GROUP BY ID
)
SELECT rs.ID
, COL_A.query('for $i in distinct-values(/root/r/text())
return if ($i eq (distinct-values(/root/r/text())[last()])[1]) then $i
else concat($i, sql:variable("@separator"))
').value('.', 'NVARCHAR(MAX)') AS COL_A
, COL_B.query('for $i in distinct-values(/root/r/text())
return if ($i eq (distinct-values(/root/r/text())[last()])[1]) then $i
else concat($i, sql:variable("@separator"))
').value('.', 'NVARCHAR(MAX)') AS COL_B
, COL_C.query('for $i in distinct-values(/root/r/text())
return if ($i eq (distinct-values(/root/r/text())[last()])[1]) then $i
else concat($i, sql:variable("@separator"))
').value('.', 'NVARCHAR(MAX)') AS COL_C
FROM rs;
Output
+----+-------+-------+-------+
| ID | COL_A | COL_B | COL_C |
+----+-------+-------+-------+
| 1 | A, B | B, C | C, D |
+----+-------+-------+-------+
不使用 window functions
。 union
可能会减慢速度,但请尝试一下,看看您是否可以忍受这种性能。
with
cte1 (id, col, indicator) as
(select id, column_a, 'col1' from t union
select id, column_b, 'col2' from t union
select id, column_c, 'col3' from t),
cte2 (id, indicator, agg) as
(select id, indicator, string_agg(col,',')
from cte1
group by id, indicator)
select id,
max(case when indicator='col1' then agg end) as column_a,
max(case when indicator='col2' then agg end) as column_b,
max(case when indicator='col3' then agg end) as column_c
from cte2
group by id;
下一个table是我在SQL服务器的问题的简化:
ID COLUMN_A COLUMN_B COLUMN_C
-------------------------------------
1 A B C
1 A B D
1 B C D
我想得到一个组,其中的列由逗号连接且没有重复值。我尝试使用 STRING_AGG() 但它 returns:
ID COLUMN_A COLUMN_B COLUMN_C
-------------------------------------
1 A, A, B B, B, C C, D, D
这是我做过的查询:
SELECT ID, STRING_AGG(COLUMN_A, ', ') AS COL_A, STRING_AGG(COLUMN_B, ', ') AS COL_B,
STRING_AGG(COLUMN_C, ', ') AS COL_C
FROM MYTABLE
GROUP BY ID;
我想要下一个结果:
ID COLUMN_A COLUMN_B COLUMN_C
-------------------------------------
1 A, B B, C C, D
谢谢!
不幸的是,string_agg(distinct)
不起作用(还)。但是你可以做一些更复杂的事情:
SELECT ID,
STRING_AGG(CASE WHEN seqnum_a = 1 THEN COLUMN_A, END ', ') AS COLUMN_A,
STRING_AGG(CASE WHEN seqnum_b = 1 THEN COLUMN_B, END ', ') AS COLUMN_B,
STRING_AGG(CASE WHEN seqnum_c = 1 THEN COLUMN_C, END ', ') AS COLUMN_C
FROM (SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY ID, COLUMN_A ORDER BY ID) as seqnum_a,
ROW_NUMBER() OVER (PARTITION BY ID, COLUMN_B ORDER BY ID) as seqnum_b,
ROW_NUMBER() OVER (PARTITION BY ID, COLUMN_C ORDER BY ID) as seqnum_c
FROM MYTABLE t
) t
GROUP BY ID;
因此,虽然 STRING_AGG()
不会删除重复项,但它会忽略 NULL
个值。
这是 XML 和基于 XQuery 的解决方案。
SQL
-- DDL and sample data population, start
DECLARE @tbl TABLE (ID INT, COLUMN_A CHAR(1), COLUMN_B CHAR(1), COLUMN_C CHAR(1));
INSERT INTO @tbl (ID, COLUMN_A, COLUMN_B, COLUMN_C)
VALUES
(1,'A','B','C'),
(1,'A','B','D'),
(1,'B','C','D');
-- DDL and sample data population, end
DECLARE @separator CHAR(1) = ',';
;WITH rs AS
(
SELECT ID
, CAST('<root><r><![CDATA[' +
REPLACE(STRING_AGG(COLUMN_A, ','), @separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML) AS COL_A
, CAST('<root><r><![CDATA[' +
REPLACE(STRING_AGG(COLUMN_B, ','), @separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML) AS COL_B
, CAST('<root><r><![CDATA[' +
REPLACE(STRING_AGG(COLUMN_c, ','), @separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML) AS COL_C
FROM @tbl
GROUP BY ID
)
SELECT rs.ID
, COL_A.query('for $i in distinct-values(/root/r/text())
return if ($i eq (distinct-values(/root/r/text())[last()])[1]) then $i
else concat($i, sql:variable("@separator"))
').value('.', 'NVARCHAR(MAX)') AS COL_A
, COL_B.query('for $i in distinct-values(/root/r/text())
return if ($i eq (distinct-values(/root/r/text())[last()])[1]) then $i
else concat($i, sql:variable("@separator"))
').value('.', 'NVARCHAR(MAX)') AS COL_B
, COL_C.query('for $i in distinct-values(/root/r/text())
return if ($i eq (distinct-values(/root/r/text())[last()])[1]) then $i
else concat($i, sql:variable("@separator"))
').value('.', 'NVARCHAR(MAX)') AS COL_C
FROM rs;
Output
+----+-------+-------+-------+
| ID | COL_A | COL_B | COL_C |
+----+-------+-------+-------+
| 1 | A, B | B, C | C, D |
+----+-------+-------+-------+
不使用 window functions
。 union
可能会减慢速度,但请尝试一下,看看您是否可以忍受这种性能。
with
cte1 (id, col, indicator) as
(select id, column_a, 'col1' from t union
select id, column_b, 'col2' from t union
select id, column_c, 'col3' from t),
cte2 (id, indicator, agg) as
(select id, indicator, string_agg(col,',')
from cte1
group by id, indicator)
select id,
max(case when indicator='col1' then agg end) as column_a,
max(case when indicator='col2' then agg end) as column_b,
max(case when indicator='col3' then agg end) as column_c
from cte2
group by id;