SQL Monthly Pivot Query Generator Using Conditional Aggregation
Planners and finance analysts keep asking for the same report: months across the top, items or customers down the side. This generator writes that cross-tab with plain CASE and SUM so it runs in any SQL database.
Fill in
Month columns with conditional aggregation
SELECT
item_code,
SUM(CASE WHEN MONTH(transaction_date) = 1 THEN quantity ELSE 0 END) AS m01_jan,
SUM(CASE WHEN MONTH(transaction_date) = 2 THEN quantity ELSE 0 END) AS m02_feb,
SUM(CASE WHEN MONTH(transaction_date) = 3 THEN quantity ELSE 0 END) AS m03_mar,
SUM(CASE WHEN MONTH(transaction_date) = 4 THEN quantity ELSE 0 END) AS m04_apr,
SUM(CASE WHEN MONTH(transaction_date) = 5 THEN quantity ELSE 0 END) AS m05_may,
SUM(CASE WHEN MONTH(transaction_date) = 6 THEN quantity ELSE 0 END) AS m06_jun,
SUM(CASE WHEN MONTH(transaction_date) = 7 THEN quantity ELSE 0 END) AS m07_jul,
SUM(CASE WHEN MONTH(transaction_date) = 8 THEN quantity ELSE 0 END) AS m08_aug,
SUM(CASE WHEN MONTH(transaction_date) = 9 THEN quantity ELSE 0 END) AS m09_sep,
SUM(CASE WHEN MONTH(transaction_date) = 10 THEN quantity ELSE 0 END) AS m10_oct,
SUM(CASE WHEN MONTH(transaction_date) = 11 THEN quantity ELSE 0 END) AS m11_nov,
SUM(CASE WHEN MONTH(transaction_date) = 12 THEN quantity ELSE 0 END) AS m12_dec,
SUM(quantity) AS period_total
FROM dbo.inventory_transactions
WHERE transaction_date >= '2025-01-01'
AND transaction_date < '2026-01-01'
AND transaction_type = 'ISSUE'
GROUP BY item_code
ORDER BY period_total DESC;Average monthly usage and months with activity
WITH monthly AS (
SELECT
item_code AS grp,
YEAR(transaction_date) AS yr,
MONTH(transaction_date) AS mth,
SUM(quantity) AS month_value
FROM dbo.inventory_transactions
WHERE transaction_date >= '2025-01-01'
AND transaction_date < '2026-01-01'
AND transaction_type = 'ISSUE'
GROUP BY item_code, YEAR(transaction_date), MONTH(transaction_date)
)
SELECT
grp,
COUNT(*) AS months_with_activity,
SUM(month_value) AS total_value,
AVG(month_value) AS avg_active_month,
MIN(month_value) AS min_month,
MAX(month_value) AS max_month
FROM monthly
GROUP BY grp
ORDER BY total_value DESC;Unpivoted monthly rows for charting
SELECT
item_code,
YEAR(transaction_date) AS yr,
MONTH(transaction_date) AS mth,
SUM(quantity) AS month_value
FROM dbo.inventory_transactions
WHERE transaction_date >= '2025-01-01'
AND transaction_date < '2026-01-01'
AND transaction_type = 'ISSUE'
GROUP BY item_code, YEAR(transaction_date), MONTH(transaction_date)
ORDER BY item_code, yr, mth;The first block assumes a window of at most 12 months in one calendar year. For a range spanning two years, use the third block or add YEAR to the CASE test, otherwise January of both years is merged. MONTH() and YEAR() are SQL Server, MySQL and PostgreSQL friendly; on Oracle use EXTRACT(MONTH FROM col). Date literals may need your engine's format.
Optional report automation
Tell us which ERP cross-tab you rebuild each month and we will outline how to automate it.
Optional. The tool and every export work without this. No spam.
How to use the generator
Enter the table or view, the column that labels each row, the date column and the value to add up. Set the start date inclusive and the end date exclusive, and add any filter such as the transaction type. Copy the first block for the cross-tab. Run it on a small group first and compare one row with the ERP's own inquiry screen. If it matches, widen the filter and paste the result into Excel for charts or a rolling forecast.
Why conditional aggregation
SQL Server has a PIVOT operator and some engines have crosstab extensions, but they differ between databases and need the column list in advance. Conditional aggregation uses SUM over a CASE per month, which is plain ANSI SQL, easy to read and easy to adjust, for example to add a quantity and a value pair. The cost is a wide query to maintain, which is why a generator helps. The date range should use a half-open interval so that the last day is never lost when the column has a time part.
Reading the result
The period total column sorts the biggest movers first. Use the second block when you need average usage for reorder points: it averages only months with activity, so an item used in 3 of 12 months looks very different from an item used every month. Compare average of active months with the total divided by the number of months to see lumpiness. That gap often explains why a safety stock calculation based on a flat average fails for lumpy demand.
Common mistakes
The most common error is a range crossing two calendar years in the first block, which merges months. Others are using BETWEEN with datetime values, which drops the final day, forgetting to filter reversals or voided rows, and summing a signed quantity where you meant issues only. Check the filter against a transaction type list in your data dictionary before trusting the totals.
Frequently Asked Questions
Why not use the PIVOT operator?
PIVOT works in SQL Server and Oracle but with different syntax and a fixed column list. Conditional aggregation is portable, easier to extend with extra measures and just as fast in most ERP reporting queries.
Can I do a rolling 12 months instead of a calendar year?
Yes. Use the unpivoted third block with a date range of the last 12 months, then pivot in Excel. Or replace the MONTH test with a month offset from the end date computed in your engine.
How do I avoid dropping the last day of the range?
Use a half-open range: on or after the start and strictly before the day after the end. Avoid BETWEEN with datetime columns because midnight ends the range.
Will this be slow on big tables?
It scans the rows in the date window once. An index on the date column, ideally including the group and value columns, helps. Filter by date range first and avoid wrapping the date column in functions in the WHERE clause.
If you want these cross-tabs scheduled from your ERP into a shared sheet or dashboard, we can build and maintain that for you.
Related Tools
SQL Running Total and Window Function Query Generator
Fill in your table and column names to get running total, previous-row change and top-N per group queries using window functions.
Developer ToolsInventory Aging Buckets SQL Generator
Build an inventory aging query with your own table and column names and bucket sizes, ready to paste into SQL Server style SQL.
Developer ToolsERP Reporting Query Quality Assessment
Answer 10 questions about how your team writes, tests, documents and runs SQL reports on ERP data, and get a score with recommendations.
Developer ToolsERP SQL Query Catalog Worksheet
An editable inventory of the queries and reports your team runs, with owner, frequency, runtime and monthly effort.
More Developer Tools tools
See all- SAP Sales Order ABAP SELECT Generator (VBAK, VBAP, VBFA)
- SAP Sales Order Create BAPI ABAP Generator (BAPI_SALESORDER_CREATEFROMDAT2)
- SAP ABAP Open Items SELECT Generator
- SAP ACDOCA SQL Query Generator
- SAP QM Inspection Lot ABAP SELECT Generator
- SAP Quality Notification Pareto SQL Query Generator
- SAP QM Usage Decision BAPI Skeleton
- ABAP Custom Exception Class Generator
- ABAP Unit Test Class Generator