SQL Server : 열에서 행으로


128

열을 행으로 변환하는 우아한 솔루션을 찾고 있습니다.

다음은 예입니다. 다음 스키마가 포함 된 테이블이 있습니다.

[ID] [EntityID] [Indicator1] [Indicator2] [Indicator3] ... [Indicator150]

결과로 얻고 싶은 것은 다음과 같습니다.

[ID] [EntityId] [IndicatorName] [IndicatorValue]

결과 값은 다음과 같습니다.

1 1 'Indicator1' 'Value of Indicator 1 for entity 1'
2 1 'Indicator2' 'Value of Indicator 2 for entity 1'
3 1 'Indicator3' 'Value of Indicator 3 for entity 1'
4 2 'Indicator1' 'Value of Indicator 1 for entity 2'

등등..

이게 말이 돼? T-SQL에서 어디를보고 어떻게해야하는지에 대한 제안이 있습니까?


2
피벗 / 피벗 해제를 아직 살펴 보셨습니까 ?
Josh Jay

마지막에는 bluefeet의 솔루션이 사용되었습니다. 우아하고 기능적입니다. 모두 감사합니다.
Sergei

답변:


247

UNPIVOT 함수를 사용하여 열을 행으로 변환 할 수 있습니다 .

select id, entityId,
  indicatorname,
  indicatorvalue
from yourtable
unpivot
(
  indicatorvalue
  for indicatorname in (Indicator1, Indicator2, Indicator3)
) unpiv;

피벗 해제하려는 열의 데이터 유형이 같아야 피벗을 적용하기 전에 데이터 유형을 변환해야 할 수 있습니다.

CROSS APPLYUNION ALL과 함께 사용 하여 열을 변환 할 수도 있습니다 .

select id, entityid,
  indicatorname,
  indicatorvalue
from yourtable
cross apply
(
  select 'Indicator1', Indicator1 union all
  select 'Indicator2', Indicator2 union all
  select 'Indicator3', Indicator3 union all
  select 'Indicator4', Indicator4 
) c (indicatorname, indicatorvalue);

SQL Server 버전에 따라 VALUES 절과 함께 CROSS APPLY를 사용할 수도 있습니다.

select id, entityid,
  indicatorname,
  indicatorvalue
from yourtable
cross apply
(
  values
  ('Indicator1', Indicator1),
  ('Indicator2', Indicator2),
  ('Indicator3', Indicator3),
  ('Indicator4', Indicator4)
) c (indicatorname, indicatorvalue);

마지막으로 피벗 해제 할 열이 150 개이고 전체 쿼리를 하드 코딩하지 않으려는 경우 동적 SQL을 사용하여 sql 문을 생성 할 수 있습니다.

DECLARE @colsUnpivot AS NVARCHAR(MAX),
   @query  AS NVARCHAR(MAX)

select @colsUnpivot 
  = stuff((select ','+quotename(C.column_name)
           from information_schema.columns as C
           where C.table_name = 'yourtable' and
                 C.column_name like 'Indicator%'
           for xml path('')), 1, 1, '')

set @query 
  = 'select id, entityId,
        indicatorname,
        indicatorvalue
     from yourtable
     unpivot
     (
        indicatorvalue
        for indicatorname in ('+ @colsunpivot +')
     ) u'

exec sp_executesql @query;

4
더 많은 볼트와 너트를 원하는 사람들을 위해 UNPIVOT. APPLY, Brad Schulz (및 후속 기사 ) 의이 2010 블로그 게시물은 아름답습니다.
ruffin

2
메시지 8167, 수준 16, 상태 1, 줄 147 열 "blahblah"의 유형이 UNPIVOT 목록에 지정된 다른 열의 유형과 충돌합니다.
JDPeckham

@JDPeckham 다른 데이터 유형이있는 경우 피벗 해제를 수행하기 전에 동일한 유형 및 길이로 변환해야합니다. 이에 대한 자세한 정보 는 다음과 같습니다 .
Taryn

xml 메소드는 & gt ;, & lt; 및. 또한 다음과 같이 다시 작성하여 성능을 크게 향상시킬 수 있습니다. select @colsUnpivot = stuff ((select ','+ quotename (C.column_name) as [text ()] as information_schema.columns from C, C.table_name = 'yourtable' 및 xml 경로에 대한 'Indicator %'와 같은 C.column_name ( '), type) .value ('text () [1] ','nvarchar (max) '), 1, 1,' ')
rrozema

24

잘 150 열이 있다면 UNPIVOT이 옵션이 아니라고 생각합니다. xml 트릭을 사용할 수 있습니다.

;with CTE1 as (
    select ID, EntityID, (select t.* for xml raw('row'), type) as Data
    from temp1 as t
), CTE2 as (
    select
         C.id, C.EntityID,
         F.C.value('local-name(.)', 'nvarchar(128)') as IndicatorName,
         F.C.value('.', 'nvarchar(max)') as IndicatorValue
    from CTE1 as c
        outer apply c.Data.nodes('row/@*') as F(C)
)
select * from CTE2 where IndicatorName like 'Indicator%'

sql fiddle demo

동적 SQL을 작성할 수도 있지만 xml이 더 좋습니다. 동적 SQL의 경우 테이블에서 직접 데이터를 선택할 수있는 권한이 있어야하며 이것이 항상 옵션은 아닙니다.

업데이트
의견에 큰 불꽃이 있기 때문에 xml / dynamic SQL의 장단점을 추가 할 것이라고 생각합니다. 나는 우아함과 추악함을 언급 할 수없는만큼 객관적이되도록 노력할 것입니다. 다른 장단점이 있다면 답변을 편집하거나 의견을 쓰십시오.

단점

  • 그것은 동적 SQL 만큼 빠르지 않습니다 . 거친 테스트 결과 xml이 동적보다 약 2.5 배 느리다는 것이 나왔습니다 (~ 250000 개의 행 테이블에 대한 하나의 쿼리 이므로이 추정치는 정확하지 않습니다). 원하는 경우 sqlfiddle 예를 보면 100000 개의 행에서 29 초 (xml) 대 14 초 (동적) 인 경우 직접 비교할 수 있습니다 .
  • xpath에 익숙하지 않은 사람들에게는 이해 하기 어려울 수 있습니다 .

찬성

  • 그것은의 동일한 범위 다른 쿼리로, 그것은 매우 편리 할 수 있습니다. 몇 가지 예가 떠오른다
    • 트리거 내부에서 쿼리 inserteddeleted테이블을 수행 할 수 있습니다 (동적으로는 불가능).
    • 사용자는 테이블에서 직접 선택에 대한 권한 이 필요하지 않습니다 . 내 말은 스토어드 프로 시저 계층이 있고 사용자에게 sp를 실행할 권한이 있지만 테이블을 직접 쿼리 할 권한이없는 경우에도 스토어드 프로 시저 내에서이 쿼리를 사용할 수 있습니다.
    • 범위에서 채운 테이블 변수쿼리 할 수 있습니다 (동적 SQL 내에서 전달하려면 대신 임시 테이블로 만들거나 유형을 작성하고이를 동적 SQL에 매개 변수로 전달해야합니다).
  • 함수 내 에서이 쿼리 를 수행 할 수 있습니다 (스칼라 또는 테이블 반환). 함수 내에서 동적 SQL을 사용할 수 없습니다.

2
테이블에서 데이터를 선택할 필요가없는 XML로 어떤 데이터를 선택합니까?
Aaron Bertrand '

1
예를 들어, 당신은 데이터 테이블에서,하지만 내가 절차 내부 XML을 위해 선택할 수 있도록 저장 프로 시저, 테이블 작업에 대한 선택하는 사용자 권한을 부여하지 않기로 결정 수 있습니다,하지만 난 동적 SQL을 사용하려면 몇 가지 해결 방법을 사용해야합니다
로마 Pekar

3
사용자가 코드를 실행할 수있게하려면 코드를 실행하는 데 필요한 모든 액세스 권한을 사용자에게 제공해야합니다. 답변의 사운드를 개선하기 위해 존재하지 않는 요구 사항을 구성하지 마십시오 (대답을 찾기 위해 경쟁 답변에 대해 언급 할 필요도 없습니다. 답변을 찾은 경우 귀하도 찾을 수 있습니다).
Aaron Bertrand '

2
또한 XML 사용에 대한 정당성이 테이블에 직접 액세스하는 것을 피하기 위해 저장 프로 시저에 넣을 수 있다는 것이라면 예제에서 저장 프로 시저에 저장하는 방법과 사용자에게 권한을 부여하는 방법을 보여 주어야합니다. 기본 테이블에 대한 읽기 권한이 없어도 실행할 수 있습니다. 필자는 테이블에 대해 쿼리를 작성하는 대부분의 사람들이 테이블에 대한 읽기 권한을 가지고 있기 때문에 스코프 크립입니다.
Aaron Bertrand '

2
지속 시간의 10 배 차이가 중요하다고 말하고 싶습니다. ~ 8,000 행은 "대량의 데이터"가 아닙니다. 800,000 행에 대해 어떤 결과가 발생해야합니까?
Aaron Bertrand '

7

새로운 독자를 돕기 위해 UNPIVOT에 대한 @bluefeet의 답변을 더 잘 이해하는 예제를 만들었습니다.

 SELECT id
        ,entityId
        ,indicatorname
        ,indicatorvalue
  FROM (VALUES
        (1, 1, 'Value of Indicator 1 for entity 1', 'Value of Indicator 2 for entity 1', 'Value of Indicator 3 for entity 1'),
        (2, 1, 'Value of Indicator 1 for entity 2', 'Value of Indicator 2 for entity 2', 'Value of Indicator 3 for entity 2'),
        (3, 1, 'Value of Indicator 1 for entity 3', 'Value of Indicator 2 for entity 3', 'Value of Indicator 3 for entity 3'),
        (4, 2, 'Value of Indicator 1 for entity 4', 'Value of Indicator 2 for entity 4', 'Value of Indicator 3 for entity 4')
       ) AS Category(ID, EntityId, Indicator1, Indicator2, Indicator3)
UNPIVOT
(
    indicatorvalue
    FOR indicatorname IN (Indicator1, Indicator2, Indicator3)
) UNPIV;

3

콜럼 이름 (트리거에 사용됨)을 알지 못하고 동적 SQL (동적 SQL이 트리거에 사용하기에는 너무 느림)없이 열을 Microsoft SQL Server에서 행으로 변환하는 솔루션이 필요했습니다.

마침내이 솔루션을 찾았습니다.

SELECT
    insRowTbl.PK,
    insRowTbl.Username,
    attr.insRow.value('local-name(.)', 'nvarchar(128)') as FieldName,
    attr.insRow.value('.', 'nvarchar(max)') as FieldValue 
FROM ( Select      
          i.ID as PK,
          i.LastModifiedBy as Username,
          convert(xml, (select i.* for xml raw)) as insRowCol
       FROM inserted as i
     ) as insRowTbl
CROSS APPLY insRowTbl.insRowCol.nodes('/row/@*') as attr(insRow)

보시다시피, 행을 XML로 변환합니다 (하위 쿼리는 xml raw의 경우 i, *를 선택하면 모든 열을 하나의 xml 열로 변환합니다)

그런 다음이 열의 각 XML 속성에 함수를 적용하여 속성 당 하나의 행을 얻습니다.

전체적으로 이것은 열 이름을 알지 못하고 동적 SQL을 사용하지 않고 열을 행으로 변환합니다. 내 목적에 충분히 빠릅니다.

(편집 : 방금 위의 Roman Pekar 답변을 보았습니다. 누가 같은 일을하고 있습니까?이 솔루션보다 10 ~ 100 배 느린 커서가있는 동적 SQL 트리거를 사용했지만 커서가 아닌 커서로 인한 것일 수 있습니다 동적 SQL 어쨌든이 솔루션은 매우 간단하므로 보편적으로 옵션입니다.

https://stackoverflow.com/a/43800286/4160788에서 찾을 수있는 전체 감사 트리거에 대한 내 게시물 에서이 설명을 참조하고 싶기 때문에이 의견을 이곳에 남겨두고 있습니다.


3
DECLARE @TableName varchar(max)=NULL
SELECT @TableName=COALESCE(@TableName+',','')+t.TABLE_CATALOG+'.'+ t.TABLE_SCHEMA+'.'+o.Name
  FROM sysindexes AS i
  INNER JOIN sysobjects AS o ON i.id = o.id
  INNER JOIN INFORMATION_SCHEMA.TABLES T ON T.TABLE_NAME=o.name
 WHERE i.indid < 2
  AND OBJECTPROPERTY(o.id,'IsMSShipped') = 0
  AND i.rowcnt >350
  AND o.xtype !='TF'
 ORDER BY o.name ASC

 print @tablename

행 개수가 350보다 큰 테이블 목록을 얻을 수 있습니다. 테이블의 솔루션 목록에서 행으로 볼 수 있습니다.


2

내가 언급하지 않았기 때문에.

2016+ 이상인 경우 동적 SQL을 실제로 사용하지 않고 데이터를 동적으로 피벗 해제하는 또 다른 옵션이 있습니다.

Declare @YourTable Table ([ID] varchar(50),[Col1] varchar(50),[Col2] varchar(50))
Insert Into @YourTable Values 
 (1,'A','B')
,(2,'R','C')
,(3,'X','D')

Select A.[ID]
      ,Item  = B.[Key]
      ,Value = B.[Value]
 From  @YourTable A
 Cross Apply ( Select * 
                From  OpenJson((Select A.* For JSON Path,Without_Array_Wrapper )) 
                Where [Key] not in ('ID','Other','Columns','ToExclude')
             ) B

보고

ID  Item    Value
1   Col1    A
1   Col2    B
2   Col1    R
2   Col2    C
3   Col1    X
3   Col2    D
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.