web-dev-qa-db-ja.com

SQLで並べと行を入れ替える簡単な方法は?

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は複雑すぎるようです。

94
edezzie

このデータを変換する方法はいくつかあります。あなたの最初の投稿で、PIVOTはこのシナリオには複雑すぎるように思われると述べましたが、SQLの UNPIVOTPIVOT の両方の関数を使って簡単に適用できます。サーバ。

ただし、これらの関数にアクセスできない場合は、UNION ALLUNPIVOTに、次にCASE文を含む集約関数を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);

Union All、Aggregate、CASE Version:

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を参照してください

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

アンピボットおよびピボット静的バージョン:

SQL ServerのUNPIVOT関数とPIVOT関数はどちらも、この変換をはるかに簡単にします。変換したい値がすべてわかっている場合は、それらを静的バージョンにハードコーディングして結果を得ることができます。

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を参照してください

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を参照してください

動的バージョンは、yourtablesys.columnsテーブルの両方を照会して、UNPIVOTPIVOTへの項目のリストを生成します。これは、実行されるクエリ文字列に追加されます。動的バージョンの利点は、colorsnamesの変更リストがある場合、実行時にそのリストが生成されることです。

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

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

これには通常、すべての列と行のラベルを事前に知っておく必要があります。以下のクエリからわかるように、ラベルはすべて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バッチに結合して任意のテーブルを処理できます。
18
RichardTheKiwi

SQLで列と行を入れ替えるためのさらにいくつかのソリューションを指摘したいと思います。

最初のものは - CURSORを使うことです。専門家コミュニティにおける一般的なコンセンサスはSQL Serverのカーソルから離れることですが、それでもカーソルの使用を推奨する場合があります。とにかく、Cursorsには、行を列に変換するための別のオプションがあります。

  • 縦方向の拡大

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

  • 水平方向の拡大

    PIVOTとは異なり、カーソルはこの領域で優れています。スクリプトを変更することなく、新しく追加した文書を含めることができるためです。

  • 掲載結果の内訳

    CURSORを使用して行を列に転置することの主な制限は、一般にカーソルの使用に関連した不利な点です。それらは、かなりのパフォーマンス上のコストがかかります。これは、Cursorが各FETCH NEXT操作に対して別々の問合せを生成するためです。

行を列に転置するもう1つの解決策は、XMLを使用することです。

行を列に置き換えるためのXMLソリューションは、動的な列の制限に対処するという点で、基本的にPIVOTの最適なバージョンです。

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

  • 縦方向の拡大

    PIVOTやCursorと同様に、新しく追加されたポリシーは、元のスクリプトを変更することなくXMLバージョンのスクリプトで取得できます。

  • 水平方向の拡大

    PIVOTとは異なり、新しく追加した文書はスクリプトを変更せずに表示できます。

  • 掲載結果の内訳

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

この記事では、これらの解決策(実際のT-SQLの例を含む)について、さらに詳しく説明しています。 https://www.sqlshack.com/multiple-options-to-transposing-rows-列へ/

10
MikeS

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'
7
Paco Zarate

最初にUnPivotを行い、その結果をCTEに格納し、CTEPivot操作に使用します。

デモ

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
mr_eclair

上記の@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
2
Jonathan Harris

私は+ bluefeetの答えに基づいて分割されたテキストを転置するのに使っているコードを共有したいです。このアプローチで私はMSSQL 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

私はこの解決策を、(by = 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
ELD

この方法では、テーブルのフィールド(列)からすべてのデータをレコード(行)に変換します。

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

0
CraigD