SQL Server PIVOT & UNPIVOT – Daten drehen leicht gemacht
Einleitung: Was ist PIVOT und UNPIVOT?
Im täglichen Reporting stoßen wir häufig auf das Bedürfnis, Daten von Zeilen in Spalten umzudrehen (PIVOT) oder mehrere Spalten in Zeilen zu überführen (UNPIVOT). SQL Server bietet hierfür die Operatoren PIVOT und UNPIVOT – relationale Operatoren, die seit SQL Server 2005 verfügbar sind. In diesem Artikel erkläre ich anhand praktischer Beispiele, wie Sie sie effektiv einsetzen, welche Fallstricke es gibt und wann Alternativen wie die klassische CASE-basierten Aggregationen oder dynamisches SQL besser geeignet sind.
📊 1. PIVOT – Aus Zeilen werden Spalten
Nehmen wir eine typische Verkaufstabelle mit monatlichen Umsätzen. Oft möchten Sie die Monate als Spalten darstellen. PIVOT erledigt dies mit einer übersichtlichen Syntax.
-- Beispieldaten: Verkäufe pro Monat und Produktkategorie
CREATE TABLE #Sales (Category VARCHAR(20), MonthName VARCHAR(10), Amount DECIMAL(10,2));
INSERT INTO #Sales VALUES
('Elektronik', 'Jan', 1500), ('Elektronik', 'Feb', 2200), ('Elektronik', 'Mär', 1800),
('Bekleidung', 'Jan', 800), ('Bekleidung', 'Feb', 1200), ('Bekleidung', 'Mär', 950);
-- PIVOT: Jeder Monat wird eine Spalte
SELECT Category, Jan, Feb, Mär
FROM (
SELECT Category, MonthName, Amount FROM #Sales
) AS SourceTable
PIVOT (
SUM(Amount) FOR MonthName IN ([Jan], [Feb], [Mär])
) AS PivotTable;
Ergebnis:
Category Jan Feb Mär
Elektronik 1500 2200 1800
Bekleidung 800 1200 950
Alle nicht genannten Spalten (hier Category) bleiben als Zeilen gruppiert erhalten – ähnlich wie bei GROUP BY.
🔄 2. UNPIVOT – Aus Spalten werden Zeilen
UNPIVOT ist der Gegenoperator: Sie haben bereits eine breite Tabelle (z. B. mit monatlichen Spalten) und möchten diese normalisieren. UNPIVOT wandelt mehrere Spalten in Zeilenpaare um.
-- Breite Tabelle (Ergebnis eines PIVOT)
CREATE TABLE #SalesPivot (Category VARCHAR(20), Jan DECIMAL(10,2), Feb DECIMAL(10,2), Mär DECIMAL(10,2));
INSERT INTO #SalesPivot VALUES
('Elektronik', 1500, 2200, 1800),
('Bekleidung', 800, 1200, 950);
-- UNPIVOT: Spalten Jan, Feb, Mär in Zeilen umwandeln
SELECT Category, MonthName, Amount
FROM #SalesPivot
UNPIVOT (
Amount FOR MonthName IN (Jan, Feb, Mär)
) AS unpvt;
UNPIVOT erzeugt für jede angegebene Spalte (Jan, Feb, Mär) eine Zeile, in der MonthName den ursprünglichen Spaltennamen und Amount den Wert enthält. NULL-Werte werden automatisch ignoriert (die Zeile entfällt).
🧠 3. PIVOT mit mehreren Aggregationen & dynamischen Spalten
Oft benötigen Sie nicht nur eine Aggregation, sondern z. B. gleichzeitig Summe und Anzahl. PIVOT erlaubt nur eine Aggregatsfunktion – für mehrere müssen Sie auf die klassische CASE-Methode ausweichen oder zweimal pivottieren und die Ergebnisse joinen.
Beispiel mit CASE (flexibel, auch für dynamische Spalten geeignet):
SELECT Category,
SUM(CASE WHEN MonthName = 'Jan' THEN Amount END) AS Jan,
SUM(CASE WHEN MonthName = 'Feb' THEN Amount END) AS Feb
FROM #Sales
GROUP BY Category;
⚠️ 4. Fallstricke und Besonderheiten
- Datentypen: PIVOT verwendet implizite Konvertierungen. Stellen Sie sicher, dass die gepivotete Spalte und die Zielspalten kompatible Datentypen haben. Bei UNPIVOT müssen alle aufgelisteten Spalten denselben Datentyp haben – sonst erhalten Sie einen Fehler (verwenden Sie CAST/CONVERT).
- NULL-Werte: PIVOT ignoriert Zeilen, in denen die gepivotete Spalte NULL ist? Nein – die Zeile taucht auf, aber der aggregierte Wert kann NULL sein. UNPIVOT ignoriert NULL-Quellwerte – die resultierende Zeile wird nicht erzeugt.
- Sortierung und Spaltennamen: Spaltennamen im PIVOT werden zu Bezeichnern. Verwenden Sie bei gemischten Groß-/Kleinschreibung oder Sonderzeichen eckige Klammern.
- Leistung: PIVOT ist oft gut optimiert, kann aber bei sehr vielen Spalten zu komplexen Plänen führen. Die CASE-Methode skaliert oft besser, wenn viele Zeilen aggregiert werden müssen.
📐 5. Praktisches Beispiel: Quartalsumsätze pro Produkt
Ein realistischeres Szenario: Sie möchten monatliche Verkaufszahlen in Quartalsspalten gruppieren.
WITH MonthlySales AS (
SELECT ProductID,
DATEPART(QUARTER, OrderDate) AS Quarter,
SUM(Amount) AS TotalAmount
FROM Sales
GROUP BY ProductID, DATEPART(QUARTER, OrderDate)
)
SELECT ProductID, [1] AS Q1, [2] AS Q2, [3] AS Q3, [4] AS Q4
FROM MonthlySales
PIVOT (
SUM(TotalAmount) FOR Quarter IN ([1],[2],[3],[4])
) AS PivotQuarter;
🛠️ 6. Dynamisches PIVOT (wenn die Werte nicht feststehen)
Manchmal kennen Sie die Werte, die zu Spalten werden sollen, zur Entwicklungszeit nicht (z. B. verschiedene Produktnamen). Dann hilft dynamisches SQL:
DECLARE @columns NVARCHAR(MAX), @sql NVARCHAR(MAX);
-- Spaltenliste dynamisch aus Tabelle erzeugen
SELECT @columns = STRING_AGG(QUOTENAME(MonthName), ', ')
FROM (SELECT DISTINCT MonthName FROM #Sales) AS Months;
SET @sql = '
SELECT Category, ' + @columns + '
FROM (
SELECT Category, MonthName, Amount FROM #Sales
) AS src
PIVOT (
SUM(Amount) FOR MonthName IN (' + @columns + ')
) AS pvt';
EXEC sp_executesql @sql;
📋 7. Alternativen und wann man sie einsetzt
- Klassische Fallunterscheidung (CASE): Flexibler, erlaubt mehrere Aggregate und ist einfacher zu lesen, wenn die Spalten feststehen. Oft performanter als PIVOT bei großen Datenmengen.
- STRING_AGG + JSON / XML: Für sehr dynamische Spalten oder wenn Sie die Ergebnisse direkt in einem anderen Format benötigen.
- Power Query / Berichtstools: Manchmal ist es besser, die Transformation im Reporting-Tool vorzunehmen, um die Datenbank zu entlasten.
🎯 Fazit
PIVOT und UNPIVOT sind mächtige Werkzeuge, um Daten zwischen Zeilen- und Spaltenorientierung zu konvertieren. Sie machen Abfragen kompakter und lesbarer, haben aber ihre Tücken (feste Spaltenliste, nur ein Aggregat). In der Praxis kombiniere ich sie oft mit CTEs, dynamischem SQL oder weiche auf die bewährte CASE-Methode aus. Entscheidend ist, die Anforderungen zu verstehen: Handelt es sich um einen festen Bericht oder ein flexibles Dashboard? Für periodische, feste Reports (Monate, Quartale) ist PIVOT ideal – für frei wählbare Dimensionen greifen Sie zu dynamischen Lösungen.
powered by dtcSoftware