Rows into columns. One row per month needs to become one row per year with a column per month. The book calls this a pivot.

// the problem

CREATE TABLE sales (
    year  INT,
    month VARCHAR(3),
    total DECIMAL(10,2)
);

-- year | month | total
-- 2002 | Jan   | 1800.00
-- 2002 | Feb   | 2400.00
-- 2002 | Mar   | 1950.00
-- 2002 | Q1    | 6150.00  <- quarterly rollup also present

Target output:

-- year | Jan     | Feb     | Mar     | ...
-- 2002 | 1800.00 | 2400.00 | 1950.00 | ...

// the original approach (2002)

No PIVOT keyword meant CASE inside an aggregate; one expression per output column:

SELECT
    year,
    SUM(CASE WHEN month = 'Jan' THEN total ELSE 0 END) AS Jan,
    SUM(CASE WHEN month = 'Feb' THEN total ELSE 0 END) AS Feb,
    SUM(CASE WHEN month = 'Mar' THEN total ELSE 0 END) AS Mar,
    SUM(CASE WHEN month = 'Apr' THEN total ELSE 0 END) AS Apr,
    SUM(CASE WHEN month = 'May' THEN total ELSE 0 END) AS May,
    SUM(CASE WHEN month = 'Jun' THEN total ELSE 0 END) AS Jun,
    SUM(CASE WHEN month = 'Jul' THEN total ELSE 0 END) AS Jul,
    SUM(CASE WHEN month = 'Aug' THEN total ELSE 0 END) AS Aug,
    SUM(CASE WHEN month = 'Sep' THEN total ELSE 0 END) AS Sep,
    SUM(CASE WHEN month = 'Oct' THEN total ELSE 0 END) AS Oct,
    SUM(CASE WHEN month = 'Nov' THEN total ELSE 0 END) AS Nov,
    SUM(CASE WHEN month = 'Dec' THEN total ELSE 0 END) AS Dec
FROM sales
WHERE month != 'Q1'
GROUP BY year;

Tedious and brittle. Add a new month abbreviation and it silently disappears from output.

// modern t-sql: the pivot operator

SQL Server 2005 added PIVOT:

SELECT year, Jan, Feb, Mar, Apr, May, Jun,
             Jul, Aug, Sep, Oct, Nov, Dec
FROM sales
WHERE month != 'Q1'
PIVOT (
    SUM(total)
    FOR month IN (Jan, Feb, Mar, Apr, May, Jun,
                  Jul, Aug, Sep, Oct, Nov, Dec)
) AS pvt;

Cleaner, but the column list is still hardcoded. Dynamic columns still require string-building and EXEC sp_executesql.

// postgres

No PIVOT keyword. Use FILTER:

SELECT
    year,
    SUM(total) FILTER (WHERE month = 'Jan') AS "Jan",
    SUM(total) FILTER (WHERE month = 'Feb') AS "Feb",
    SUM(total) FILTER (WHERE month = 'Mar') AS "Mar",
    -- ...
    SUM(total) FILTER (WHERE month = 'Dec') AS "Dec"
FROM sales
WHERE month != 'Q1'
GROUP BY year;

For dynamic pivots, crosstab() via the tablefunc extension works but has an awkward interface.

// oracle

SELECT *
FROM sales
WHERE month != 'Q1'
PIVOT (
    SUM(total)
    FOR month IN ('Jan' AS Jan, 'Feb' AS Feb, 'Mar' AS Mar,
                  'Apr' AS Apr, 'May' AS May, 'Jun' AS Jun,
                  'Jul' AS Jul, 'Aug' AS Aug, 'Sep' AS Sep,
                  'Oct' AS Oct, 'Nov' AS Nov, 'Dec' AS Dec)
)
ORDER BY year;

Oracle's PIVOT also supports ANY for dynamic column generation, with caveats.

// sqlite

No PIVOT. FILTER is supported from 3.30 (2019) and works identically to PostgreSQL. Use CASE for older versions.

// summary

SQL cannot produce a dynamically-columned result set without dynamic SQL. FILTER (Postgres, SQLite) and PIVOT (SQL Server, Oracle) are cleaner than the CASE wall, but still require enumerating columns up front.

If the number of output columns is truly dynamic, the pivot belongs in the application layer, not the query.

// dynamic pivot in c#: linq

record SaleRow(int Year, string Month, decimal Total);

var pivoted = rows
    .Where(r => r.Month != "Q1")
    .GroupBy(r => r.Year)
    .Select(g => new
    {
        Year    = g.Key,
        Columns = g.ToDictionary(r => r.Month, r => r.Total)
    })
    .OrderBy(r => r.Year)
    .ToList();

foreach (var row in pivoted)
{
    Console.WriteLine($"{row.Year}: Jan={row.Columns.GetValueOrDefault("Jan"):C}");
}

Assumes one row per month per year. For duplicates, replace ToDictionary with:

Columns = g.GroupBy(r => r.Month)
            .ToDictionary(m => m.Key, m => m.Sum(r => r.Total))

// dynamic pivot in c#: optimized

The LINQ version makes two passes per group and allocates a dictionary per year. For large result sets, a single-pass loop is faster with less GC pressure:

var pivot = new Dictionary<int, Dictionary<string, decimal>>();

foreach (var row in rows)
{
    if (row.Month == "Q1") continue;

    if (!pivot.TryGetValue(row.Year, out var months))
    {
        months = new Dictionary<string, decimal>(12); // pre-size: 12 months
        pivot[row.Year] = months;
    }

    months[row.Month] = months.TryGetValue(row.Month, out var existing)
        ? existing + row.Total
        : row.Total;
}

var allMonths = new List<string> { "Jan","Feb","Mar","Apr","May","Jun",
                                    "Jul","Aug","Sep","Oct","Nov","Dec" };

foreach (var (year, months) in pivot.OrderBy(kv => kv.Key))
{
    var values = allMonths.Select(m => months.GetValueOrDefault(m));
    Console.WriteLine($"{year}: {string.Join(" | ", values)}");
}

Differences vs. LINQ:

  • Single pass - no intermediate groupings
  • Pre-sized inner dictionary - avoids rehashing
  • No closure/enumerator allocation - LINQ's chain allocates per element; the loop does not

For typical reporting queries this difference is irrelevant. It matters at millions of rows in bulk exports, dashboards, or ETL pipelines.