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