하위 쿼리의 여러 행을 단일 구분 필드로 "결합"하는 SQL Server 함수를 만드는 방법은 무엇입니까?[복제하다]

StackOverflow https://stackoverflow.com/questions/6899

문제

설명을 위해 다음과 같은 두 개의 테이블이 있다고 가정합니다.

VehicleID Name
1         Chuck
2         Larry

LocationID VehicleID City
1          1         New York
2          1         Seattle
3          1         Vancouver
4          2         Los Angeles
5          2         Houston

다음 결과를 반환하는 쿼리를 작성하고 싶습니다.

VehicleID Name    Locations
1         Chuck   New York, Seattle, Vancouver
2         Larry   Los Angeles, Houston

나는 이것이 서버 측 커서를 사용하여 수행될 수 있다는 것을 알고 있습니다. 즉:

DECLARE @VehicleID int
DECLARE @VehicleName varchar(100)
DECLARE @LocationCity varchar(100)
DECLARE @Locations varchar(4000)
DECLARE @Results TABLE
(
  VehicleID int
  Name varchar(100)
  Locations varchar(4000)
)

DECLARE VehiclesCursor CURSOR FOR
SELECT
  [VehicleID]
, [Name]
FROM [Vehicles]

OPEN VehiclesCursor

FETCH NEXT FROM VehiclesCursor INTO
  @VehicleID
, @VehicleName
WHILE @@FETCH_STATUS = 0
BEGIN

  SET @Locations = ''

  DECLARE LocationsCursor CURSOR FOR
  SELECT
    [City]
  FROM [Locations]
  WHERE [VehicleID] = @VehicleID

  OPEN LocationsCursor

  FETCH NEXT FROM LocationsCursor INTO
    @LocationCity
  WHILE @@FETCH_STATUS = 0
  BEGIN
    SET @Locations = @Locations + @LocationCity

    FETCH NEXT FROM LocationsCursor INTO
      @LocationCity
  END
  CLOSE LocationsCursor
  DEALLOCATE LocationsCursor

  INSERT INTO @Results (VehicleID, Name, Locations) SELECT @VehicleID, @Name, @Locations

END     
CLOSE VehiclesCursor
DEALLOCATE VehiclesCursor

SELECT * FROM @Results

그러나 보시다시피 이를 위해서는 많은 양의 코드가 필요합니다.내가 원하는 것은 다음과 같은 작업을 수행할 수 있는 일반 함수입니다.

SELECT VehicleID
     , Name
     , JOIN(SELECT City FROM Locations WHERE VehicleID = Vehicles.VehicleID, ', ') AS Locations
FROM Vehicles

이것이 가능한가?아니면 비슷한 것?

도움이 되었습니까?

해결책

SQL Server 2005를 사용하는 경우 FOR XML PATH 명령을 사용할 수 있습니다.

SELECT [VehicleID]
     , [Name]
     , (STUFF((SELECT CAST(', ' + [City] AS VARCHAR(MAX)) 
         FROM [Location] 
         WHERE (VehicleID = Vehicle.VehicleID) 
         FOR XML PATH ('')), 1, 2, '')) AS Locations
FROM [Vehicle]

커서를 사용하는 것보다 훨씬 쉽고 꽤 잘 작동하는 것 같습니다.

다른 팁

참고하세요 매트의 코드 문자열 끝에 추가 쉼표가 생깁니다.Lance 게시물의 링크에 표시된 대로 COALESCE(또는 해당 문제의 경우 ISNULL)를 사용하면 비슷한 방법을 사용하지만 제거할 추가 쉼표가 남지 않습니다.완전성을 기하기 위해 sqlteam.com에 있는 Lance 링크의 관련 코드는 다음과 같습니다.

DECLARE @EmployeeList varchar(100)
SELECT @EmployeeList = COALESCE(@EmployeeList + ', ', '') + 
    CAST(EmpUniqueID AS varchar(5))
FROM SalesCallsEmployees
WHERE SalCal_UniqueID = 1

하나의 쿼리 내에서 이를 수행할 수 있는 방법이 없다고 생각하지만 임시 변수를 사용하여 다음과 같은 트릭을 수행할 수 있습니다.

declare @s varchar(max)
set @s = ''
select @s = @s + City + ',' from Locations

select @s

커서 위를 걷는 것보다 확실히 코드가 적고 아마도 더 효율적일 것입니다.

단일 SQL 쿼리에서 FOR XML 절을 사용하지 않습니다.
공통 테이블 표현식은 결과를 재귀적으로 연결하는 데 사용됩니다.

-- rank locations by incrementing lexicographical order
WITH RankedLocations AS (
  SELECT
    VehicleID,
    City,
    ROW_NUMBER() OVER (
        PARTITION BY VehicleID 
        ORDER BY City
    ) Rank
  FROM
    Locations
),
-- concatenate locations using a recursive query
-- (Common Table Expression)
Concatenations AS (
  -- for each vehicle, select the first location
  SELECT
    VehicleID,
    CONVERT(nvarchar(MAX), City) Cities,
    Rank
  FROM
    RankedLocations
  WHERE
    Rank = 1

  -- then incrementally concatenate with the next location
  -- this will return intermediate concatenations that will be 
  -- filtered out later on
  UNION ALL

  SELECT
    c.VehicleID,
    (c.Cities + ', ' + l.City) Cities,
    l.Rank
  FROM
    Concatenations c -- this is a recursion!
    INNER JOIN RankedLocations l ON
        l.VehicleID = c.VehicleID 
        AND l.Rank = c.Rank + 1
),
-- rank concatenation results by decrementing length 
-- (rank 1 will always be for the longest concatenation)
RankedConcatenations AS (
  SELECT
    VehicleID,
    Cities,
    ROW_NUMBER() OVER (
        PARTITION BY VehicleID 
        ORDER BY Rank DESC
    ) Rank
  FROM 
    Concatenations
)
-- main query
SELECT
  v.VehicleID,
  v.Name,
  c.Cities
FROM
  Vehicles v
  INNER JOIN RankedConcatenations c ON 
    c.VehicleID = v.VehicleID 
    AND c.Rank = 1

내가 볼 수있는 것에서 FOR XML (이전에 게시한 대로) OP와 마찬가지로 다른 열(대부분 그럴 것이라고 생각함)을 선택하려는 경우 이를 수행할 수 있는 유일한 방법입니다.사용 COALESCE(@var... 다른 열의 포함을 허용하지 않습니다.

업데이트:덕분에 프로그래밍 솔루션 .net "후행"쉼표를 제거하는 방법이 있습니다.선행 쉼표로 만들고 STUFF MSSQL의 함수에서는 아래와 같이 첫 번째 문자(선행 쉼표)를 빈 문자열로 바꿀 수 있습니다.

stuff(
    (select ',' + Column 
     from Table
         inner where inner.Id = outer.Id 
     for xml path('')
), 1,1,'') as Values

~ 안에 SQL 서버 2005

SELECT Stuff(
  (SELECT N', ' + Name FROM Names FOR XML PATH(''),TYPE)
  .value('text()[1]','nvarchar(max)'),1,2,N'')

SQL 서버 2016에서

당신은 사용할 수 있습니다 FOR JSON 구문

즉.

SELECT per.ID,
Emails = JSON_VALUE(
   REPLACE(
     (SELECT _ = em.Email FROM Email em WHERE em.Person = per.ID FOR JSON PATH)
    ,'"},{"_":"',', '),'$[0]._'
) 
FROM Person per

그리고 그 결과는 다음과 같을 것이다

Id  Emails
1   abc@gmail.com
2   NULL
3   def@gmail.com, xyz@gmail.com

데이터에 잘못된 XML 문자가 포함되어 있어도 작동합니다.

'"},{"":"'는 데이터에 '"},{"가 포함되어 있으므로 안전합니다.":"', "},{\"_\":\"로 이스케이프됩니다.

','를 문자열 구분 기호로 바꿀 수 있습니다.


그리고 SQL Server 2017에서는 Azure SQL 데이터베이스

새로운 것을 사용할 수 있습니다 STRING_AGG 함수

아래 코드는 SQL Server 2000/2005/2008에서 작동합니다.

CREATE FUNCTION fnConcatVehicleCities(@VehicleId SMALLINT)
RETURNS VARCHAR(1000) AS
BEGIN
  DECLARE @csvCities VARCHAR(1000)
  SELECT @csvCities = COALESCE(@csvCities + ', ', '') + COALESCE(City,'')
  FROM Vehicles 
  WHERE VehicleId = @VehicleId 
  return @csvCities
END

-- //Once the User defined function is created then run the below sql

SELECT VehicleID
     , dbo.fnConcatVehicleCities(VehicleId) AS Locations
FROM Vehicles
GROUP BY VehicleID

다음 함수를 만들어 해결책을 찾았습니다.

CREATE FUNCTION [dbo].[JoinTexts]
(
  @delimiter VARCHAR(20) ,
  @whereClause VARCHAR(1)
)
RETURNS VARCHAR(MAX)
AS 
BEGIN
    DECLARE @Texts VARCHAR(MAX)

    SELECT  @Texts = COALESCE(@Texts + @delimiter, '') + T.Texto
    FROM    SomeTable AS T
    WHERE   T.SomeOtherColumn = @whereClause

    RETURN @Texts
END
GO

용법:

SELECT dbo.JoinTexts(' , ', 'Y')

버전 참고:이 솔루션의 경우 호환성 수준이 90 이상으로 설정된 SQL Server 2005 이상을 사용해야 합니다.

이것 좀 봐 MSDN 기사 테이블의 열에서 가져온 문자열 값 집합을 연결하는 사용자 정의 집계 함수를 만드는 첫 번째 예입니다.

제가 추천하는 것은 추가된 쉼표를 생략하여 자신만의 임시 구분 기호를 사용할 수 있도록 하는 것입니다.

예제 1의 C# 버전을 참조하면 다음과 같습니다.

change:  this.intermediateResult.Append(value.Value).Append(',');
    to:  this.intermediateResult.Append(value.Value);

그리고

change:  output = this.intermediateResult.ToString(0, this.intermediateResult.Length - 1);
    to:  output = this.intermediateResult.ToString();

이렇게 하면 사용자 지정 집계를 사용할 때 다음과 같이 자체 구분 기호를 사용하거나 전혀 사용하지 않도록 선택할 수 있습니다.

SELECT dbo.CONCATENATE(column1 + '|') from table1

메모: 집계된 데이터의 양에 주의하세요.수천 개의 행이나 매우 큰 데이터 유형을 연결하려고 하면 "버퍼가 부족합니다."라는 .NET Framework 오류가 발생할 수 있습니다.

기타 답변의 경우 답변을 읽는 사람이 차량 테이블을 인지하고 차량 테이블과 데이터를 생성하여 솔루션을 테스트해야 합니다.

다음은 SQL Server "Information_Schema.Columns" 테이블을 사용하는 예입니다.이 솔루션을 사용하면 테이블을 생성하거나 데이터를 추가할 필요가 없습니다.이 예에서는 데이터베이스의 모든 테이블에 대해 쉼표로 구분된 열 이름 목록을 만듭니다.

SELECT
    Table_Name
    ,STUFF((
        SELECT ',' + Column_Name
        FROM INFORMATION_SCHEMA.Columns Columns
        WHERE Tables.Table_Name = Columns.Table_Name
        ORDER BY Column_Name
        FOR XML PATH ('')), 1, 1, ''
    )Columns
FROM INFORMATION_SCHEMA.Columns Tables
GROUP BY TABLE_NAME 

Mun의 답변이 저에게 효과적이지 않았기 때문에 해당 답변을 일부 변경하여 작동하게 했습니다.이것이 누군가에게 도움이 되기를 바랍니다.SQL Server 2012 사용:

SELECT [VehicleID]
     , [Name]
     , STUFF((SELECT DISTINCT ',' + CONVERT(VARCHAR,City) 
         FROM [Location] 
         WHERE (VehicleID = Vehicle.VehicleID) 
         FOR XML PATH ('')), 1, 2, '') AS Locations
FROM [Vehicle]

이 쿼리를 사용해 보세요

SELECT v.VehicleId, v.Name, ll.LocationList
FROM Vehicles v 
LEFT JOIN 
    (SELECT 
     DISTINCT
        VehicleId,
        REPLACE(
            REPLACE(
                REPLACE(
                    (
                        SELECT City as c 
                        FROM Locations x 
                        WHERE x.VehicleID = l.VehicleID FOR XML PATH('')
                    ),    
                    '</c><c>',', '
                 ),
             '<c>',''
            ),
        '</c>', ''
        ) AS LocationList
    FROM Locations l
) ll ON ll.VehicleId = v.VehicleId

SQL Server 2005를 실행하는 경우 다음을 작성할 수 있습니다. 사용자 정의 CLR 집계 함수 이것을 처리하기 위해.

C# 버전:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Text;
using Microsoft.SqlServer.Server;
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedAggregate(Format.UserDefined,MaxByteSize=8000)]
public class CSV:IBinarySerialize
{
    private StringBuilder Result;
    public void Init() {
        this.Result = new StringBuilder();
    }

    public void Accumulate(SqlString Value) {
        if (Value.IsNull) return;
        this.Result.Append(Value.Value).Append(",");
    }
    public void Merge(CSV Group) {
        this.Result.Append(Group.Result);
    }
    public SqlString Terminate() {
        return new SqlString(this.Result.ToString());
    }
    public void Read(System.IO.BinaryReader r) {
        this.Result = new StringBuilder(r.ReadString());
    }
    public void Write(System.IO.BinaryWriter w) {
        w.Write(this.Result.ToString());
    }
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top