SQL UNPIVOT
Introduction
When working with databases, you'll often encounter situations where data is stored in a wide format (many columns) but needs to be analyzed in a tall format (many rows). The SQL UNPIVOT
operation is a powerful tool that helps you transform columns into rows, effectively reversing what the PIVOT
operation does.
Think of UNPIVOT
as a way to normalize denormalized data. It's particularly useful when you need to:
- Convert reports or crosstab data back into a relational format
- Transform spreadsheet-like data into a more database-friendly structure
- Prepare data for certain types of analysis or visualization
Understanding UNPIVOT
The Concept
The UNPIVOT
operation takes multiple columns and converts them into rows with two main components:
- A column that will contain the former column names
- A column that will contain the values from those columns
Basic Syntax
SELECT column1, column2, ...
FROM table_name
UNPIVOT
(
value_column -- The column that will store the values
FOR name_column -- The column that will store the original column names
IN (column_list) -- The list of columns to be unpivoted
) AS unpivot_alias;
UNPIVOT in Action: Basic Example
Let's start with a simple example. Imagine we have a table of product sales by quarter:
Original Table: ProductSales
ProductID | Q1_2023 | Q2_2023 | Q3_2023 | Q4_2023
----------|---------|---------|---------|--------
1 | 10000 | 15000 | 12000 | 18000
2 | 8000 | 9000 | 10000 | 11000
3 | 5000 | 4000 | 4500 | 6000
To analyze this data by quarter, we can unpivot it:
SELECT
ProductID,
Quarter,
Sales
FROM ProductSales
UNPIVOT
(
Sales -- Column to store values
FOR Quarter -- Column to store original column names
IN (Q1_2023, Q2_2023, Q3_2023, Q4_2023) -- Columns to unpivot
) AS UnpivotedSales;
Result:
ProductID | Quarter | Sales
----------|---------|-------
1 | Q1_2023 | 10000
1 | Q2_2023 | 15000
1 | Q3_2023 | 12000
1 | Q4_2023 | 18000
2 | Q1_2023 | 8000
2 | Q2_2023 | 9000
2 | Q3_2023 | 10000
2 | Q4_2023 | 11000
3 | Q1_2023 | 5000
3 | Q2_2023 | 4000
3 | Q3_2023 | 4500
3 | Q4_2023 | 6000
The transformation is visualized below:
Handling NULL Values
By default, UNPIVOT
excludes rows where the original column values are NULL. If you want to include NULL values, you need to use a different approach. Here's how you can handle it:
SELECT
ProductID,
Quarter,
Sales
FROM
(
SELECT
ProductID,
ISNULL(Q1_2023, 0) AS Q1_2023,
ISNULL(Q2_2023, 0) AS Q2_2023,
ISNULL(Q3_2023, 0) AS Q3_2023,
ISNULL(Q4_2023, 0) AS Q4_2023
FROM ProductSales
) p
UNPIVOT
(
Sales
FOR Quarter IN (Q1_2023, Q2_2023, Q3_2023, Q4_2023)
) AS UnpivotedSales;
UNPIVOT with Multiple Measure Columns
Sometimes you might need to unpivot multiple measure columns at once. You can do this with a combination of UNPIVOT
and CROSS APPLY
.
Original Table: ProductMetrics
ProductID | Q1_Sales | Q1_Profit | Q2_Sales | Q2_Profit
----------|----------|-----------|----------|----------
1 | 10000 | 3000 | 15000 | 4500
2 | 8000 | 2400 | 9000 | 2700
To unpivot this into a format with Quarter, Metric, and Value columns:
SELECT
ProductID,
SUBSTRING(Measure, 1, 2) AS Quarter,
SUBSTRING(Measure, 4, LEN(Measure)) AS Metric,
Value
FROM ProductMetrics
UNPIVOT
(
Value
FOR Measure IN (Q1_Sales, Q1_Profit, Q2_Sales, Q2_Profit)
) AS UnpivotedMetrics;
Result:
ProductID | Quarter | Metric | Value
----------|---------|--------|-------
1 | Q1 | Sales | 10000
1 | Q1 | Profit | 3000
1 | Q2 | Sales | 15000
1 | Q2 | Profit | 4500
2 | Q1 | Sales | 8000
2 | Q1 | Profit | 2400
2 | Q2 | Sales | 9000
2 | Q2 | Profit | 2700
UNPIVOT in Different Database Systems
Microsoft SQL Server
SQL Server provides native UNPIVOT
support as shown in the examples above.
Oracle
Oracle's syntax is slightly different:
SELECT ProductID, Quarter, Sales
FROM ProductSales
UNPIVOT (Sales FOR Quarter IN (
Q1_2023 AS 'Q1_2023',
Q2_2023 AS 'Q2_2023',
Q3_2023 AS 'Q3_2023',
Q4_2023 AS 'Q4_2023'
));
PostgreSQL and MySQL
These databases don't have a native UNPIVOT
operator, but you can achieve the same result using UNION ALL
:
SELECT ProductID, 'Q1_2023' AS Quarter, Q1_2023 AS Sales FROM ProductSales
UNION ALL
SELECT ProductID, 'Q2_2023' AS Quarter, Q2_2023 AS Sales FROM ProductSales
UNION ALL
SELECT ProductID, 'Q3_2023' AS Quarter, Q3_2023 AS Sales FROM ProductSales
UNION ALL
SELECT ProductID, 'Q4_2023' AS Quarter, Q4_2023 AS Sales FROM ProductSales;
Real-World Application: Sales Analysis
Let's look at a practical example of how UNPIVOT
can be used in a real-world scenario.
Imagine you're analyzing sales data for a retail company. You have a table with monthly sales figures for different regions:
Original Table: RegionalSales
Region | Jan_2023 | Feb_2023 | Mar_2023 | Apr_2023 | May_2023 | Jun_2023
----------|----------|----------|----------|----------|----------|--------
North | 120000 | 115000 | 118000 | 125000 | 130000 | 140000
South | 95000 | 98000 | 102000 | 105000 | 108000 | 112000
East | 88000 | 90000 | 92000 | 94000 | 98000 | 105000
West | 110000 | 112000 | 115000 | 118000 | 122000 | 128000
To analyze sales trends over time or compare monthly performance across regions, you need to transform this wide format into a tall format:
SELECT
Region,
SUBSTRING(Month, 1, 3) AS MonthName,
SUBSTRING(Month, 5, 4) AS Year,
Sales
FROM RegionalSales
UNPIVOT
(
Sales
FOR Month IN (Jan_2023, Feb_2023, Mar_2023, Apr_2023, May_2023, Jun_2023)
) AS UnpivotedSales
ORDER BY Year, CASE
WHEN MonthName = 'Jan' THEN 1
WHEN MonthName = 'Feb' THEN 2
WHEN MonthName = 'Mar' THEN 3
WHEN MonthName = 'Apr' THEN 4
WHEN MonthName = 'May' THEN 5
WHEN MonthName = 'Jun' THEN 6
END,
Region;
This transformation allows you to:
- Create time series visualizations for each region
- Compare monthly performance across regions
- Calculate month-over-month growth rates
- Apply time-based aggregations (quarterly totals, etc.)
Using UNPIVOT for Data Cleaning
Another common use for UNPIVOT
is data cleaning and preparation. Consider a scenario where you have survey data with responses in multiple columns:
Original Table: SurveyResponses
RespondentID | Question1 | Question2 | Question3 | Question4
-------------|-----------|-----------|-----------|----------
1 | 5 | 4 | 3 | 5
2 | 4 | NULL | 5 | 4
3 | 3 | 5 | 4 | 2
Unpivoting this data makes it easier to analyze:
SELECT
RespondentID,
REPLACE(Question, 'Question', '') AS QuestionNumber,
Response
FROM SurveyResponses
UNPIVOT
(
Response
FOR Question IN (Question1, Question2, Question3, Question4)
) AS UnpivotedSurvey
WHERE Response IS NOT NULL;
Result:
RespondentID | QuestionNumber | Response
-------------|----------------|--------
1 | 1 | 5
1 | 2 | 4
1 | 3 | 3
1 | 4 | 5
2 | 1 | 4
2 | 3 | 5
2 | 4 | 4
3 | 1 | 3
3 | 2 | 5
3 | 3 | 4
3 | 4 | 2
This makes it much easier to perform analyses like:
- Average score per question
- Distribution of responses for each question
- Correlation between different question responses
Summary
The SQL UNPIVOT
operation is a powerful tool for transforming wide format data (many columns) into tall format data (many rows). It's essential for data analysis, reporting, and data cleaning tasks.
Key takeaways:
UNPIVOT
converts columns into rows with two main components: a column for names and a column for values- It's the opposite of the
PIVOT
operation - Different database systems have slightly different syntax
- When
UNPIVOT
isn't available, you can useUNION ALL
as an alternative - Real-world applications include sales analysis, survey data analysis, and data cleaning
Practice Exercises
-
Given a table
EmployeeSkills
with columnsEmployeeID
,SQL
,Python
,Java
, andJavaScript
with values 1-5 representing skill levels, write a query to unpivot this data. -
You have a
WeatherData
table with columnsCityID
,Jan_Temp
,Feb_Temp
,Mar_Temp
, etc. Write an unpivot query to analyze temperature trends over months. -
Challenge: For a table
ProductInventory
with columnsProductID
,WarehouseA_Stock
,WarehouseA_Capacity
,WarehouseB_Stock
,WarehouseB_Capacity
, write a query that unpivots the data to show each warehouse's stock and capacity in separate rows.
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)