web-dev-qa-db-ja.com

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でどこを見てどこでそれを実現するかについて何か提案はありますか?

100
Sergei

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;
202
Taryn

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 /動的SQLの長所と短所をいくつか追加します。私は出来る限り客観的になるように努力しますが、優雅さと醜さは言うまでもありません。他にも長所と短所がある場合は、答えを編集するか、コメントを書いてください。

短所

  • それは 速くはない 動的なSQLほどではありません。ラフなテストでxmlはその動的な2.5倍遅いことがわかりました(〜250000行のテーブルに対する1つのクエリだったので、この見積もりはまったく正確ではありません)。あなたが望むなら、あなたはそれを自分で比較することができます、これは sqlfiddle 例です、100000行でそれは29秒(xml)対14秒(動的)でした。
  • 理解するのがより難しい xpathに慣れていない人のために;

長所

  • それは 同じスコープ あなたの他のクエリと同じです、そしてそれは非常に便利かもしれません。いくつかの例が思い浮かぶ
    • trigger 内のinsertedテーブルとdeletedテーブルを問い合わせることができます(動的では不可能です)。
    • ユーザーは 権限 から直接選択する必要はありません。つまり、ストアドプロシージャレイヤがあり、ユーザがspを実行する権限を持っていて、テーブルを直接クエリする権限を持っていない場合でも、ストアドプロシージャ内でこのクエリを使用できます。
    • テーブル変数を問い合わせることができます 動的なSQLの中にそれを移入するには、代わりに一時テーブルにするか、型を作成して動的SQLにパラメータとして渡す必要があります。
  • 関数内で クエリを実行できます (スカラまたはテーブル値)。関数内で動的SQLを使用することはできません。
18
Roman Pekar

新しい読者を助けるために、@ bluefeetのUNPIVOTに関する回答をよりよく理解するための例を作成しました。

 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;
6
Dmyan

Microsoft SQL Serverで列を行に変換するための解決策が必要でした。(トリガーで使用される)列名を認識せずに、動的SQLを使用しないでください(動的SQLはトリガーで使用するには遅すぎます)。

私はついにこの解決策を見つけました、それはうまくいきます:

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に対してSubquery select i、*、これはすべての列を1つのxml列に変換します)。

次に、この列の各XML属性に関数をクロス適用して、属性ごとに1行を取得します。

全体として、これは列名を知らなくても動的SQLを使用せずに、列を行に変換します。私の目的には十分速いです。

(編集:私はちょうど同じことをしている、上でRoman Pekarの答えを見た、私は最初にカーソルで動的SQLトリガーを使用した。これはこのソリューションよりも10から100倍遅くなっていた。動的SQLとにかく、この解決策は非常に単純で普遍的なので、その決定的な選択肢です。

私はこのコメントをこの場所に残しています、なぜなら私はあなたがここで見つけることができるという完全な監査トリガーについての私のポストでこの説明を参照したいからです。 https://stackoverflow.com/a/43800286/4160788 =

3
flack
DECLARE @TableName nvarchar(50)
DECLARE column_to_row CURSOR FOR

--List of tables that we want to unpivot columns as row
SELECT DISTINCT t.name FROM sys.tables t
JOIN sys.schemas s ON t.schema_id=t.schema_id
WHERE t.name like '%_CT%'
AND s.name='cdc'

OPEN  column_to_row
FETCH NEXT FROM column_to_row INTO @TableName
WHILE @@FETCH_STATUS = 0
BEGIN

DECLARE @script nvarchar(max) = null
DECLARE @columns nvarchar(2000) = null

-- keep the table's column list
select @columns = COALESCE(@columns + ',','') + c.name from sys.tables  t
join sys.columns c on t.object_id = c.object_id
where t.name = @TableName

set @script = 'SELECT '+@columns+' FROM [cdc].['+@TableName+'] (nolock)'
--print (@script)
exec (@script)

FETCH NEXT FROM column_to_row INTO @TableName
END
CLOSE column_to_row
DEALLOCATE column_to_row

これは、列から行への別の方法です。テーブルの数と列の数は重要ではありません。パラメータを設定して結果を取得するだけです。私はこれを書きました。時々私は別のテーブルBのフィールドとして(行フィールドでなければならない)テーブルA(列の結果セットです)の結果が必要だからです。その場合、テーブルBに設定したフィールド数がわかりません。

3
cunay

私がそれが言及されているのを見なかったという理由だけで。

2016+の場合は、動的SQLを実際に使用せずにデータを動的にアンピボットするもう1つの方法があります。

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
0