Precomputed Crosstab Reports
General use case
Precomputed crosstab reports are designed for pre-computed or pre-aggregated data stored as rows in the database.
Instead of aggregating raw transactions at report time (like regular crosstab reports do),
setting crosstab_precomputed = True reads existing values and spreads a field’s distinct values into columns.
This is common with:
Materialized views or summary tables
Data warehouse fact tables
ETL pipeline outputs
Pre-aggregated reporting tables
Legacy tables with denormalized data
Example source data:
product_id |
month |
total_sales |
total_quantity |
|---|---|---|---|
1 |
2024-01-01 |
500 |
10 |
1 |
2024-02-01 |
600 |
12 |
2 |
2024-01-01 |
300 |
5 |
2 |
2024-02-01 |
400 |
8 |
The precomputed crosstab report transforms this into:
product_id |
total_sales Jan |
total_sales Feb |
|---|---|---|
1 |
500 |
600 |
2 |
300 |
400 |
How it relates to regular crosstab
Precomputed crosstab is a mode of the standard crosstab feature. Both spread a field’s distinct values as columns. The difference:
Regular crosstab (
crosstab_precomputed = False) aggregates raw data per crosstab value usingComputationFieldPrecomputed crosstab (
crosstab_precomputed = True) reads pre-existing values from the database — no aggregation
Both modes use the same metadata and frontend integration, so all existing chart types (bar, line, pie, area) work with precomputed crosstab data.
Basic example
from slick_reporting.views import ReportView, Chart
class MonthlyProductSales(ReportView):
report_model = MonthlySummary
date_field = "month"
group_by = "product_id"
crosstab_field = "month"
crosstab_columns = ["total_sales", "total_quantity"]
crosstab_precomputed = True
columns = ["product_id", "__crosstab__"]
chart_settings = [
Chart(
"Monthly Sales",
Chart.LINE,
data_source=["total_sales"],
title_source=["product_id"],
),
]
Key attributes:
crosstab_precomputed = True— read pre-computed values instead of aggregatingcrosstab_field— the column whose distinct values become the dynamic columnscrosstab_columns— list of database column name strings to read values fromcolumns— use"__crosstab__"as a placeholder for where crosstab columns appear
Crosstab by a non-date field
The crosstab field doesn’t have to be a date — it can be any column:
class SalesByRegion(ReportView):
report_model = RegionalSummary
group_by = "product_id"
crosstab_field = "region"
crosstab_columns = ["total_sales"]
crosstab_precomputed = True
columns = ["product_id", "__crosstab__"]
This produces one row per product with a column for each region.
Note
When multiple source rows exist for the same (group_by, crosstab_field) combination,
the last row encountered is used. Precomputed crosstab does not aggregate — if you
need aggregation, use a standard crosstab (without crosstab_precomputed) or time series report instead.
Using with dynamic models
Precomputed crosstab reports pair naturally with table_name for tables without a Django model:
class WarehouseReport(ReportView):
table_name = "warehouse_monthly_sales"
date_field = "period_date"
group_by = "sku"
crosstab_field = "period_date"
crosstab_columns = ["revenue", "units_sold"]
crosstab_precomputed = True
columns = ["sku", "__crosstab__"]
See Dynamic Models for more on reporting from arbitrary database tables.
Using with ReportGenerator directly
from slick_reporting.generator import ReportGenerator
report = ReportGenerator(
report_model=MonthlySummary,
group_by="product_id",
date_field="month",
crosstab_field="month",
crosstab_columns=["total_sales"],
crosstab_precomputed=True,
columns=["product_id", "__crosstab__"],
start_date=datetime.datetime(2024, 1, 1),
end_date=datetime.datetime(2024, 6, 30),
)
data = report.get_report_data()
Date filtering
When date_field is set, the precomputed crosstab respects start_date and end_date filters.
Only rows within the date range are included, and only crosstab values found in those rows
appear as columns.
Crosstab values with spaces or special characters
Crosstab values like "New York" or "Q1/2024" are sanitized for use in column names
(non-alphanumeric characters replaced with underscores). The verbose_name preserves the
original value for display in tables and charts.
For example, a crosstab value of "New York" produces:
Column name:
total_salesCTNew_York(sanitized, used internally)Verbose name:
total_sales New York(original, displayed to users)
Limitations
No aggregation. Precomputed crosstab reads pre-existing values. If you need computation (Sum, Count, etc.), use a standard crosstab without
crosstab_precomputed.Last value wins. If multiple rows share the same
(group_by, crosstab_field)value, the last row’s values are used.No FK traversal. When using dynamic models, foreign key columns are plain integers. Group by the column directly (e.g.
product_id) rather than traversing (product__name).