What is a role-playing dimension, and how do you join one date dimension three times?
answer
- a fact has four different dates
- how many date tables do you build?
- one table, several foreign keys
- each join is a different role
- give each role its own view and prefix
basics
~20 sA role-playing dimension is one physical dimension joined to the same fact table several times under different meanings — order date, ship date and due date — usually exposed as one view or alias per role so column names stay unambiguous.
solid answer
~40 sA fact row often has several dates: ordered, shipped, due, returned. You do not build four date dimensions; you build one conformed `dim_date` and give the fact four foreign keys, each join being a different **role** the same table plays. In SQL you alias it per role. For consumers you usually publish a thin view per role — `dim_order_date`, `dim_ship_date` — that renames columns with a role prefix (`ship_year`, `ship_month_name`), because otherwise three joined copies all offer a column called `year` and reports quietly reference the wrong one. The pattern is not date-specific: an employee dimension can play salesperson and manager, an airport dimension origin and destination. The point is one physical table, one load, one set of attribute definitions, several logical roles.
code
sql · 8 linesSELECT o.calendar_year AS order_year,
o.month_name AS order_month,
s.month_name AS ship_month,
SUM(f.extended_amount) AS revenue
FROM fact_order_line f
JOIN dim_date o ON o.date_key = f.order_date_key
JOIN dim_date s ON s.date_key = f.ship_date_key
GROUP BY o.calendar_year, o.month_name, s.month_name;go deeper
Know the term and the one-line answer: one physical date dimension joined several times through different foreign keys, one per role, rather than several copies of the calendar.
Explain the mechanics — aliases in SQL, a thin renaming view per role — and why the renaming matters: three copies of a column called year is how a report ends up on the wrong date.
Show the operational judgment: unknown-member rows so unshipped orders survive the join, a documented default time axis for the mart, and reconciliation between roles when two reports disagree.
Own the convention: naming rules for role views, which role a published metric is defined on, and how you stop teams from forking a role view into a differently-defined calendar.
## The situation Almost every interesting fact has more than one date attached. An order line was ordered on one day, promised for another, shipped on a third and paid on a fourth. A claim has an incident date, a filing date and a settlement date. If each of those got its own dimension table you would maintain four copies of the same calendar, and they would drift: someone patches a fiscal-period label in one and forgets the others, and two reports that both say "Q3" disagree. ## The pattern Build one date dimension. Put several foreign keys in the fact table, one per role, and join the same physical table once per key: ```sql CREATE TABLE fact_order_line ( order_date_key INTEGER NOT NULL, ship_date_key INTEGER NOT NULL, due_date_key INTEGER NOT NULL, product_key INTEGER NOT NULL, quantity INTEGER NOT NULL, extended_amount DECIMAL(18,2) NOT NULL ); SELECT o.calendar_year AS order_year, s.calendar_year AS ship_year, SUM(f.extended_amount) AS revenue FROM fact_order_line f JOIN dim_date o ON o.date_key = f.order_date_key JOIN dim_date s ON s.date_key = f.ship_date_key GROUP BY o.calendar_year, s.calendar_year; ``` The table is joined three times; each join is a role. Nothing is duplicated on disk, the calendar is loaded once, and a fix to a holiday flag or a fiscal-period label is instantly correct in every role. ## Why you usually publish views Raw aliases work fine in hand-written SQL but are hostile to everyone else. Three aliased copies of `dim_date` all expose `calendar_year`, `month_name`, `is_holiday`; in a report, on a dashboard or in a shared query, a plain `year` is ambiguous and an analyst can pick the wrong one without any error being raised. The standard remedy is a thin view per role with role-prefixed column names: ```sql CREATE VIEW dim_ship_date AS SELECT date_key AS ship_date_key, full_date AS ship_date, calendar_year AS ship_year, month_name AS ship_month_name, fiscal_quarter AS ship_fiscal_quarter FROM dim_date; ``` A view costs nothing to store, is defined once, and inherits every change to the base table. The naming, not the view mechanism, is the point: `ship_year` cannot be mistaken for `order_year`. The alternative — physically copying the dimension per role — is what you are being tested on rejecting. Copies must be loaded, reconciled and corrected N times, and the first time one of them lags a load the report labelled "by ship month" stops agreeing with the report labelled "by order month" for reasons no one can find. ## It is not only dates Any dimension can play roles. In a sales fact, `dim_employee` can be joined as the salesperson and again as the approving manager. In a flight fact, `dim_airport` plays origin and destination. In a transfer fact, `dim_account` plays source and target. The reasoning is identical: one physical table, one definition of "what an airport is", several foreign keys with role-specific names. ## Where it goes wrong - **The wrong role in a report.** This is the dominant real-world failure. Revenue joined on `ship_date_key` but labelled "revenue by order month" is silently wrong — the numbers are plausible, the total over all time is right, and only the monthly shape is off. Role-prefixed naming is the cheapest defence; a test that reconciles a metric across two roles is the next one. - **Nullable roles.** Not every order has shipped. A null `ship_date_key` breaks an inner join and silently drops rows, so warehouses conventionally reserve a special row in the date dimension (for example key 0, labelled "Not yet shipped" or "Unknown") and point unshipped rows at it. Then the join is total and the missing case is visible as a labelled bucket rather than as absent rows. - **Attributes that differ per role.** Occasionally a role wants an attribute the others do not — a "days late" style calculation belongs on the fact, not in a role view. Keep the role views thin renames of the shared table; the moment a role view starts adding role-specific logic, you have begun forking the dimension. - **Deciding which role is the default.** A mart usually names one role as the primary time axis for its metrics. Decide it explicitly and document it, because half of the "the numbers don't match" tickets come from two teams having assumed different defaults. ## What an interviewer is checking That you say "one table, several joins, one alias or view per role" rather than "four date tables", and that you can name the ambiguity problem the role naming solves. It is a vocabulary shibboleth: the term itself is short, but the follow-up about how unshipped rows join, or which role a metric defaults to, is where judgment shows.
- How do you handle an order that has not shipped yet, so ship_date_key is unknown?Do not leave the key null — an inner join would silently drop the row. Reserve a special row in the date dimension (commonly key 0) labelled "Not yet shipped" or "Unknown" and point those facts at it. The join stays total and the unshipped population shows up as a labelled bucket you can count.
- Why not physically copy the date dimension once per role?Copies must each be loaded, corrected and reconciled, and they drift: a fiscal-calendar fix applied to one copy leaves reports on other roles disagreeing. One table joined several times has a single definition and a single load, and views or aliases give you the per-role naming without duplicating data.
- Can a dimension other than date play roles?Yes — an employee dimension joined as both salesperson and approving manager, an airport dimension as origin and destination, an account dimension as source and target of a transfer. The pattern is about one physical table serving several foreign keys, and dates are simply the most common case.
One actor, several costumes: the same date dimension walks on stage as the order date, then as the ship date, and the audience must be told which part it is playing.
saying these in an interview costs you the question
- Builds a separate date dimension per date column
- Leaves role columns unrenamed so 'year' is ambiguous
- Leaves an unknown role key null and inner-joins it
- Thinks role playing requires copying data per role
- Claims role playing only applies to date dimensions