SQLで列と行を転置する簡単な方法は?


110

SQLで列と行を単純に切り替えるにはどうすればよいですか?転置する簡単なコマンドはありますか?

つまり、この結果を有効にします。

        Paul  | John  | Tim  |  Eric
Red     1       5       1       3
Green   8       4       3       5
Blue    2       2       9       1

これに:

        Red  | Green | Blue
Paul    1       8       2
John    5       4       2
Tim     1       3       9
Eric    3       5       1

PIVOT このシナリオでは複雑すぎるようです。


1

回答:


145

このデータを変換する方法はいくつかあります。あなたのオリジナルのポストでは、と述べPIVOT、このシナリオには複雑すぎるようだが、それは両方使用して非常に容易に適用することが可能UNPIVOTPIVOT SQL Serverの機能を。

ただし、これらの関数にアクセスできない場合は、UNION ALLto UNPIVOTを使用してレプリケートし、CASEステートメントto を使用して集約関数を複製できますPIVOT

テーブルを作成:

CREATE TABLE yourTable([color] varchar(5), [Paul] int, [John] int, [Tim] int, [Eric] int);

INSERT INTO yourTable
    ([color], [Paul], [John], [Tim], [Eric])
VALUES
    ('Red', 1, 5, 1, 3),
    ('Green', 8, 4, 3, 5),
    ('Blue', 2, 2, 9, 1);

ユニオンオール、アグリゲート、CASEバージョン:

select name,
  sum(case when color = 'Red' then value else 0 end) Red,
  sum(case when color = 'Green' then value else 0 end) Green,
  sum(case when color = 'Blue' then value else 0 end) Blue
from
(
  select color, Paul value, 'Paul' name
  from yourTable
  union all
  select color, John value, 'John' name
  from yourTable
  union all
  select color, Tim value, 'Tim' name
  from yourTable
  union all
  select color, Eric value, 'Eric' name
  from yourTable
) src
group by name

SQL Fiddle with Demoを見る

UNION ALL実行UNPIVOT列を変換することにより、データのPaul, John, Tim, Eric別の行に。次にsum()caseステートメントを使用して集計関数を適用し、それぞれの新しい列を取得しますcolor

アンピボットとピボットの静的バージョン:

SQLサーバーのUNPIVOTPIVOT関数の両方により、この変換がはるかに簡単になります。変換するすべての値がわかっている場合は、それらを静的バージョンにハードコードして結果を得ることができます。

select name, [Red], [Green], [Blue]
from
(
  select color, name, value
  from yourtable
  unpivot
  (
    value for name in (Paul, John, Tim, Eric)
  ) unpiv
) src
pivot
(
  sum(value)
  for color in ([Red], [Green], [Blue])
) piv

SQL Fiddle with Demoを見る

を使用した内部クエリUNPIVOTは、と同じ機能を実行しますUNION ALL。列のリストを受け取り、それを行にPIVOT変換してから、最終的な変換を列に実行します。

動的ピボットバージョン:

不明な数の列(Paul, John, Tim, Eric例では)があり、不明な数の色を変換する場合は、動的SQLを使用してリストを生成しUNPIVOT、次にPIVOT

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

select @colsUnpivot = stuff((select ','+quotename(C.name)
         from sys.columns as C
         where C.object_id = object_id('yourtable') and
               C.name <> 'color'
         for xml path('')), 1, 1, '')

select @colsPivot = STUFF((SELECT  ',' 
                      + quotename(color)
                    from yourtable t
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')


set @query 
  = 'select name, '+@colsPivot+'
      from
      (
        select color, name, value
        from yourtable
        unpivot
        (
          value for name in ('+@colsUnpivot+')
        ) unpiv
      ) src
      pivot
      (
        sum(value)
        for color in ('+@colsPivot+')
      ) piv'

exec(@query)

SQL Fiddle with Demoを見る

動的バージョンの両方を照会yourtableした後、sys.columnsテーブルは、アイテムのリストを生成するUNPIVOTPIVOT。次に、これを実行するクエリ文字列に追加します。動的バージョンのプラスは、変更するリストがある場合colorsnames、実行時にリストが生成される場合です。

3つのクエリはすべて同じ結果になります。

| NAME | RED | GREEN | BLUE |
-----------------------------
| Eric |   3 |     5 |    1 |
| John |   5 |     4 |    2 |
| Paul |   1 |     8 |    2 |
|  Tim |   1 |     3 |    9 |

データベースに対するクエリの結果を転置するメソッドが必要であることを強調しておきます。クエリ自体、または情報の抽出元である列、テーブルなどについて何も知らなくても、クエリの結果セットに対して実行できる単純な転置タイプのコマンド。(TRANSPOSE(SELECT ......))上記の例では、最初のテーブルが、複数のテーブル、ユニオン、ピボットなどを含む非常に複雑なクエリから生成された結果セットであると想像してください。それでも結果はシンプルなテーブル。この結果では、列と行を入れ替えたいだけです。
edezzie

結果をac#DataTableに渡します。ここで簡単なメソッド 'Transpose(Datatable dt)'を作成できますが、SQLにはこれを行うための何かがあります。
edezzie

@edezzie SQLでこれを行う簡単な方法はありません。おそらく、動的バージョンを変更してテーブル名を渡し、システムテーブルから情報をプルすることができます。
タリン

この見事な回答に回答としてフラグを付けなかった理由はありますか?@bluefeetにメダルをあげよう!
Fandango68

これは、テーブルからピボットを選択してうまく機能しています*([[Paul]、[John]、[Tim]、[Eric])の名前の値)([[Red]、[Green]の色の最大値(max(value)) 、[Blue]))pしかし、この選択*をテーブルから書き出すことは可能ですunpivot(([Paul]、[John]、[Tim]、[Eric])の名前の値)upピボット(max(value)for (SELECT COLOR FROM TABLENAME))の色p引用符で囲まれた値を入力する代わりに、選択クエリを使用して直接値を取得できません。
Mogli

20

これは通常、事前にすべての列と行のラベルを知っている必要があります。以下のクエリでわかるように、ラベルはすべてUNPIVOT操作と(re)PIVOT操作の両方で完全にリストされています。

MS SQL Server 2012スキーマのセットアップ

create table tbl (
    color varchar(10), Paul int, John int, Tim int, Eric int);
insert tbl select 
    'Red' ,1 ,5 ,1 ,3 union all select
    'Green' ,8 ,4 ,3 ,5 union all select
    'Blue' ,2 ,2 ,9 ,1;

クエリ1

select *
from tbl
unpivot (value for name in ([Paul],[John],[Tim],[Eric])) up
pivot (max(value) for color in ([Red],[Green],[Blue])) p

結果

| NAME | RED | GREEN | BLUE |
-----------------------------
| Eric |   3 |     5 |    1 |
| John |   5 |     4 |    2 |
| Paul |   1 |     8 |    2 |
|  Tim |   1 |     3 |    9 |

その他の注意事項:

  1. テーブル名を指定すると、local-name()を使用してsys.columnsまたはFOR XML トリックからすべての列名を特定できます。
  2. FOR XMLを使用して、異なる色(または1つの列の値)のリストを作成することもできます。
  3. 上記は、任意のテーブルを処理するために動的SQLバッチに組み合わせることができます。

11

SQLで列と行を転置するためのいくつかのソリューションを指摘したいと思います。

1つ目は、CURSORの使用です。専門家コミュニティにおける一般的なコンセンサスはSQL Serverカーソルから離れることですが、カーソルの使用が推奨される場合もあります。とにかく、カーソルは行を列に転置する別のオプションを提供します。

  • 垂直拡張

    PIVOTと同様に、カーソルには、データセットが拡張されてより多くのポリシー番号が含まれるようになると、行を追加する動的機能があります。

  • 水平拡張

    PIVOTとは異なり、スクリプトを変更せずに新しく追加されたドキュメントを含めるように拡張できるため、カーソルはこの領域で優れています。

  • パフォーマンスの内訳

    CURSORを使用して行を列に転置することの主な制限は、一般にカーソルの使用に関連する欠点です-パフォーマンスコストがかなり高くなります。これは、カーソルがFETCH NEXT操作ごとに個別のクエリを生成するためです。

行を列に転置する別のソリューションは、XMLを使用することです。

行を列に転置するXMLソリューションは、基本的に、動的な列の制限に対処する点でPIVOTの最適なバージョンです。

スクリプトのXMLバージョンは、XMLパス、動的T-SQL、およびいくつかの組み込み関数(つまり、STUFF、QUOTENAME)の組み合わせを使用して、この制限に対処します。

  • 垂直拡張

    PIVOTおよびカーソルと同様に、新しく追加されたポリシーは、元のスクリプトを変更せずに、スクリプトのXMLバージョンで取得できます。

  • 水平拡張

    PIVOTとは異なり、新しく追加されたドキュメントは、スクリプトを変更せずに表示できます。

  • パフォーマンスの内訳

    IOの点では、スクリプトのXMLバージョンの統計はPIVOTとほとんど同じです。唯一の違いは、XMLにdtTransposeテーブルの2番目のスキャンがありますが、今回は論理読み取りからのデータキャッシュです。

これらのソリューション(実際のT-SQLの例を含む)の詳細については、次の記事を参照してください。https://www.sqlshack.com/multiple-options-to-transposing-rows-into-columns/


8

bluefeetからのこのソリューションに基づいて、動的SQLを使用して転置テーブルを生成するストアドプロシージャを次に示します。転置された列(結果のテーブルのヘッダーになる列)を除くすべてのフィールドが数値である必要があります。

/****** Object:  StoredProcedure [dbo].[SQLTranspose]    Script Date: 11/10/2015 7:08:02 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:      Paco Zarate
-- Create date: 2015-11-10
-- Description: SQLTranspose dynamically changes a table to show rows as headers. It needs that all the values are numeric except for the field using for     transposing.
-- Parameters: @TableName - Table to transpose
--             @FieldNameTranspose - Column that will be the new headers
-- Usage: exec SQLTranspose <table>, <FieldToTranspose>
-- =============================================
ALTER PROCEDURE [dbo].[SQLTranspose] 
  -- Add the parameters for the stored procedure here
  @TableName NVarchar(MAX) = '', 
  @FieldNameTranspose NVarchar(MAX) = ''
AS
BEGIN
  -- SET NOCOUNT ON added to prevent extra result sets from
  -- interfering with SELECT statements.
  SET NOCOUNT ON;

  DECLARE @colsUnpivot AS NVARCHAR(MAX),
  @query  AS NVARCHAR(MAX),
  @queryPivot  AS NVARCHAR(MAX),
  @colsPivot as  NVARCHAR(MAX),
  @columnToPivot as NVARCHAR(MAX),
  @tableToPivot as NVARCHAR(MAX), 
  @colsResult as xml

  select @tableToPivot = @TableName;
  select @columnToPivot = @FieldNameTranspose


  select @colsUnpivot = stuff((select ','+quotename(C.name)
       from sys.columns as C
       where C.object_id = object_id(@tableToPivot) and
             C.name <> @columnToPivot 
       for xml path('')), 1, 1, '')

  set @queryPivot = 'SELECT @colsResult = (SELECT  '','' 
                    + quotename('+@columnToPivot+')
                  from '+@tableToPivot+' t
                  where '+@columnToPivot+' <> ''''
          FOR XML PATH(''''), TYPE)'

  exec sp_executesql @queryPivot, N'@colsResult xml out', @colsResult out

  select @colsPivot = STUFF(@colsResult.value('.', 'NVARCHAR(MAX)'),1,1,'')

  set @query 
    = 'select name, rowid, '+@colsPivot+'
        from
        (
          select '+@columnToPivot+' , name, value, ROW_NUMBER() over (partition by '+@columnToPivot+' order by '+@columnToPivot+') as rowid
          from '+@tableToPivot+'
          unpivot
          (
            value for name in ('+@colsUnpivot+')
          ) unpiv
        ) src
        pivot
        (
          sum(value)
          for '+@columnToPivot+' in ('+@colsPivot+')
        ) piv
        order by rowid'
  exec(@query)
END

このコマンドで提供されるテーブルを使用してテストできます。

exec SQLTranspose 'yourTable', 'color'

アーグ。しかし、すべてのフィールドがnvarchar(max)である1つが必要です
2015年

1
すべてのフィールドについて、nvarchar(max)は、最後から6行目のsum(value)をmax(value)に変更するだけ
Paco Zarate

2

私がやっているUnPivot最初とに結果を格納CTEし、使用CTE中にPivot操作。

デモ

with cte as
(
select 'Paul' as Name, color, Paul as Value 
 from yourTable
 union all
 select 'John' as Name, color, John as Value 
 from yourTable
 union all
 select 'Tim' as Name, color, Tim as Value 
 from yourTable
 union all
 select 'Eric' as Name, color, Eric as Value 
 from yourTable
 )

select Name, [Red], [Green], [Blue]
from
(
select *
from cte
) as src
pivot 
(
  max(Value)
  for color IN ([Red], [Green], [Blue])
) as Dtpivot;

2

上記の@Paco Zarateの素晴らしい答えに追加して、複数のタイプの列を持つテーブルを転置したい場合は、これを行39の最後に追加して、int列のみ転置するようにします。

and C.system_type_id = 56   --56 = type int

変更される完全なクエリは次のとおりです。

select @colsUnpivot = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id(@tableToPivot) and
      C.name <> @columnToPivot and C.system_type_id = 56    --56 = type int
for xml path('')), 1, 1, '')

system_type_idのを見つけるには、これを実行します。

select name, system_type_id from sys.types order by name

1

+ bluefeetの回答に基づいて分割テキストを転置するために使用しているコードを共有したいと思います。このアプローチでは、MS SQL 2005のプロシージャとして実装されています

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:      ELD.
-- Create date: May, 5 2016.
-- Description: Transpose from rows to columns the user split function.
-- =============================================
CREATE PROCEDURE TransposeSplit @InputToSplit VARCHAR(8000)
    ,@Delimeter VARCHAR(8000) = ','
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @colsUnpivot AS NVARCHAR(MAX)
        ,@query AS NVARCHAR(MAX)
        ,@queryPivot AS NVARCHAR(MAX)
        ,@colsPivot AS NVARCHAR(MAX)
        ,@columnToPivot AS NVARCHAR(MAX)
        ,@tableToPivot AS NVARCHAR(MAX)
        ,@colsResult AS XML

    SELECT @tableToPivot = '#tempSplitedTable'

    SELECT @columnToPivot = 'col_number'

    CREATE TABLE #tempSplitedTable (
        col_number INT
        ,col_value VARCHAR(8000)
        )

    INSERT INTO #tempSplitedTable (
        col_number
        ,col_value
        )
    SELECT ROW_NUMBER() OVER (
            ORDER BY (
                    SELECT 100
                    )
            ) AS RowNumber
        ,item
    FROM [DB].[ESCHEME].[fnSplit](@InputToSplit, @Delimeter)

    SELECT @colsUnpivot = STUFF((
                SELECT ',' + quotename(C.NAME)
                FROM [tempdb].sys.columns AS C
                WHERE C.object_id = object_id('tempdb..' + @tableToPivot)
                    AND C.NAME <> @columnToPivot
                FOR XML path('')
                ), 1, 1, '')

    SET @queryPivot = 'SELECT @colsResult = (SELECT  '','' 
                    + quotename(' + @columnToPivot + ')
                  from ' + @tableToPivot + ' t
                  where ' + @columnToPivot + ' <> ''''
          FOR XML PATH(''''), TYPE)'

    EXEC sp_executesql @queryPivot
        ,N'@colsResult xml out'
        ,@colsResult OUT

    SELECT @colsPivot = STUFF(@colsResult.value('.', 'NVARCHAR(MAX)'), 1, 1, '')

    SET @query = 'select name, rowid, ' + @colsPivot + '
        from
        (
          select ' + @columnToPivot + ' , name, value, ROW_NUMBER() over (partition by ' + @columnToPivot + ' order by ' + @columnToPivot + ') as rowid
          from ' + @tableToPivot + '
          unpivot
          (
            value for name in (' + @colsUnpivot + ')
          ) unpiv
        ) src
        pivot
        (
          MAX(value)
          for ' + @columnToPivot + ' in (' + @colsPivot + ')
        ) piv
        order by rowid'

    EXEC (@query)

    DROP TABLE #tempSplitedTable
END
GO

このソリューションを、注文なしの行の注文方法に関する情報(SQLAuthority.com)とMSDNの分割関数(social.msdn.microsoft.com)と混ぜています

手続きを実行するとき

DECLARE @RC int
DECLARE @InputToSplit varchar(MAX)
DECLARE @Delimeter varchar(1)

set @InputToSplit = 'hello|beautiful|world'
set @Delimeter = '|'

EXECUTE @RC = [TransposeSplit] 
   @InputToSplit
  ,@Delimeter
GO

あなたは次の結果を得た

  name       rowid  1      2          3
  col_value  1      hello  beautiful  world

1

このようにして、テーブル内のFilelds(Columns)からのすべてのデータをRecord(Row)に変換します。

Declare @TableName  [nvarchar](128)
Declare @ExecStr    nvarchar(max)
Declare @Where      nvarchar(max)
Set @TableName = 'myTableName'
--Enter Filtering If Exists
Set @Where = ''

--Set @ExecStr = N'Select * From '+quotename(@TableName)+@Where
--Exec(@ExecStr)

Drop Table If Exists #tmp_Col2Row

Create Table #tmp_Col2Row
(Field_Name nvarchar(128) Not Null
,Field_Value nvarchar(max) Null
)

Set @ExecStr = N' Insert Into #tmp_Col2Row (Field_Name , Field_Value) '
Select @ExecStr += (Select N'Select '''+C.name+''' ,Convert(nvarchar(max),'+quotename(C.name) + ') From ' + quotename(@TableName)+@Where+Char(10)+' Union All '
         from sys.columns as C
         where (C.object_id = object_id(@TableName)) 
         for xml path(''))
Select @ExecStr = Left(@ExecStr,Len(@ExecStr)-Len(' Union All '))
--Print @ExecStr
Exec (@ExecStr)

Select * From #tmp_Col2Row
Go

0

Paco Zarateのソリューションを使用することができ、それは美しく機能します。1行追加する必要がありましたが( "SET ANSI_WARNINGS ON")、それは私がそれを使用したり呼び出したりする方法に固有の何かかもしれません。私の使い方に問題があり、誰かが私を助けてくれることを願っています:

このソリューションは、実際のSQLテーブルでのみ機能します。一時テーブルとインメモリ(宣言済み)テーブルで試してみましたが、それらでは機能しません。したがって、呼び出しコードでSQLデータベースにテーブルを作成し、SQLTransposeを呼び出します。繰り返しますが、うまくいきます。それは私が欲しいものです。これが私の問題です:

全体的なソリューションを真に動的にするために、SQLTransposeに「オンザフライ」で送信する準備済みの情報を一時的に格納するテーブルを作成し、SQLTransposeが呼び出されたらそのテーブルを削除する必要があります。テーブルの削除は、私の最終的な実装計画に問題を提示しています。コードは、エンドユーザーアプリケーション(Microsoft Accessフォーム/メニューのボタン)から実行する必要があります。このSQLプロセスを使用すると(SQLテーブルの作成、SQLTransposeの呼び出し、SQLテーブルの削除)、使用したSQLアカウントにはテーブルを削除する権限がないため、エンドユーザーアプリケーションでエラーが発生します。

だから私はいくつかの可能な解決策があると思います:

  1. SQLTransposeを一時テーブルまたは宣言されたテーブル変数で機能させる方法を見つけます。

  2. 実際のSQLテーブルを必要としない、行と列の転置のための別の方法を見つけます。

  3. エンドユーザーが使用するSQLアカウントがテーブルを削除できるようにする適切な方法を見つけます。これは、Accessアプリケーションにコード化された単一の共有SQLアカウントです。権限は付与できないdboタイプの権限のようです。

私はこれのいくつかが別の別のスレッドと質問を正当化するかもしれないことを認識しています。ただし、1つの解決策が行と列の転置を実行するための単なる別の方法である可能性があるため、このスレッドでここに最初の投稿を行います。

編集:Pacoが示唆したように、最後から6行目でsum(value)をmax(value)に置き換えました。

編集:

私は自分に役立つ何かを見つけました。それがベストアンサーかどうかはわかりません。

格納された手順を実行するために使用される読み取り専用のユーザーアカウントを持っているため、データベースからレポート出力を生成します。私が作成したSQLTranspose関数は、「正当な」テーブル(宣言されたテーブルではなく、一時テーブルではない)でのみ機能するため、読み取り専用ユーザーアカウントがテーブルを作成(および後で削除)する方法を理解する必要がありました。 。

私の目的では、ユーザーアカウントがテーブルの作成を許可されていても問題ないと考えました。それでもユーザーはテーブルを削除できませんでした。私の解決策は、ユーザーアカウントが承認されるスキーマを作成することでした。次に、そのテーブルを作成、使用、または削除するたびに、指定されたスキーマで参照します。

私は最初に「sa」または「sysadmin」アカウントからこのコマンドを発行しました:CREATE SCHEMA ro AUTHORIZATION

「tmpoutput」テーブルを参照するときはいつでも、次の例のように指定します。

ドロップテーブルro.tmpoutput


私は自分に役立つ何かを見つけました。それがベストアンサーかどうかはわかりません。
CraigD
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.