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のソリューションに行きました。エレガントで機能的。どうもありがとうございました。
セルゲイ

回答:


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
UNPIVOTand / vs についてもっと詳しく知りたい人のために。APPLYブラッドシュルツ(およびその後の記事)からの2010年のこのブログ投稿は(美しい)です。
ルフィン、2015年

2
メッセージ8167、レベル16、状態1、行147列 "blahblah"のタイプが、UNPIVOTリストで指定されている他の列のタイプと競合しています。
JDPeckham 2017

@JDPeckham異なるデータ型がある場合、アンピボットを実行する前に、それらを同じ型と長さに変換する必要があります。その詳細についてこちらをご覧ください
タリン

>、<などのxmlコードのエスケープ解除に失敗するため、xmlメソッドに問題があります。および&。さらに、パフォーマンスを大幅に改善するには、次のように書き直します。 xml path( '')、type).value( 'text()[1]'、 'nvarchar(max)')、1、1、 '')の 'Indicator%'のようなC.column_name
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 /動的SQLの長所と短所をいくつか追加すると思います。私はできる限り客観的になるようにし、エレガントさと醜さについては触れません。他に賛否両論がある場合は、回答を編集するか、コメントを書き込んでください

短所

  • それはだとして、高速ではない、動的SQLとして、大まかなテストは、XMLが動的な(この推定値は、正確な方法はありませんので、それは、〜250000個の行をテーブルの上に1つのクエリた)ことを約2.5倍遅いことを私に与えました。必要に応じて、自分で比較することもできます。これはsqlfiddleの例です。100000行では、29 (xml)と14 (動的)でした。
  • xpathに慣れていない人にとっては理解するの難しいかもしれません。

長所

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

2
テーブルからデータを選択する必要がないXMLで選択しているデータは何ですか?
アーロンバートランド

1
たとえば、テーブルからデータを選択するためのアクセス許可をユーザーに与えないようにすることができますが、テーブルを操作するストアドプロシージャに対してのみなので、プロシージャ内でxmlを選択できますが、動的SQLを使用する場合はいくつかの回避策を使用する必要があります
Roman Pekar

3
ユーザーがコードを実行できるようにするには、コードを実行するために必要なアクセス権をユーザーに付与する必要があります。回答の音を良くするために存在しない要件を構成しないでください(回答を確認するために競合する回答にコメントする必要もありません。その回答が見つかった場合、彼らもあなたの回答を見つけることができます)。
アーロンバートランド

2
また、XMLを使用する理由が、テーブルに直接アクセスできないようにストアドプロシージャに入れることができる場合は、例として、XMLをストアドプロシージャに入れる方法と、ユーザーに権限を付与して、基になるテーブルへの読み取りアクセス権がなくても実行できます。テーブルに対するクエリを作成するほとんどの人がテーブルへの読み取りアクセス権を持っているので、私にとってそれはスコープクリープです。
アーロンバートランド

2
はい、期間の10倍の違いは重要だと思います。そして、8,000行は「大量のデータ」ではありません-800,000行に対して何が起こるかを見る必要がありますか?
アーロンバートランド

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に変換します(サブクエリselect i、* for xml raw、これによりすべての列が1つのxml列に変換されます)

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

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

(編集:同じことをしている上記のRoman Pekarの回答を見たところです。最初にカーソルで動的SQLトリガーを使用しました。これは、このソリューションよりも10倍から100倍遅い速度でしたが、おそらくカーソルではなく、動的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.