Expert guide
Microsoft Access Crosstab Query Guide
Turn rows into a grid of months, regions or products, ready for reports
Call us at +1 (385) 386-3860
Short answer: a crosstab uses TRANSFORM aggregate SELECT row-heading FROM ... GROUP BY row-heading PIVOT column-heading. Fix the columns with PIVOT ... IN (...) so they always exist and stay in order, declare any form references with a PARAMETERS clause, wrap the aggregate in Nz to show zeros, and add a row total by including the aggregate again in the SELECT. Report-ready crosstabs are a regular part of our Access reports and forms work.
Managers think in grids: sales by month across the top and region down the side, jobs by status per technician, defects by type per supplier. Access stores data in rows. The crosstab query bridges the two, and once its few quirks are understood, it is one of the most useful tools in Access.
The Anatomy of a Crosstab
TRANSFORM Sum(l.Qty * l.UnitPrice) AS Sales -- value in each cell
SELECT c.Region -- row heading(s)
FROM (tblOrders AS o
INNER JOIN tblOrderLines AS l ON o.OrderID = l.OrderID)
INNER JOIN tblCustomers AS c ON o.CustomerID = c.CustomerID
WHERE o.OrderDate >= #2026-01-01# AND o.OrderDate < #2027-01-01#
GROUP BY c.Region
PIVOT Format(o.OrderDate, "mmm"); -- column heading| Clause | Role |
|---|---|
| TRANSFORM | The aggregate shown in each cell |
| SELECT ... GROUP BY | Row headings (one or more fields) |
| PIVOT | The field whose values become columns |
| WHERE | Filters before aggregation |
Fix Column Order and Presence
PIVOT Format(o.OrderDate, "mmm")
IN ("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec");Without IN, columns appear alphabetically (Apr, Aug, Dec...) and months with no sales disappear. With IN, every month is always present, in calendar order, which is what reports and exports need. The same idea applies to status columns: IN ("Open","Scheduled","In progress","Completed").
Zeros Instead of Blanks
TRANSFORM Nz(Sum(l.Qty * l.UnitPrice), 0) AS Sales
Row Totals
TRANSFORM Nz(Sum(l.Qty * l.UnitPrice), 0) AS Sales
SELECT c.Region, Sum(l.Qty * l.UnitPrice) AS [Year Total]
FROM ...
GROUP BY c.Region
PIVOT Format(o.OrderDate, "mmm") IN ("Jan","Feb", ... ,"Dec");Any aggregate in the SELECT list, besides the row headings, becomes an extra column calculated across the whole row.
Parameters From a Form
PARAMETERS [Forms]![frmReports]![txtStart] DateTime,
[Forms]![frmReports]![txtEnd] DateTime;
TRANSFORM Nz(Count(j.JobID), 0) AS Jobs
SELECT t.TechName
FROM tblJobs AS j INNER JOIN tblTechs AS t ON j.TechID = t.TechID
WHERE j.ScheduledOn Between [Forms]![frmReports]![txtStart]
And [Forms]![frmReports]![txtEnd]
GROUP BY t.TechName
PIVOT j.Status IN ("Open","Scheduled","In progress","Completed");The PARAMETERS line is mandatory for crosstabs that reference form controls. Normal select queries tolerate undeclared form references; crosstabs do not.
Multiple Values per Cell
A crosstab allows only one TRANSFORM value. To show, for example, both sales and margin by month, create two crosstabs and join them on the row heading in a third query, or use a select query with conditional aggregates instead:
SELECT c.Region, Sum(IIf(Month(o.OrderDate)=1, l.Qty*l.UnitPrice, 0)) AS Jan_Sales, Sum(IIf(Month(o.OrderDate)=1, l.Qty*(l.UnitPrice-l.UnitCost), 0)) AS Jan_Margin, ... FROM ... GROUP BY c.Region;
Conditional aggregates are more typing but behave like ordinary queries and translate directly to SQL Server.
Crosstabs and Reports
Always fix column headings, so report controls bind to stable field names (Jan, Feb...).
For rolling periods (last 12 months), pivot on a relative label such as M1 to M12 calculated from the date, and show real month names in report labels with expressions.
Add report-level totals in the report footer with =Sum([Jan]) and so on.
Relative month label for a rolling 12-month crosstab:
PIVOT "M" & (DateDiff("m", o.OrderDate, Date()) + 1)
IN ("M12","M11","M10","M9","M8","M7","M6","M5","M4","M3","M2","M1");Crosstab or Excel PivotTable?
| Need | Crosstab query | Excel PivotTable |
|---|---|---|
| Fixed, repeatable report | Yes | Possible |
| Ad hoc slicing by users | Limited | Excellent |
| Feeding an Access report | Yes | No |
| Large data in the database | Efficient | Needs export or connection |
For ad hoc questions in plain English instead of building crosstabs, see natural-language queries on Access; for writing crosstab SQL with AI, see ChatGPT for Access SQL.
FAQ
Access crosstab query FAQ
Columns, parameters and reports