(+351) 21 24 10006  ·  info@bconcepts.pt
Carnaxide, Lisbon

How to create a Degenerate Dimension with rolling attributes in Data Modeling (Kimball)

João Barros 04 de November de 2026 5 min read

This tutorial shows how to create a Degenerate Dimension that supports rolling attributes in Data Modeling (Kimball), useful when a transaction identifier carries small attributes that change frequently and do not justify a separate dimension. The technique reduces joins and maintains performance without losing traceability.

Prerequisites

  • Basic knowledge of Data Modeling (Kimball) and star schema.
  • Access to SQL (e.g.: SQL Server, PostgreSQL or equivalent) for testing.
  • Examples of transaction data with small, mutable attributes (e.g.: short state, quick codes).

Step 1: Understand when to use a Degenerate Dimension with rolling attributes

A Degenerate Dimension (DD) is a set of attributes stored in the fact table or linked to the fact by a degenerate identifier. Here we want to support "rolling attributes": small values that change frequently per transaction (e.g.: short state, flags, external codes) that do not justify a wide dimension. We use the DD to avoid creating many small dimensions and to speed up aggregate queries.

Step 2: Choose attributes and define retention rules

Identify which attributes are candidates: they are typically short (varchar/char), have a low number of distinct values and high volatility. Define rules: which attributes go into the DD, the temporal granularity (latest version per transaction, history per event) and the retention period (e.g.: 90 days).

Step 3: Model the structure in the star schema

There are two common approaches: (A) store the attributes directly in the fact table as degenerate columns; (B) create a separate Degenerate Dimension table with degenerate_id key and link to the fact via that key. Option B keeps the fact more compact if there are multiple rolling attributes and multiple versions per transaction.

-- Exemplo: criar Dimension Degenerate separada (PostgreSQL/SQL Server sintaxe genérica)
CREATE TABLE dim_degenerate (
  degenerate_id BIGINT IDENTITY PRIMARY KEY,
  transaction_id VARCHAR(50) NOT NULL,
  attribute_key VARCHAR(50) NOT NULL,
  attribute_value VARCHAR(200),
  valid_from TIMESTAMP NOT NULL,
  valid_to TIMESTAMP NULL
);

CREATE INDEX idx_deg_txn_key ON dim_degenerate(transaction_id, attribute_key, valid_from);

-- Fact table guarda degenerate_id (última versão ou referência por evento)
CREATE TABLE fact_sales (
  sale_key BIGINT IDENTITY PRIMARY KEY,
  sale_date DATE NOT NULL,
  transaction_id VARCHAR(50) NOT NULL,
  degenerate_id BIGINT NULL,
  amount DECIMAL(18,2),
  FOREIGN KEY (degenerate_id) REFERENCES dim_degenerate(degenerate_id)
);

Step 4: Define ETL/ELT process for rolling attributes

The process should insert new versions into dim_degenerate and update links in the fact according to the rule (e.g.: always link to the most recent version when closing the transaction). Use merge/upsert to keep history with valid_from/valid_to or overwrite when history is not required.

-- Exemplo MERGE (SQL Server) para inserir nova versão e fechar anterior
MERGE INTO dim_degenerate AS target
USING (VALUES (@transaction_id, @attribute_key, @attribute_value, @now))
       AS source(transaction_id, attribute_key, attribute_value, valid_from)
ON target.transaction_id = source.transaction_id
   AND target.attribute_key = source.attribute_key
   AND target.valid_to IS NULL
WHEN MATCHED AND target.attribute_value <> source.attribute_value
  THEN UPDATE SET valid_to = source.valid_from
WHEN NOT MATCHED BY TARGET
  THEN INSERT (transaction_id, attribute_key, attribute_value, valid_from)
       VALUES (source.transaction_id, source.attribute_key, source.attribute_value, source.valid_from);

-- Depois, ajustar fact_sales.degenerate_id para apontar para a nova versão (poderá usar SELECT para obter degenerate_id recém-criada)

Step 5: Queries and aggregations: use the DD efficiently

For reports, combine the fact with dim_degenerate filtering by attribute_key and time window. For performance, prefer linking via degenerate_id in the fact when possible; otherwise, join by transaction_id + validity.

-- Exemplo: obter último attribute_value por venda
SELECT f.sale_key, f.amount, d.attribute_value
FROM fact_sales f
LEFT JOIN dim_degenerate d
  ON f.degenerate_id = d.degenerate_id;

-- Ou, se não tiver degenerate_id, buscar por transaction_id e data
SELECT f.sale_key, f.amount, d.attribute_value
FROM fact_sales f
LEFT JOIN dim_degenerate d
  ON d.transaction_id = f.transaction_id
  AND d.valid_from <= f.sale_date
  AND (d.valid_to IS NULL OR d.valid_to > f.sale_date)
  AND d.attribute_key = 'status';

Verify the result

Validate that: (1) each transaction has the correct version of the attribute (see examples with dates); (2) aggregate queries use indexes and are fast; (3) the volume of versions in dim_degenerate matches expectations (e.g.: there are no duplicate versions). Simple tests: compare counts between the event source and dim_degenerate, and verify that fact_sales points to the correct degenerate_id.

Conclusion

A Degenerate Dimension with rolling attributes reduces complexity when there are many short, volatile attributes, preserving performance and history when needed. Next steps: define purge policies, monitor cardinality and consider compression/partitioning. Tip: start with a few keys and validate retention rules before generalizing to all transactions.