Report pages: Overview · Drivers · Sellers
A review-grain fact table with three many-to-many bridges and one deliberately disconnected table, reusing dim_date, dim_customer, dim_seller and dim_product from the earlier two parts without modifying any existing table.
Worth naming precisely: this is not a textbook star schema. Bridges sit between the fact and three of its dimensions, so dim_seller, dim_product and dim_customer are two hops from fact_reviews rather than one; all three bridges use bidirectional cross-filtering; and bridge_review_category_delay has no relationships at all. It is a bridge-heavy dimensional model, and each departure from the clean star was a decision with a reason.
The obvious design puts a single order_id column on the review table. It is also wrong, and quietly so. In this dataset 789 review IDs appear against more than one order, and 547 orders carry more than one review. The duplicated rows are identical on every field except order_id, so a careless DISTINCT hides the problem rather than solving it. bridge_review_order resolves it as two clean many-to-one hops. Each of the three many-to-many relationships across the series was verified with a count query before its bridge was built, not assumed from the table names.
One table is deliberately disconnected. The category-by-delivery-outcome heatmap has to cross two independent many-to-many bridges in a single filter context — product category through one, delivery delay through another. Three separate DAX approaches all returned wrong-but-plausible numbers, the standard failure mode when averaging across two unrelated many-to-many paths. Rather than keep iterating on DAX producing numbers that could not be independently reproduced, the join was pre-flattened in SQL into bridge_review_category_delay and left with no relationships in the model. The heatmap reads it directly, the result is deterministic, and it matches the validation queries exactly. It is an unglamorous solution and it breaks the clean star, which is why it is documented rather than hidden.
Every object in this project is new, and the four shared dimensions are read but never written. dim_date was checked against this project's full date range before any fact load — it already spanned September 2016 to November 2018, so unlike Part 2 no extension was needed. Verifying that up front is the point; Part 2 discovered its gap as a foreign key violation mid-load.
bridge_review_order rather than forcing a false one-to-one, verified with count queries before designing the schema.[Avg Days to Review], with the rows retained so the anomaly stays inspectable.order_value, confirmed as the known zero-item orders from Part 1 — cancelled or unavailable before fulfilment. Bucketed as "Unknown" and filtered out of the price chart rather than coerced to zero: a cancelled order is not an order worth R$0.BLANK() = FALSE evaluates to TRUE in DAX, so is_late = FALSE silently swept in 2,865 never-delivered orders. Added explicit NOT ISBLANK() guards. Without them the on-time count read 91,523 instead of 88,658 and the on-time average read 4.22 instead of 4.29 — an error that flatters the result and would have shipped invisibly.fact_reviews. Set all three to bidirectional. Caught the same way as in Part 2: every category rendering an identical value is the symptom.category_name_en exists in both dim_product and the flattened table with no relationship between them. Dragging the wrong one into a visual makes every matrix row show the identical grand total. Fixed by verifying the source table of every field, not just its name.NVARCHAR(MAX) collapsed CSV import to around ten rows per second. Bounding them to NVARCHAR(4000) dropped the same 98,000-row import from over ten minutes to 41 seconds.Unknown, n=1,457, is translation residue rather than a business category, and is excluded from category rankings. The source also contains a genuine typo category, costruction_tools_garden, distinct from garden_tools — retained as-is, since silently merging it would misstate the source.[Pct No Comment] is defined as 1 - [Comment Rate] rather than as a has_comment = FALSE test. Both return the same figure here, but the subtraction is immune by construction to the blank-comparison trap, and guarantees the pages reconcile permanently rather than by coincidence.fact_reviews and bridge_review_order relate to dim_date four times in total. Only review creation is active; the rest are invoked with USERELATIONSHIP(). Only one can be active at a time — the direct path from the bridge and the path through the fact table together form a loop.