it-swarm.com.de

Einfache Möglichkeit, Spalten und Zeilen in SQL zu transponieren?

Wie kann ich in SQL einfach Spalten mit Zeilen wechseln? Gibt es einen einfachen Befehl zum Umsetzen?

drehen Sie also dieses Ergebnis:

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

das sehr gut finden:

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

PIVOT scheint für dieses Szenario zu komplex.

88
edezzie

Es gibt mehrere Möglichkeiten, diese Daten umzuwandeln. In Ihrem ursprünglichen Beitrag haben Sie angegeben, dass PIVOT für dieses Szenario zu komplex erscheint, es kann jedoch sehr einfach mit den Funktionen UNPIVOT UND PIVOT in SQL Server angewendet werden. 

Wenn Sie jedoch keinen Zugriff auf diese Funktionen haben, kann dies mit UNION ALL nach UNPIVOT und einer Aggregatfunktion mit einer CASE-Anweisung nach PIVOT repliziert werden:

Tabelle erstellen:

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- und 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

Siehe SQL Fiddle mit Demo

UNION ALL führt das UNPIVOT der Daten durch, indem die Spalten Paul, John, Tim, Eric in separate Zeilen umgewandelt werden. Anschließend wenden Sie die Aggregatfunktion sum() mit der Anweisung case an, um die neuen Spalten für jede color-Anweisung abzurufen.

Statische Version von Unpivot und Pivot:

Die beiden Funktionen UNPIVOT und PIVOT im SQL-Server vereinfachen diese Umwandlung erheblich. Wenn Sie alle Werte kennen, die Sie transformieren möchten, können Sie sie in eine statische Version umcodieren, um das Ergebnis zu erhalten:

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

Siehe SQL Fiddle mit Demo

Die innere Abfrage mit UNPIVOT erfüllt dieselbe Funktion wie UNION ALL. Es nimmt die Liste der Spalten und wandelt sie in Zeilen um, die PIVOT führt dann die endgültige Umwandlung in Spalten durch.

Dynamische Pivot-Version:

Wenn Sie eine unbekannte Anzahl von Spalten haben (Paul, John, Tim, Eric in Ihrem Beispiel) und dann eine unbekannte Anzahl von Farben, die Sie transformieren möchten, können Sie mithilfe von dynamischem SQL die Liste nach UNPIVOT und dann nach PIVOT generieren.

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, '[email protected]+'
      from
      (
        select color, name, value
        from yourtable
        unpivot
        (
          value for name in ('[email protected]+')
        ) unpiv
      ) src
      pivot
      (
        sum(value)
        for color in ('[email protected]+')
      ) piv'

exec(@query)

Siehe SQL Fiddle mit Demo

Die dynamische Version fragt sowohl yourtable als auch die sys.columns-Tabelle ab, um die Liste der Elemente für UNPIVOT und PIVOT zu generieren. Dies wird dann zu einer Abfragezeichenfolge hinzugefügt, die ausgeführt werden soll. Das Plus der dynamischen Version ist, wenn Sie eine Änderungsliste von colors und/oder names haben, wird die Liste zur Laufzeit generiert.

Alle drei Abfragen führen zum gleichen Ergebnis:

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

Normalerweise müssen Sie ALLE Spalten- und Zeilenbeschriftungen vorher kennen. Wie Sie in der unten stehenden Abfrage sehen können, werden alle Bezeichnungen sowohl in den UNPIVOT- als auch in den (erneuten) PIVOT-Operationen aufgeführt.

MS SQL Server 2012 Schema-Setup:

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;

Abfrage 1:

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

Ergebnisse:

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

Zusätzliche Bemerkungen:

  1. Wenn Sie einen Tabellennamen erhalten, können Sie alle Spaltennamen aus sys.columns oder FOR XML Trick mit local-name () ermitteln.
  2. Sie können die Liste der verschiedenen Farben (oder Werte für eine Spalte) auch mit FOR XML erstellen.
  3. Das Obige kann zu einem dynamischen SQL-Stapel kombiniert werden, um jede Tabelle zu bearbeiten.
17
RichardTheKiwi

Ich möchte ein paar weitere Lösungen für die Umsetzung von Spalten und Zeilen in SQL aufzeigen.

Der erste ist - mit CURSOR. Obwohl der allgemeine Konsens darin besteht, sich von SQL Server-Cursors fernzuhalten, gibt es immer noch Fälle, in denen die Verwendung von Cursorn empfohlen wird. Wie auch immer, Cursors bieten uns eine weitere Möglichkeit, Zeilen in Spalten umzuwandeln.

  • Vertikale Ausdehnung

    Ähnlich wie beim PIVOT verfügt der Cursor über die dynamische Fähigkeit, mehr Zeilen anzufügen, wenn Ihre Datenmenge erweitert wird, um mehr Richtliniennummern aufzunehmen.

  • Horizontale Ausdehnung

    Im Gegensatz zum PIVOT zeichnet sich der Cursor in diesem Bereich aus, da er in der Lage ist, neu hinzugefügte Dokumente aufzunehmen, ohne das Skript zu ändern.

  • Leistungsausfall

    Die hauptsächliche Einschränkung bei der Umsetzung von Zeilen in Spalten mit CURSOR ist ein Nachteil, der mit der Verwendung von Cursors im Allgemeinen verbunden ist - sie verursachen erhebliche Leistungskosten. Dies liegt daran, dass der Cursor für jede FETCH NEXT-Operation eine separate Abfrage generiert.

Eine andere Lösung für das Umsetzen von Zeilen in Spalten ist die Verwendung von XML.

Die XML-Lösung zum Umsetzen von Zeilen in Spalten ist grundsätzlich eine optimale Version von PIVOT, da sie die dynamische Spaltenbegrenzung anspricht.

Die XML-Version des Skripts behebt diese Einschränkung durch Verwendung einer Kombination aus XML-Pfad, dynamischem T-SQL und einigen integrierten Funktionen (d. H. STUFF, QUOTENAME).

  • Vertikale Ausdehnung

    Ähnlich wie PIVOT und Cursor können neu hinzugefügte Richtlinien in der XML-Version des Skripts abgerufen werden, ohne dass das ursprüngliche Skript geändert wird.

  • Horizontale Ausdehnung

    Im Gegensatz zum PIVOT können neu hinzugefügte Dokumente angezeigt werden, ohne das Skript zu ändern.

  • Leistungsausfall

    Was die IO betrifft, ist die Statistik der XML-Version des Skripts fast identisch mit der des PIVOT-Protokolls. Der einzige Unterschied besteht darin, dass das XML einen zweiten Scan der Tabelle dtTranspose enthält, diesmal jedoch aus einem logischen Lesedaten-Cache.

Weitere Informationen zu diesen Lösungen (einschließlich einiger tatsächlicher T-SQL-Beispiele) finden Sie in diesem Artikel: https://www.sqlshack.com/multiple-options-to-transposing-rows-into-columns/

10
MikeS

Basierend auf dieser Lösung from bluefeet ist hier eine gespeicherte Prozedur, die dynamische sql verwendet, um die transponierte Tabelle zu generieren. Es ist erforderlich, dass alle Felder numerisch sind, mit Ausnahme der umgesetzten Spalte (der Spalte, die als Überschrift in der resultierenden Tabelle verwendet wird):

/****** 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('[email protected]+')
                  from '[email protected]+' t
                  where '[email protected]+' <> ''''
          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, '[email protected]+'
        from
        (
          select '[email protected]+' , name, value, ROW_NUMBER() over (partition by '[email protected]+' order by '[email protected]+') as rowid
          from '[email protected]+'
          unpivot
          (
            value for name in ('[email protected]+')
          ) unpiv
        ) src
        pivot
        (
          sum(value)
          for '[email protected]+' in ('[email protected]+')
        ) piv
        order by rowid'
  exec(@query)
END

Sie können es mit der mit diesem Befehl gelieferten Tabelle testen:

exec SQLTranspose 'yourTable', 'color'
7
Paco Zarate

Ich mache zuerst UnPivot und speichere die Ergebnisse in CTE und verwende die CTE-Operation in Pivot.

Demo

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

Wenn Sie zu der hervorragenden Antwort von @Paco Zarate hinzufügen, eine Tabelle mit mehreren Arten von Spalten umsetzen, fügen Sie diese an das Ende der Zeile 39 an, sodass nur die Spalten int transponiert werden:

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

Hier ist die vollständige Abfrage, die geändert wird:

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, '')

Um andere system_type_ids zu finden, führen Sie folgendes aus:

select name, system_type_id from sys.types order by name
2
Jonathan Harris

Ich teile gerne den Code, den ich verwende, um einen geteilten Text basierend auf + bluefeet answer zu transponieren. In diesem Ansatz bin ich als Prozedur in MS SQL 2005 implementiert.

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

Ich mische diese Lösung mit den Informationen zum Anordnen von Zeilen ohne Reihenfolge nach ( SQLAuthority.com ) und der Split-Funktion in MSDN ( social.msdn.Microsoft.com ).

Wenn Sie das Prodecure ausführen

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

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

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

sie erhalten das nächste Ergebnis

  name       rowid  1      2          3
  col_value  1      hello  beautiful  world
1
ELD

Konvertieren Sie auf diese Weise alle Daten aus Feldern (Spalten) in Tabelle in Datensatz (Zeile).

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)[email protected]
--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)[email protected]+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

Ich konnte die Lösung von Paco Zarate verwenden und sie funktioniert wunderbar. Ich musste eine Zeile hinzufügen ("SET ANSI_WARNINGS ON"), aber das mag etwas Einzigartiges sein, wie ich es benutzt oder genannt habe. Es gibt ein Problem mit meiner Nutzung und ich hoffe, jemand kann mir dabei helfen:

Die Lösung funktioniert nur mit einer tatsächlichen SQL-Tabelle. Ich habe es mit einer temporären Tabelle und einer (deklarierten) In-Memory-Tabelle versucht, aber bei diesen funktioniert es nicht. In meinem aufrufenden Code erstelle ich eine Tabelle in meiner SQL-Datenbank und rufe dann SQLTranspose auf. Wieder funktioniert es großartig. Es ist genau das, was ich will. Hier ist mein Problem:

Damit die Gesamtlösung wirklich dynamisch ist, muss ich diese Tabelle erstellen, in der ich die vorbereiteten Informationen, die ich "on the fly" an SQLTranspose sende, temporär speichere, und diese Tabelle anschließend löschen, sobald SQLTranspose aufgerufen wird. Das Löschen der Tabelle ist ein Problem mit meinem endgültigen Implementierungsplan. Der Code muss in einer Endbenutzeranwendung ausgeführt werden (eine Schaltfläche in einem Microsoft Access-FormularMenü). Wenn ich diesen SQL-Prozess verwende (eine SQL-Tabelle erstellen, SQLTranspose aufrufen, SQL-Tabelle löschen), stößt die Endbenutzeranwendung auf einen Fehler, weil das verwendete SQL-Konto nicht über die Rechte zum Löschen einer Tabelle verfügt.

Ich denke, es gibt einige mögliche Lösungen:

  1. Finden Sie eine Möglichkeit, SQLTranspose mit einer temporären Tabelle oder einer deklarierten Tabellenvariablen arbeiten zu lassen.

  2. Finden Sie eine andere Methode für die Umsetzung von Zeilen und Spalten heraus, für die keine tatsächliche SQL-Tabelle erforderlich ist.

  3. Finden Sie eine geeignete Methode, um dem von meinen Endbenutzern verwendeten SQL-Konto das Löschen einer Tabelle zu ermöglichen. Es ist ein einzelnes freigegebenes SQL-Konto, das in meiner Access-Anwendung codiert ist. Es scheint, dass die Berechtigung ein Dbo-Privileg ist, das nicht gewährt werden kann.

Ich erkenne, dass einige davon einen anderen, separaten Thread und eine andere Frage rechtfertigen könnten. Da es jedoch die Möglichkeit gibt, dass eine Lösung einfach eine andere Art ist, Zeilen und Spalten zu transponieren, werde ich hier in diesem Thread meinen ersten Beitrag verfassen.

EDIT: Ich habe auch sum (value) in der 6. Zeile vom Ende durch max (value) ersetzt, wie Paco vorgeschlagen hat.

BEARBEITEN:

Ich habe etwas herausgefunden, das für mich funktioniert. Ich weiß nicht, ob es die/- beste Antwort ist oder nicht.

Ich verfüge über ein schreibgeschütztes Benutzerkonto, mit dem gespeicherte Prozeduren ausgeführt und daher Berichtsausgaben aus einer Datenbank generiert werden. Da die von mir erstellte SQLTranspose-Funktion nur mit einer "legitimen" Tabelle (keine deklarierte Tabelle und keine temporäre Tabelle) funktioniert, musste ich herausfinden, wie ein schreibgeschütztes Benutzerkonto eine Tabelle erstellen (und später löschen) kann .

Ich habe festgestellt, dass es für meine Zwecke in Ordnung ist, dass das Benutzerkonto eine Tabelle erstellen darf. Der Benutzer konnte die Tabelle jedoch immer noch nicht löschen. Meine Lösung bestand darin, ein Schema zu erstellen, in dem das Benutzerkonto autorisiert ist. Wenn ich dann diese Tabelle erstelle, verwende oder lösche, verweise ich sie auf das angegebene Schema.

Ich habe diesen Befehl zum ersten Mal von einem "sa" - oder "sysadmin" -Konto aus ausgegeben: CREATE SCHEMA ro AUTHORIZATION

Wenn ich mich auf meine "tmpoutput" -Tabelle beziehe, gebe ich sie wie folgt an:

drop table ro.tmpoutput

0
CraigD