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).