1. Define the grain of an App Store purchase fact table.
Design the purchase fact and supporting dimensions for App Store transactions. Specify what one fact row represents, how order, app, account token, storefront, currency, and event time are keyed, and how refunds and reversals relate to the original purchase without overwriting financial history.
I would define the grain as one immutable warehouse row per App Store transaction lifecycle event. Purchases, refunds, and refund reversals are separate rows. Each row gets a fact_event_key, keeps the Apple transaction identifiers, joins to the required dimensions, and never overwrites earlier financial history.
The table should keep a separate record whenever money related to an App Store transaction changes. A purchase creates one record. A later refund creates another record, and undoing that refund creates another. Earlier records stay unchanged, so anyone can see what happened over time and calculate the final money amount correctly. Each record also points to the related order, app, account token, selling region, currency, and time. This keeps reports consistent and prevents later changes from erasing the history of the original purchase.
- Should the warehouse capture purchases, refunds, and refund reversals as separate lifecycle events?
- Is the app account token optional, as shown in the model?
- Should financial reporting use signed amount changes so purchases and refund reversals add value while refunds subtract value?
Start by declaring the grain: one immutable row in fact_app_store_purchase represents one App Store transaction lifecycle event. The event can be a purchase, refund, or refund_reversal. Grain means exactly what one fact-table row represents.
Use fact_event_key as the warehouse primary key. It is a BIGINT surrogate key, meaning it is generated for the warehouse and uniquely identifies each stored lifecycle event. Keep transaction_id as the Apple transaction business identifier. Keep original_transaction_id separately because it preserves the Apple transaction chain; it is not the warehouse self-reference used to connect a refund or refund reversal to the affected purchase row.
Key the supporting dimensions at the same fact grain. order_id is a foreign key to dim_order.order_id. app_id is a foreign key to dim_app.app_id. account_token_key is a nullable foreign key to dim_account_token.account_token_key, where app_account_token is the optional UUID attribute. storefront_id is a foreign key to dim_storefront.storefront_id. currency_code is a foreign key to dim_currency.currency_code. Storefront and currency are keyed independently and should not be inferred from one another. event_time_key is a foreign key to dim_event_time.event_time_key, where the dimension carries event_timestamp, event_date, year, quarter, month, day, and day_of_week.
The fact also stores event_type, quantity, and amount_delta. amount_delta is a signed warehouse amount in the transaction currency. A purchase contributes a positive amount, a refund contributes a negative amount, and a refund reversal contributes the restoring positive amount. This lets downstream calculations sum lifecycle rows without modifying earlier financial records.
For refund history, use related_purchase_event_key as a nullable self-referencing foreign key to fact_app_store_purchase.fact_event_key. A purchase normally has NULL in this field. A refund or refund-reversal warehouse row points to the fact_event_key of the affected purchase. Multiple lifecycle rows may therefore reference one purchase row. This relationship is separate from original_transaction_id: related_purchase_event_key is the warehouse relationship between lifecycle rows, while original_transaction_id preserves the Apple transaction-chain identifier.
For example, suppose fact_event_key 1001 is a purchase for transaction_id 2000001234567890 with amount_delta 9.99. A later refund is fact_event_key 1002 with the same transaction_id, related_purchase_event_key 1001, and amount_delta -9.99. If that refund is reversed, fact_event_key 1003 again uses the same transaction_id, points to purchase 1001, and carries amount_delta 9.99. Nothing overwrites row 1001.
The main tradeoff is that this event-style fact stores more rows than a model that keeps only the latest state. The benefit is stronger auditability: every financial change remains visible, and net financial results can be recomputed from immutable history.
- Declare the business process as App Store transaction lifecycle activity.
- Set the fact grain to one immutable warehouse row per purchase, refund, or refund-reversal event.
- Generate fact_event_key as the warehouse primary key while retaining transaction_id and original_transaction_id as Apple business identifiers.
- Resolve order_id, app_id, nullable account_token_key, storefront_id, currency_code, and event_time_key to their supporting dimensions.
- Store event_type, quantity, and signed amount_delta at that same event grain.
- For a refund or refund reversal, append a new fact row and set related_purchase_event_key to the affected purchase fact_event_key.
- Never update the original purchase row to represent a later financial event.
Storage grows with the number of lifecycle events because refunds and reversals add rows instead of replacing earlier rows. Each new event needs dimension-key resolution and one new fact row. Reporting stays simple because signed amount_delta values can be summed, but consumers must understand the event grain. Maintenance requires consistent source identifiers, foreign keys, and related_purchase_event_key relationships. The benefit is complete financial history and straightforward auditing.
This tests whether the candidate can declare a precise fact-table grain before designing dimensions and measures, separate warehouse keys from source-system business identifiers, model dimension relationships correctly, and preserve an auditable financial history when purchases later receive refunds or refund reversals.
Common mistakes are defining the grain as one row per order or one row per transaction regardless of later lifecycle events; overwriting the purchase when a refund arrives; treating transaction_id as the warehouse event primary key; using original_transaction_id as though it were the warehouse refund-to-purchase foreign key; making the optional app account token mandatory; joining a raw timestamp directly to a date-only key instead of using event_time_key; inferring storefront from currency; and representing refunds without a negative financial effect. Another mistake is placing descriptive dimension attributes in the fact table instead of using the corresponding dimensions.
State the grain first in one sentence. Then name the warehouse key, the Apple business identifiers, the six required dimension relationships, and the append-only refund relationship. Finish with the three-row purchase, refund, and refund-reversal example to show why financial history is never overwritten.









