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 using ComputationField

  • Precomputed 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 aggregating

  • crosstab_field — the column whose distinct values become the dynamic columns

  • crosstab_columns — list of database column name strings to read values from

  • columns — 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).