다른 행의 문자열을 단일 행으로 집계하는 방법을 찾고 있습니다. 여러 곳에서이 작업을 수행하려고하므로이를 용이하게하는 기능이 있으면 좋을 것입니다. 나는 COALESCE
및 을 사용하여 솔루션을 시도 FOR XML
했지만 그들은 나를 위해 그것을 자르지 않습니다.
문자열 집계는 다음과 같은 작업을 수행합니다.
id | Name Result: id | Names
-- - ---- -- - -----
1 | Matt 1 | Matt, Rocks
1 | Rocks 2 | Stylus
2 | Stylus
나는 한 번 봐 찍은 CLR 정의 집계 함수 를 대체 COALESCE
하고 FOR XML
,하지만 분명히 SQL 애저는 하지 않습니다 내가의 전체를 많이 해결할 사용할 수있는 알고 있기 때문에 나를 위해 고통 CLR 정의 물건을 지원 나를위한 문제.
가능한 해결 방법, 또는 (CLR과 같은 최적으로되지 않을 수도 있지만, 마찬가지로 최적의 방법이있다 헤이 나는 내 물건을 집계하는 데 사용할 수있는 내가 무엇을 얻을 수 할게요)는?
답변
해결책
최적 의 정의 는 다를 수 있지만 다음은 Azure에서 제대로 작동하는 일반 Transact SQL을 사용하여 다른 행의 문자열을 연결하는 방법입니다.
;WITH Partitioned AS
(
SELECT
ID,
Name,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Name) AS NameNumber,
COUNT(*) OVER (PARTITION BY ID) AS NameCount
FROM dbo.SourceTable
),
Concatenated AS
(
SELECT
ID,
CAST(Name AS nvarchar) AS FullName,
Name,
NameNumber,
NameCount
FROM Partitioned
WHERE NameNumber = 1
UNION ALL
SELECT
P.ID,
CAST(C.FullName + ', ' + P.Name AS nvarchar),
P.Name,
P.NameNumber,
P.NameCount
FROM Partitioned AS P
INNER JOIN Concatenated AS C
ON P.ID = C.ID
AND P.NameNumber = C.NameNumber + 1
)
SELECT
ID,
FullName
FROM Concatenated
WHERE NameNumber = NameCount
설명
접근 방식은 세 단계로 요약됩니다.
-
연결을 위해 필요에 따라 행을 사용
OVER
하고PARTITION
그룹화하고 순서를 지정합니다. 결과는Partitioned
CTE입니다. 나중에 결과를 필터링하기 위해 각 파티션의 행 수를 유지합니다. -
재귀 CTE (
Concatenated
)를 사용하면 행 번호 (NameNumber
열)를 반복Name
하여FullName
열에 값을 추가 합니다 . -
가장 높은 결과를 제외한 모든 결과를 필터링합니다
NameNumber
.
이 쿼리를 예측 가능하게 만들려면 그룹화 (예 : 시나리오에서 동일한 행 ID
이 연결됨)와 정렬 (연결하기 전에 문자열을 알파벳순으로 정렬한다고 가정)을 모두 정의 해야합니다.
다음 데이터를 사용하여 SQL Server 2012에서 솔루션을 빠르게 테스트했습니다.
INSERT dbo.SourceTable (ID, Name)
VALUES
(1, 'Matt'),
(1, 'Rocks'),
(2, 'Stylus'),
(3, 'Foo'),
(3, 'Bar'),
(3, 'Baz')
쿼리 결과 :
ID FullName
----------- ------------------------------
2 Stylus
3 Bar, Baz, Foo
1 Matt, Rocks
답변
아래와 같이 FOR XML PATH를 사용하는 방법이 정말 느리나요? Itzik Ben-Gan은 T-SQL Querying 책에서이 방법이 좋은 성능을 가지고 있다고 썼습니다 (Mr. Ben-Gan은 제 생각에 신뢰할 수있는 출처입니다).
create table #t (id int, name varchar(20))
insert into #t
values (1, 'Matt'), (1, 'Rocks'), (2, 'Stylus')
select id
,Names = stuff((select ', ' + name as [text()]
from #t xt
where xt.id = t.id
for xml path('')), 1, 2, '')
from #t t
group by id
답변
이것을 발견 한 우리를 위해 Azure SQL Database를 사용하지 않습니다.:
STRING_AGG()
PostgreSQL, SQL Server 2017 및 Azure SQL
https://www.postgresql.org/docs/current/static/functions-aggregate.html
https://docs.microsoft.com/en-us/sql/t-sql/ 함수 / 문자열 -agg-transact-sql
GROUP_CONCAT()
MySQL에서
http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html#function_group-concat
(Azure 업데이트를 위해 @Brianjorden 및 @milanio에게 감사드립니다)
예제 코드 :
select Id
, STRING_AGG(Name, ', ') Names
from Demo
group by Id
SQL Fiddle : http://sqlfiddle.com/#!18/89251/1
답변
@serge 대답은 정확하지만 xmlpath와의 시간 소비를 비교했으며 xmlpath가 너무 빠르다는 것을 알았습니다. 비교 코드를 작성하고 직접 확인할 수 있습니다. 이것은 @serge 방식입니다.
DECLARE @startTime datetime2;
DECLARE @endTime datetime2;
DECLARE @counter INT;
SET @counter = 1;
set nocount on;
declare @YourTable table (ID int, Name nvarchar(50))
WHILE @counter < 1000
BEGIN
insert into @YourTable VALUES (ROUND(@counter/10,0), CONVERT(NVARCHAR(50), @counter) + 'CC')
SET @counter = @counter + 1;
END
SET @startTime = GETDATE()
;WITH Partitioned AS
(
SELECT
ID,
Name,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Name) AS NameNumber,
COUNT(*) OVER (PARTITION BY ID) AS NameCount
FROM @YourTable
),
Concatenated AS
(
SELECT ID, CAST(Name AS nvarchar) AS FullName, Name, NameNumber, NameCount FROM Partitioned WHERE NameNumber = 1
UNION ALL
SELECT
P.ID, CAST(C.FullName + ', ' + P.Name AS nvarchar), P.Name, P.NameNumber, P.NameCount
FROM Partitioned AS P
INNER JOIN Concatenated AS C ON P.ID = C.ID AND P.NameNumber = C.NameNumber + 1
)
SELECT
ID,
FullName
FROM Concatenated
WHERE NameNumber = NameCount
SET @endTime = GETDATE();
SELECT DATEDIFF(millisecond,@startTime, @endTime)
--Take about 54 milliseconds
그리고 이것은 xmlpath 방식입니다.
DECLARE @startTime datetime2;
DECLARE @endTime datetime2;
DECLARE @counter INT;
SET @counter = 1;
set nocount on;
declare @YourTable table (RowID int, HeaderValue int, ChildValue varchar(5))
WHILE @counter < 1000
BEGIN
insert into @YourTable VALUES (@counter, ROUND(@counter/10,0), CONVERT(NVARCHAR(50), @counter) + 'CC')
SET @counter = @counter + 1;
END
SET @startTime = GETDATE();
set nocount off
SELECT
t1.HeaderValue
,STUFF(
(SELECT
', ' + t2.ChildValue
FROM @YourTable t2
WHERE t1.HeaderValue=t2.HeaderValue
ORDER BY t2.ChildValue
FOR XML PATH(''), TYPE
).value('.','varchar(max)')
,1,2, ''
) AS ChildValues
FROM @YourTable t1
GROUP BY t1.HeaderValue
SET @endTime = GETDATE();
SELECT DATEDIFF(millisecond,@startTime, @endTime)
--Take about 4 milliseconds
답변
업데이트 : Ms SQL Server 2017+, Azure SQL Database
다음을 사용할 수 있습니다 STRING_AGG
..
OP의 요청에 대한 사용법은 매우 간단합니다.
SELECT id, STRING_AGG(name, ', ') AS names
FROM some_table
GROUP BY id
글쎄요, 제 예전 무답은 제대로 삭제되었지만 (아래에 그대로 남아 있음), 만약 누군가가 미래에 여기에 착륙한다면 좋은 소식이 있습니다. Azure SQL Database에서도 STRING_AGG ()를 구현했습니다. 이 게시물에서 원래 요청 된 기능을 기본 및 내장 지원과 함께 제공해야합니다. @hrobky는 이전에이를 당시 SQL Server 2016 기능으로 언급했습니다.
— 이전 게시물 : @hrobky에 직접 답장하기에는 평판이 충분하지 않지만 STRING_AGG는 훌륭해 보이지만 현재 SQL Server 2016 vNext에서만 사용할 수 있습니다. 조만간 Azure SQL Datababse를 따르기를 바랍니다.
답변
+ =를 사용하여 문자열을 연결할 수 있습니다. 예를 들면 다음과 같습니다.
declare @test nvarchar(max)
set @test = ''
select @test += name from names
@test를 선택하면 연결된 모든 이름이 제공됩니다.
답변
나는 Serge의 대답이 매우 유망하다는 것을 알았지 만 작성된대로 성능 문제가 발생했습니다. 그러나 이중 CTE 테이블을 포함하지 않고 임시 테이블을 사용하도록 재구성했을 때 1000 개의 결합 된 레코드에 대해 성능이 1 분 40 초에서 1 초 미만으로 떨어졌습니다. 이전 버전의 SQL Server에서 FOR XML없이이 작업을 수행해야하는 모든 사용자를위한 것입니다.
DECLARE @STRUCTURED_VALUES TABLE (
ID INT
,VALUE VARCHAR(MAX) NULL
,VALUENUMBER BIGINT
,VALUECOUNT INT
);
INSERT INTO @STRUCTURED_VALUES
SELECT ID
,VALUE
,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY VALUE) AS VALUENUMBER
,COUNT(*) OVER (PARTITION BY ID) AS VALUECOUNT
FROM RAW_VALUES_TABLE;
WITH CTE AS (
SELECT SV.ID
,SV.VALUE
,SV.VALUENUMBER
,SV.VALUECOUNT
FROM @STRUCTURED_VALUES SV
WHERE VALUENUMBER = 1
UNION ALL
SELECT SV.ID
,CTE.VALUE + ' ' + SV.VALUE AS VALUE
,SV.VALUENUMBER
,SV.VALUECOUNT
FROM @STRUCTURED_VALUES SV
JOIN CTE
ON SV.ID = CTE.ID
AND SV.VALUENUMBER = CTE.VALUENUMBER + 1
)
SELECT ID
,VALUE
FROM CTE
WHERE VALUENUMBER = VALUECOUNT
ORDER BY ID
;