skip to content

What is a factless fact table, and what kinds of questions does it answer?

level: middleimportance: nice to knowfreq 42%

answer

  1. a fact table with no numbers in it
  2. what is being measured if nothing is summed?
  3. attendance and eligibility are the two shapes
  4. how do you count what did not happen?
  5. set difference against the sales facts

basics

~20 s

A factless fact table records that something happened or that a condition applied, using only dimension keys and no numeric measures. It comes in two flavours: event tracking, such as student attendance, and coverage, such as which products were on promotion.

solid answer

~50 s

A **factless fact table** has the foreign keys of a fact table but no numeric measures — the existence of the row is the measurement, and you count rows. There are two recognised flavours. **Event-tracking** factless tables record that an event occurred with no quantity attached: a student attended a class, a customer viewed a page, an employee completed a course. The interesting numbers are counts and distinct counts across dimensions. **Coverage** factless tables record a condition that applied over a period rather than an event: which products were on promotion in which stores this week, which plans a member was eligible for, which salespeople were assigned to which territories. These exist mainly to answer negative questions — the classic being "which promoted products sold nothing" — which you get by set-differencing the coverage table against the sales fact table. Some teams add a constant `count = 1` column purely so BI tools have something to aggregate; that is a convenience, not a requirement.

code

sql · 16 lines
sql
-- coverage flavour: which store-product-weeks were promoted
CREATE TABLE fct_promotion_coverage (
  week_key      INTEGER NOT NULL,
  store_key     INTEGER NOT NULL,
  product_key   INTEGER NOT NULL,
  promotion_key INTEGER NOT NULL
);

-- the negative question a sales fact table cannot answer
SELECT c.store_key, c.product_key
FROM fct_promotion_coverage c
WHERE c.week_key = 202612
  AND NOT EXISTS (SELECT 1 FROM fct_sales s
                  WHERE s.week_key = c.week_key
                    AND s.store_key = c.store_key
                    AND s.product_key = c.product_key);

go deeper

for a junior

Recall that a factless fact table has dimension keys but no measures, and that you analyse it by counting rows. Class attendance is the standard example.

for a middle

Distinguish the two flavours — event tracking versus coverage — and explain the set-difference pattern that answers what did not happen.

for a senior

Show awareness of scale and sourcing: coverage tables are dense, are loaded from eligibility or promotion sources rather than events, and their distinct counts are non-additive.

for a principal

Judge whether a coverage table is worth its storage against the negative questions it unlocks, and set the convention for whether teams carry a constant counter column at all.

## Definition A factless fact table is a fact table whose row carries only dimension foreign keys (plus perhaps a degenerate key such as a document number) and **no numeric measures**. It seems odd at first — a fact table with no facts — but the row's *existence* is the fact. The measurement is the count of rows meeting a set of dimensional constraints. ```sql CREATE TABLE fct_class_attendance ( date_key INTEGER NOT NULL, student_key INTEGER NOT NULL, course_key INTEGER NOT NULL, teacher_key INTEGER NOT NULL, room_key INTEGER NOT NULL ); ``` There is nothing to sum here, but there is plenty to count: attendances per course per term, distinct students per teacher, room utilisation by day of week. ## Flavour one: event tracking The first and more common flavour records that an event happened when the event has no natural quantity. Examples: - a student attended a class session - a customer viewed a product page or opened an email - an employee completed a mandatory training module - a patient was screened for a condition - an insurance policy was quoted (but not necessarily bound) All the analysis is counting: `COUNT(*)` for occurrences, `COUNT(DISTINCT student_key)` for reach, ratios of counts for participation rates. Watch that those distinct counts and ratios are themselves non-additive — you can sum attendance counts across days, but distinct-student counts have to be recomputed from the atomic rows for every grouping, because the same student appears on many days. A design temptation to resist is bolting a measure on just to feel comfortable: adding a fabricated `attendance_flag = 1` is harmless as a convenience column, but inventing `duration_minutes` when the source does not record it puts fiction in the mart. ## Flavour two: coverage or condition The second flavour records a *state of eligibility or applicability* over an interval, rather than an event: - which products were on promotion in which stores during which weeks - which customers were eligible for which offers - which salespeople covered which territories in which month - which parts are valid for which product models Nothing happened here; a condition simply held. These tables are usually dense — a promotion covering 800 products across 300 stores for a week generates 240,000 rows — and are loaded from the promotion, assignment or eligibility source rather than from a transaction stream. ## Why coverage tables exist: the negative question The reason a coverage table earns its storage is that a sales fact table can only tell you what *did* happen. "Which promoted products sold nothing last week?" cannot be answered by a fact table of sales, because the absent rows are precisely the answer. With a coverage table, it becomes a set difference: ```sql SELECT c.store_key, c.product_key FROM fct_promotion_coverage c WHERE c.week_key = 202612 AND NOT EXISTS ( SELECT 1 FROM fct_sales s WHERE s.week_key = c.week_key AND s.store_key = c.store_key AND s.product_key = c.product_key); ``` The same pattern answers "which eligible members never enrolled", "which assigned territories produced no calls", "which trained employees never used the system". Any time the business question contains the word *not*, look for a coverage table. ## Relationship to the other fact types Factless is orthogonal to the transaction/periodic/accumulating split rather than a fourth position on the same axis. An event-tracking factless table is a transaction fact table that happens to have no measures; a coverage table is closest to a periodic snapshot of a condition. Interviewers usually list all four together, so name it as a type but be ready to explain that its distinguishing feature is the absence of measures, not the timing of the rows. ## The optional counter column Many implementations add a column holding the constant 1 — `attendance_count`, `event_count` — so that reporting tools which insist on aggregating a measure have something to point at, and so SUM and COUNT give the same answer. It stores a byte per row of pure redundancy in exchange for tool convenience; either choice is defensible, but do not present the constant column as what makes it a fact table. ## Common mistakes The most common error is inventing a measure so the table "looks normal", which puts numbers in the warehouse that no source ever produced. The second is failing to spot that a question is a negative one and trying to answer it from the transaction table alone — you cannot count rows that were never written. The third is forgetting the density of coverage tables: every product-store-week combination that a promotion touches becomes a row, so a broad promotion can dwarf the sales fact table it supports.

  • Why can't a sales fact table answer which promoted products sold nothing?
    Because a sales fact table only contains rows for products that sold; the products that sold nothing are represented by absent rows, and you cannot filter on rows that do not exist. A promotion coverage table supplies the universe of product-store-week combinations that were promoted, and the answer is that universe minus the combinations present in sales.
  • Should you add a constant column holding the value 1 to a factless fact table?
    It is optional. Some reporting tools want a measure to aggregate, and a constant one makes SUM and COUNT agree, which avoids user error. The cost is a redundant column on a table that is often very large. Either way it is not what makes the table a fact table — the dimension keys and the row's existence are.

saying these in an interview costs you the question

  • Invents a measure column so the table looks like a normal fact table
  • Says a factless table cannot answer any quantitative question
  • Tries to answer a did-not-happen question from the transaction facts
  • Assumes coverage tables are small because nothing happened
  • Confuses factless with a dimension table because it has no measures

context