열을 행으로 변환하는 우아한 솔루션을 찾고 있습니다.
다음은 예입니다. 다음 스키마가 포함 된 테이블이 있습니다.
[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 APPLY
UNION 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을 작성할 수도 있지만 xml이 더 좋습니다. 동적 SQL의 경우 테이블에서 직접 데이터를 선택할 수있는 권한이 있어야하며 이것이 항상 옵션은 아닙니다.
업데이트
의견에 큰 불꽃이 있기 때문에 xml / dynamic SQL의 장단점을 추가 할 것이라고 생각합니다. 나는 우아함과 추악함을 언급 할 수없는만큼 객관적이되도록 노력할 것입니다. 다른 장단점이 있다면 답변을 편집하거나 의견을 쓰십시오.
단점
- 그것은 동적 SQL 만큼 빠르지 않습니다 . 거친 테스트 결과 xml이 동적보다 약 2.5 배 느리다는 것이 나왔습니다 (~ 250000 개의 행 테이블에 대한 하나의 쿼리 이므로이 추정치는 정확하지 않습니다). 원하는 경우 sqlfiddle 예를 보면 100000 개의 행에서 29 초 (xml) 대 14 초 (동적) 인 경우 직접 비교할 수 있습니다 .
- xpath에 익숙하지 않은 사람들에게는 이해 하기 어려울 수 있습니다 .
찬성
- 그것은의 동일한 범위 다른 쿼리로, 그것은 매우 편리 할 수 있습니다. 몇 가지 예가 떠오른다
- 트리거 내부에서 쿼리
inserted
및deleted
테이블을 수행 할 수 있습니다 (동적으로는 불가능). - 사용자는 테이블에서 직접 선택에 대한 권한 이 필요하지 않습니다 . 내 말은 스토어드 프로 시저 계층이 있고 사용자에게 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