A junk dimension grew from 200 rows to 3 million after a column was added — what went wrong?
answer
- what bounds a junk dimension's row count?
- one column can multiply the whole table
- check distinct counts per column first
- 'Y', 'y' and 'Y ' are three values
- also check for duplicate combination rows
basics
~20 sAlmost certainly a high-cardinality or unnormalized value was admitted — an identifier, a timestamp, an amount, or codes differing only by case or whitespace — so the combination count multiplied. Diagnose per-column distinct counts, then move the offender out.
solid answer
~50 sA junk dimension holds one row per distinct **combination** of low-cardinality flags, so its size is the product of its columns' cardinalities. Adding a column with thousands of distinct values multiplies the table by that factor. The usual culprits are an identifier that should be a degenerate dimension in the fact row, a timestamp or amount that is really a measure, a free-text field, or a code set that looks small but is dirty — `Y`, `y` and `Y ` counting as three values. A second cause is non-deterministic key assignment during the load, inserting a fresh row for a combination that already exists. Diagnose with per-column `COUNT(DISTINCT)` and a duplicate-combination check on the combination columns. Fix by removing or re-homing the offending column, normalizing values, rebuilding from the combinations that actually occur, and re-keying the affected facts.
code
sql · 7 linesSELECT COUNT(*) AS total_rows,
COUNT(DISTINCT order_channel) AS n_channel,
COUNT(DISTINCT payment_type) AS n_payment,
COUNT(DISTINCT ship_mode) AS n_ship,
COUNT(DISTINCT gift_wrap_flag) AS n_gift,
COUNT(DISTINCT promo_code) AS n_promo
FROM dim_order_indicator;go deeper
Know that a junk dimension's size is the product of its columns' cardinalities, so one column with many distinct values multiplies the whole table.
Explain the diagnosis: per-column distinct counts to find the dominating column, sample values to tell an identifier from a continuous value from a dirty code set, and a duplicate check on the combination columns.
Show the full incident response — re-home the column according to what it really is, normalize values at load, rebuild the dimension, re-key affected facts, and reconcile totals before and after.
Own the prevention: load-time tests that enforce a row ceiling and combination uniqueness, plus a review standard for what may enter these tables, so the design intent is enforced by the pipeline rather than by memory.
## Why the size exploded at all The grain of a junk dimension is one row per distinct combination of its columns, so its row count is bounded by the product of the columns' cardinalities. Four columns with 3, 3, 2 and 2 values cap the table at 36 rows. Add a fifth column with 20,000 distinct values and the cap becomes 720,000. The pattern's entire economy depends on every member being low cardinality, and it has no defence against a column that is not. ## The four causes, in the order you should check them **1. A high-cardinality identifier was admitted.** Someone had a leftover column — `order_number`, `session_id`, `external_reference` — and swept it into the junk dimension because it did not belong to any other dimension. Identifiers with no descriptive attributes belong in the fact row as degenerate dimensions, precisely because a table for them would have one row per document. **2. A continuous value was admitted.** A timestamp, an amount, a weight, a raw score. Each distinct value creates its own combination. If the value is a measure, it belongs on the fact table; if it is genuinely a grouping attribute, it must be banded into a handful of ranges first. **3. The values are dirty.** This one is nasty because the column looks legitimate. A flag whose source sends `Y`, `y`, `YES`, `Yes` and `Y ` contributes five values instead of two, and the effect multiplies against every other column. A null that is sometimes an empty string is the same problem. A mixed-locale or unit-suffixed code set likewise. **4. Key assignment is non-deterministic.** The load inserts a new surrogate row for a combination it fails to match — because the lookup compares differently-typed or differently-trimmed values, because it runs before deduplication, or because concurrent load tasks each insert the same new combination. Here the tell is different: not high cardinality per column, but duplicate rows with identical combination values. ## Diagnosing it Two queries settle it in a minute. First, per-column distinct counts, which immediately identifies whether one column dominates: ```sql SELECT COUNT(*) AS rows, COUNT(DISTINCT order_channel) AS n_channel, COUNT(DISTINCT payment_type) AS n_payment, COUNT(DISTINCT ship_mode) AS n_ship, COUNT(DISTINCT gift_wrap_flag) AS n_gift, COUNT(DISTINCT new_column) AS n_new FROM dim_order_indicator; ``` If `n_new` is in the thousands, cause 1, 2 or 3 is confirmed and the next step is to look at sample values to tell them apart. Second, a grain-uniqueness test on the combination columns, which catches cause 4: ```sql SELECT order_channel, payment_type, ship_mode, gift_wrap_flag, COUNT(*) AS dup FROM dim_order_indicator GROUP BY order_channel, payment_type, ship_mode, gift_wrap_flag HAVING COUNT(*) > 1; ``` Any rows returned mean the same combination was issued more than one surrogate key, which also means facts that should share a key are split across several, and any report grouping by the junk attributes will look right while any report counting distinct keys will not. ## Fixing it Re-home the offending column first, according to what it actually is: - An identifier goes into the fact row as a degenerate dimension. - A measure goes into the fact row as a measure. - A continuous grouping attribute gets banded, with the bands agreed with the business, and then it may come back in. - An attribute with its own descriptive attributes deserves a real dimension of its own. - A dirty code set gets normalized at the point of load — trim, case-fold, map synonyms to a canonical value — and a test that fails the load if an unexpected value appears. Then rebuild. If the combination space is bounded, enumerate the cross-product and reissue keys; if it is sparse, rebuild from the combinations actually observed. Either way the facts must be re-keyed, which is the expensive part and the reason this is a real incident rather than a tidy-up. Do it as a rebuild of the dimension plus a re-derivation of the key on affected fact rows, and reconcile counts before and after so you can prove nothing was lost. ## Preventing the next one Add a test that fails the build when the dimension exceeds an agreed row ceiling, and one that fails when the combination columns are not unique. Both are cheap, both catch the whole class of failure at load time rather than when an analyst notices a dashboard has gone slow, and both express the design intent — that this table is small by construction — as something the pipeline enforces rather than something a reviewer must remember. ## What an interviewer wants to hear That you know the size is a product of cardinalities, that you would look at per-column distinct counts before theorizing, that you can name the four causes and distinguish them by evidence, and that your fix includes re-keying the facts and adding a guard rather than just deleting rows.
- How do you tell a high-cardinality column apart from a broken key assignment as the cause?Per-column distinct counts distinguish them. If one column has thousands of values, an identifier, a continuous value or a dirty code set got in. If every column is still low cardinality but grouping by the combination columns returns duplicates, the load issued several surrogate keys for the same combination.
- Once you have removed the offending column, why is re-keying the fact rows the expensive part?Every fact row points at a surrogate key from the old dimension, and those keys no longer describe the corrected combination set. You must rebuild the dimension, re-derive each fact's key from its underlying attribute values, and reconcile row counts and metric totals before and after to prove nothing was lost or double counted.
- What guard would you add so this cannot recur silently?Two load-time tests: one that fails the build when the dimension exceeds an agreed row ceiling, and one that fails when the combination columns are not unique. Together they encode the design intent — small by construction, one row per combination — so a bad column is caught at load rather than by an analyst weeks later.
saying these in an interview costs you the question
- Deletes rows instead of removing the offending column
- Assumes growth is normal because the business grew
- Ignores duplicate combinations from non-deterministic keys
- Overlooks dirty values like 'Y', 'y' and 'YES'
- Fixes the dimension without re-keying affected facts