[sql] SQL Server : 열에서 행으로

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

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

[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에서 어디를보고 어떻게해야하는지에 대한 제안이 있습니까?



답변

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;


답변

잘 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을 사용할 수 없습니다.

답변

새로운 독자를 돕기 위해 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;


답변

콜럼 이름 (트리거에 사용됨)을 알지 못하고 동적 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에서 찾을 수있는 전체 감사 트리거에 대한 내 게시물 에서이 설명을 참조하고 싶기 때문에이 의견을 이곳에 남겨두고 있습니다.


답변

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보다 큰 테이블 목록을 얻을 수 있습니다. 테이블의 솔루션 목록에서 행으로 볼 수 있습니다.


답변

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

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


답변