2013-08-02 23 views
82

Auf der Suche nach eleganten (oder einer beliebigen) Lösung zum Konvertieren von Spalten in Zeilen.SQL Server: Spalten in Zeilen

Hier ein Beispiel: Ich habe eine Tabelle mit folgendem Schema haben:

[ID] [EntityID] [Indicator1] [Indicator2] [Indicator3] ... [Indicator150] 

Hier ist, was ich als Ergebnis erhalten möchten:

[ID] [EntityId] [IndicatorName] [IndicatorValue] 

Und die Ergebniswerte werden:

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' 

Und so weiter ..

Macht das Sinn? Haben Sie Vorschläge, wo Sie in T-SQL nachsehen können?

+2

Haben Sie sich in [Pivot/Unpivot] (http://msdn.microsoft.com/en-us/library/ms177410 (v = SQL.105) .aspx) noch? –

+0

Am Ende ging es mit der Bluefoot-Lösung. Elegant und funktional. Vielen Dank an alle. – Sergei

Antwort

154

können Sie verwenden, um die UNPIVOT Funktion, um die Spalten in Zeilen zu konvertieren:

select id, entityId, 
    indicatorname, 
    indicatorvalue 
from yourtable 
unpivot 
(
    indicatorvalue 
    for indicatorname in (Indicator1, Indicator2, Indicator3) 
) unpiv; 

Hinweis, die Datentypen der Spalten Sie die gleichen sein werden unpivoting müssen, damit Sie die Datentypen umwandeln könnte müssen vor der Anwendung entmutigen.

könnten Sie auch CROSS APPLY mit UNION verwenden, um alle Spalten zu konvertieren:

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); 

auf Ihrer Version von SQL Server Je könnten Sie sogar CROSS mit der VALUES-Klausel GILT verwenden:

select id, entityid, 
    indicatorname, 
    indicatorvalue 
from yourtable 
cross apply 
(
    values 
    ('Indicator1', Indicator1), 
    ('Indicator2', Indicator2), 
    ('Indicator3', Indicator3), 
    ('Indicator4', Indicator4) 
) c (indicatorname, indicatorvalue); 

Schließlich Wenn Sie 150 Spalten zum Entsperren haben und die gesamte Abfrage nicht fest codieren möchten, können Sie die SQL-Anweisung mit dynamischem SQL generieren:

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; 
+1

Tolle Erklärung! +1 – Oscar

+0

Wow große Antwort mit super Erklärung. Vielen Dank. + 1 von mir 2 – kevin

+4

Für diejenigen, die mehr Nüsse und Bolzen über 'UNPIVOT' und/vs wollen. "APPLY", [dieser 2010 Blog-Beitrag von Brad Schulz] (und http://bradsruminations.blogspot.com/2010/02/spotlight-on-unpivot-part-1.html) : //bradsruminations.blogspot.com/2010/02/spotlight-on-unpivot-part-2.html)) ist (sind) schön. – ruffin

15

gut Wenn Sie 150 Spalten haben, dann denke ich, dass UNPIVOT keine Option ist. So könnte man xml Trick

;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

Sie könnten verwendet auch dynamische SQL schreiben, aber Ich mag xml mehr - für dynamischen SQL haben Sie Berechtigungen Daten auszuwählen, direkt aus der Tabelle und Das ist nicht immer eine Option.

UPDATE
Da gibt es eine große Flamme in den Kommentaren, ich denke, ich werde einige Vor- und Nachteile von XML/Dynamic SQL hinzufügen. Ich werde versuchen, so objektiv wie möglich zu sein und Eleganz und Hässlichkeit nicht zu erwähnen. Wenn Sie irgendwelche anderen Vor-und Nachteile habe, die Antwort bearbeiten oder in den Kommentaren

Nachteile schreiben

  • es nicht so schnell als dynamische SQL, grobe Tests gab mir, dass xml ist etwa 2,5-mal langsamer diese dynamische (es war eine Abfrage in ~ 250000 Zeilen Tabelle, so dass diese Schätzung nicht genau ist).Sie können es selbst vergleichen, wenn Sie wollen, hier ist sqlfiddle Beispiel, auf 100000 Zeilen war es 29s (XML) vs 14s (dynamisch);
  • kann es sein könnte schwieriger zu verstehen für Leute, die nicht mit XPath vertraut sind;

Profis

  • es ist der gleiche Umfang wie Ihre andere Anfragen, und das sehr praktisch sein könnte. Einige Beispiele kommen
    • wohlgemerkt inserted und deleted Tabellen in Ihrem Trigger (nicht möglich mit dynamisch überhaupt) nicht abfragen;
    • Benutzer müssen nicht Berechtigungen auf direkt aus der Tabelle auswählen. Was ich meine ist, wenn Sie gespeicherte Prozeduren Ebene und Benutzer Berechtigungen zum Ausführen von sp haben, aber keine Berechtigungen haben, Tabellen direkt abzufragen, können Sie diese Abfrage weiterhin in gespeicherten Prozedur verwenden;
    • Sie könnten Abfrage Tabellenvariable Sie in Ihrem Namensraum veröffentlicht haben (es innerhalb der dynamischen SQL passieren Sie müssen entweder temporäre Tabelle machen statt oder Typ erstellen und als Parameter in dynamische SQL-Pass;
  • Sie können diese Abfrage innerhalb der Funktion tun (Skalar oder Tabellenwert) Es ist nicht möglich, dynamische SQL innerhalb der Funktionen zu verwenden;.
+2

Welche Daten wählen Sie mit XML aus, für die keine Daten aus der Tabelle ausgewählt werden müssen? –

+0

Zum Beispiel könnten Sie entscheiden, Benutzern nicht die Erlaubnis zu geben, Daten aus Tabellen auszuwählen, sondern nur gespeicherte Prozeduren, die mit Tabellen arbeiten, also könnte ich innerhalb der Prozedur für xml auswählen, aber ich muss einige Problemumgehungen verwenden, wenn ich dynamisches SQL verwenden möchte –

+3

Wenn Sie möchten, dass Ihre Benutzer den Code ausführen können, müssen Sie ihnen den Zugriff gewähren, den sie zum Ausführen des Codes benötigen. Stellen Sie keine Anforderungen, die nicht existieren, um Ihre Antwort besser klingen zu lassen (Sie müssen auch keine konkurrierenden Antworten kommentieren, um Ihre Antwort zu sehen - wenn sie diese Antwort gefunden haben, können sie auch Ihre finden). –

3
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 '[email protected]+' FROM [cdc].['[email protected]+'] (nolock)' 
--print (@script) 
exec (@script) 

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

hier ein anderes Verfahren für colu ist mns zu Zeilen, wie viele Tabelle und wie viele Spalten Sie haben, ist nicht wichtig. Stellen Sie einfach die Parameter ein und erhalten Sie das Ergebnis. Ich schrieb dies, weil ich manchmal das Ergebnis einer Tabelle A (die Spalte Ergebnismenge) ist, als Felder einer anderen Tabelle B (die Zeilenfelder sein muss). In diesem Fall weiß ich nicht, wie viele Felder ich für meine Tabelle B gesetzt habe.

1

Ich brauchte eine Lösung zum Konvertieren von Spalten in Zeilen in Microsoft SQL Server, ohne die Namen der Spalten (im Trigger verwendet) und ohne dynamische sql (dynamic sql ist zu langsam für die Verwendung in einem Trigger).

ich endlich diese Lösung gefunden, die gut funktioniert:

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) 

Wie Sie sehen können, konvertiere ich die Zeile in XML (Subquery i wählen, * für xml roh, diese konvertiert alle Spalten in einer XML-Spalte)

Dann CROSS APPLY eine Funktion für jedes XML-Attribut dieser Spalte, so dass ich eine Zeile pro Attribut erhalten.

Insgesamt konvertiert dies Spalten in Zeilen, ohne die Spaltennamen zu kennen und ohne dynamische SQL. Es ist schnell genug für meinen Zweck.

(Edit: Ich habe gerade gesehen, Roman Pekar oben beantworten, das die gleiche tut ich die dynamischen SQL-Trigger mit Cursor verwendet ersten, die als diese Lösung betrug 10 bis 100-mal langsamer, aber vielleicht war es durch die verursacht wird. Cursor, nicht durch die dynamische SQL. Wie auch immer, diese Lösung ist sehr einfach und universell, so ist es definitiv eine Option).

ich an dieser Stelle diesen Kommentar verlasse, weil ich diese Erklärung in meinem Beitrag über die vollen Audit-Trigger verweisen mag, dass Sie hier finden: https://stackoverflow.com/a/43800286/4160788

3

Nur neue Leser zu helfen, die ich angelegt habe ein Beispiel, um @ bluefeets Antwort zu UNPIVOT besser zu verstehen.

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; 
Verwandte Themen