Skip to content
Labs · 05Analytics engineering

Raw data lies. This warehouse checks.

A million real rows from a UK online retailer, 2009–2011. Sum them as they come and your best-selling “product” is a manual adjustment ledger, with the postman in third place. This lab builds the thing that stands between that spreadsheet and anyone making a decision: a dimensional warehouse (dbt + DuckDB) where every known trap has a test that catches it, and revenue reconciles across every layer to the penny — £18,909,762.12, four times over.

Top products by revenue · same data, two answers
01
Manual← manual adjustment, not a product£340,004
02
REGENCY CAKESTAND 3 TIER£331,084
03
DOTCOM POSTAGE← postage, not a product£309,854
04
WHITE HANGING HEART T-LIGHT HOLDER£261,510
05
JUMBO BAG RED RETROSPOT£183,000
06
PAPER CRAFT , LITTLE BIRDIE£168,470
07
PARTY BUNTING£148,396
08
ASSORTED COLOUR BIRD ORNAMENT£129,681
09
POSTAGE← postage, not a product£125,682
10
PAPER CHAIN KIT 50'S CHRISTMAS£117,884

Concatenate the spreadsheet and sum: a manual adjustment ledger is your best seller and the postman is #3.

Same file, same metric, two answers. Every number above is real and regenerated by the pipeline; the flagged rows are stock codes that were never products — postage, fees, adjustments — evicted by a 61-row seed table with a test that proves each one exists in the data.

£18,909,762.12

net revenue reconciled to the penny across all four layers — raw parquet, staging, intermediate, star schema. One test compares the four sums; a single cent of drift aborts the build

91

tests that run on every build and fail loudly. Every tolerated exception — 3,393 warehouse adjustments, five negative bad-debt prices, one cancellation with positive quantity — is pinned by name and counted, never waved through

22,523

rows duplicated between the file's two sheets — the same nine days of December 2010, present twice. Concatenate naively and revenue inflates ~2%. The warehouse removes exactly these, and a test counts them

The trap catalogueevery line has a test
1,088 invoices duplicated across sheetsdedup + pinned count
22.77% of rows have no customer at allunknown member (−1)
42,638 invoice+product pairs repeat — the "obvious" key isn't onesurrogate line key
3,393 negative quantities that are not cancellations ("damaged", "thrown away")flagged, £0.00 impact
61 stock codes that aren't products — postage, fees, a test row in productionseed + relationship test
a 80,995-unit order bought and cancelled the same daynetted to zero
The workdbt + DuckDB · reproducible
01
Verified download

The official UCI zip, with SHA-256 pinned for both the archive and the spreadsheet inside. A byte of drift aborts. One command rebuilds everything from the source.

1,067,371 rows · 29 invariants
02
Staging

Trim, normalise product codes (85123a → 85123A), money to DECIMAL, and remove the 22,523 rows the overlapping sheets duplicate — without losing a single one of the 53,628 invoices.

1,044,848 rows out
03
Star schema

Facts at invoice-line grain with a surrogate key, dimensions for date, customer and product, the invoice kept as a degenerate dimension. Every foreign key tested for orphans.

grain: one line, declared
04
History (SCD2)

Product names change over time — a whole catalogue renamed SPOTTY → POLKADOT in autumn 2010. The dimension versions every name with validity ranges; 83 renames captured, no gaps, no overlaps.

5,586 versions · 415 products
05
BI export

KPI marts exported as static CSV and loaded into Qlik Sense Cloud. The dashboard's numbers are checked against the warehouse to the penny — and documented as a dbt exposure in the lineage.

verified to the penny
From model to dashboardnothing live, nothing to break
Qlik Sense dashboard: net revenue £18,909,762.12, return rate 7.44%, customer concentration and the raw-vs-clean top products

The Qlik Sense Cloud dashboard, fed only by exported CSV files. Its four headline numbers were verified against the warehouse to the penny before this screenshot was taken.

The full lineage, navigable

Every model, test, seed and the dashboard itself documented in dbt’s auto-generated docs — one static HTML file, from raw parquet source to the Qlik exposure. Column by column, with the reasoning in the descriptions.

Open the dbt docs →
What this is — and isn’t

This is one company’s data — a UK gift-ware retailer with a wholesale bias, 2009–2011 — so the KPIs describe that business, not e-commerce in general. The source captured no history, so the product dimension’s versions are derived from the events themselves; a second implementation simulates 25 monthly loads through dbt’s snapshot mechanism and converges on the same versions, which is the point of showing both.

Honest fine print: DuckDB here is a single-node warehouse — the scale conversation is theoretical, though the dbt project ports to a cloud warehouse without changes. The dataset carries no costs, so there are no margins. And the free Qlik tier expires; the screenshot above is the permanent artifact, which is why the page depends on nothing live.

Code on GitHub·Labs · 04·Labs · 01