SQL Pivot: How to Convert Rows to Columns
SQL pivot converts category values stored in rows into separate result columns. This example shows how to build a pivot-style table with conditional aggregation and FILTER.
Updated · SQL Arena team
What you will practice
- Turn known row categories into columns
- Use PostgreSQL aggregate FILTER
- Build a portable pivot with SUM(CASE ...)
- Choose deliberately between NULL and zero
- Reverse a fixed pivot with UNION ALL
A practice problem in the trainer
This is how a problem looks in SQL Arena: the task, the editor, the result and the database schema.
For every shop, return shop_name, web revenue as web_sales, and in-store revenue as store_sales. Keep shops even when they have no matching sales, and display missing channel totals as 0.
SELECT s.shop_name, COALESCE(SUM(sa.amount) FILTER (WHERE sa.channel = 'web'), 0) AS web_sales, COALESCE(SUM(sa.amount) FILTER (WHERE sa.channel = 'store'), 0) AS store_sales FROM shops s LEFT JOIN sales sa ON sa.shop_id = s.id GROUP BY s.id, s.shop_name ORDER BY s.shop_name;
| # | shop_name | web_sales | store_sales |
|---|---|---|---|
| 1 | Central | 90 | 110 |
| 2 | Harbor | 0 | 150 |
| 3 | Market | 0 | 0 |
| 4 | North | 120 | 80 |
For every shop, return shop_name, web revenue as web_sales, and in-store revenue as store_sales. Keep shops even when they have no matching sales, and display missing channel totals as 0.
SELECT s.shop_name, COALESCE(SUM(sa.amount) FILTER (WHERE sa.channel = 'web'), 0) AS web_sales, COALESCE(SUM(sa.amount) FILTER (WHERE sa.channel = 'store'), 0) AS store_sales FROM shops s LEFT JOIN sales sa ON sa.shop_id = s.id GROUP BY s.id, s.shop_name ORDER BY s.shop_name;
How SQL Pivot Works Step by Step
The sales table is in long format: the channel is stored as a row value such as web, store, or partner. The report needs a wide format where web and store are separate columns. PostgreSQL's FILTER syntax makes that concise: SUM(amount) performs the aggregation, while FILTER decides which rows are allowed into that particular sum.
Because the report starts from shops and keeps every shop, some groups have no matching rows for one of the requested channels. SUM then produces NULL for that measure. COALESCE changes the missing aggregate to 0, which is appropriate here because the report defines the value as sales through that channel. Harbor therefore gets zero web sales, and Market gets zero in both requested columns.
The same rows-to-columns query can be written with SUM(CASE WHEN channel = 'web' THEN amount ELSE 0 END). CASE is useful when you want a pattern that transfers easily across SQL dialects. Both approaches assume that the output columns are known in advance. The query explicitly defines web_sales and store_sales rather than discovering new columns at runtime.
SELECT s.shop_name, COALESCE(SUM(sa.amount) FILTER (WHERE sa.channel = 'web'), 0) AS web_sales, COALESCE(SUM(sa.amount) FILTER (WHERE sa.channel = 'store'), 0) AS store_sales FROM shops s LEFT JOIN sales sa ON sa.shop_id = s.id GROUP BY s.id, s.shop_name ORDER BY s.shop_name;
| shop_name | web_sales | store_sales |
|---|---|---|
| Central | 90 | 110 |
| Harbor | 0 | 150 |
| Market | 0 | 0 |
| North | 120 | 80 |
| Park | 70 | 0 |
Common SQL Pivot Mistakes
Returning NULL when the report expects zero
Conditional aggregates can return NULL when no matching rows exist. If the business meaning is 'there were zero sales', wrap the aggregate in COALESCE or use an appropriate ELSE branch in CASE.
Expecting static SQL to create arbitrary columns
A regular SELECT has a fixed output shape. SUM(CASE...) and FILTER work well when values such as web and store are known before the query is written. Truly dynamic categories usually require generated SQL or a long-format result instead.
Replacing every NULL with zero automatically
NULL can mean unknown, unavailable, or not applicable, while zero is an actual numeric value. A pivot query should only apply COALESCE when those meanings are genuinely interchangeable for the report.
Practice problems on this topic
SQL from zero → Analyst trackOnce the basic pivot pattern is clear, apply it to practical scorecards: split payment totals by payment method, calculate account balances from transaction types, or build delivery metrics by store. SQL Arena includes free Easy tasks that use these patterns, while more complex exercises extend them to larger analytical queries. For a structured route from fundamentals to analyst-style SQL, continue with the SQL Beginner → Analyst track.
- EasyfreeFinal balance by accountpostgresqlmysqlclickhouse
- EasyfreeApple Pay and other payment totalspostgresqlmysqlclickhouse
- EasyfreeShare of Same-Day Deliveriespostgresqlmysqlclickhouse
- EasyfreeStore delivery scorecardpostgresql
- MediumPremiumActivity by customerpostgresql
- MediumPremiumShare of patients in ward CARD-Apostgresqlmysqlclickhouse
- MediumPremiumPercentage of patients born in 2000postgresqlmysqlclickhouse
- MediumPremiumPupils Above the 50% Markpostgresqlmysqlclickhouse
Reference on this topic
In-depth articles with syntax and examples in the SQL Arena reference.
Frequently asked questions
What is SQL pivot?
SQL pivot is a pattern that turns category values from rows into separate columns. It is commonly used to build compact reports and summary tables with one row per entity or group.
How do you pivot rows to columns in SQL?
The exact syntax depends on the database. You can use a dedicated PIVOT operator where available, or use conditional aggregation with SUM, COUNT, CASE, or FILTER to calculate each output column.
How do you create a pivot table in SQL?
Choose the field that defines each result row, then create one aggregate expression for every category you want as a column. A GROUP BY combines the source rows while the conditional aggregates populate the pivoted columns.
Can you pivot in SQL without a PIVOT operator?
Yes. Conditional aggregation is a common alternative and works by calculating separate aggregates for different category values. Depending on the SQL dialect, those conditions can be written with CASE expressions or FILTER clauses.
Practice SQL Pivot Queries
SQL Arena contains 1,192 automatically checked tasks, with separate browser sandboxes for PostgreSQL, MySQL, ClickHouse, and BigQuery. The free plan needs no card and includes 292 tasks across all four dialects.
Practice in SQL ArenaMore practice from CodeRang
- Python ArenaPython Arena has 1300+ Python 3.12 problems covering functions, classes, async code and technical interview tasks.1300+ tasks · Python 3.12
- Playwright ArenaPlaywright Arena has 30 JavaScript and E2E problems where you write a test and immediately see what it did in the browser.30 tasks · JavaScript / E2E