.duckdb file plus a companion dbt manifest.
Compared to the ecommerce guide, this project exercises a different schema shape: a geography dimension chain (station → municipality → province) resolved by spatial joins at build time, a dimension with a synthetic unknown fallback row, and a fact table pinned to a single demo day.
Full source:
examples/dutch-railway/Data
The fact table is pinned to a single demo day (service_date = 2024-08-01): 63,946 service stops across 397 stations.
| Table | Rows | Grain |
|---|---|---|
dim_nl_provinces | 13 (12 + unknown) | province_sk |
dim_nl_municipalities | 343 (342 + unknown) | municipality_sk |
dim_nl_train_stations | 397 | station_sk |
fact_services | 63,946 | service_sk |
Quickstart
Metrics
| Metric | Kind | Definition |
|---|---|---|
service_count | single | Realized service stops (guardrailed; excludes cancelled arrivals) |
scheduled_stops | single | Same expression as service_count, but ungated: the unguarded denominator for the rate metrics |
cancelled_arrivals / cancelled_departures | single | Sibling metrics, unaffected by the guardrail |
cancelled_arrival_rate / cancelled_departure_rate | ratio | cancelled_* / scheduled_stops |
Why scheduled_stops exists as a separate metric
exclude-cancelled-arrivals is a mandatory_filter guardrail matched by measure name, scoped to fact_services.service_count. scheduled_stops has the same underlying expression (count(service_sk)) under a different measure name, so the guardrail doesn’t touch it; deliberately. cancelled_arrival_rate needs an unguarded total in its denominator; reusing the guarded service_count there would silently exclude cancellations from both sides of the ratio and always yield the same value regardless of cancellations.
Example queries
Three-hop join (fact_services → dim_nl_train_stations → dim_nl_municipalities → dim_nl_provinces):
ratio-kind metric; ratio/weighted_avg metrics compile to a CTE per component and must be queried alone (no other metrics or dimensions in the same call):
The dbt connector: a definition connector with no database
Wired intocanonic.yaml with no credentials at all:
RelationSchema + model evidence; descriptions, grain, FK-derived joins; rather than measure/dimension evidence. The real measures live in the hand-authored semantics/railway_duckdb/*.yaml. Modeling-tier evidence from dbt still outranks live DuckDB introspection wherever the two overlap.
What got adapted from upstream
Two build-time fixups make the upstream project (which relies on DuckDB’sspatial extension and Postgres cross-database attach) work as a pure, offline canonic example:
- Geometry → WKT text: canonic has no geometry type, and the joins already live in surrogate keys, so polygon/point geometry becomes plain WKT text at build time; a centroid point for the two polygon dimensions, the exact point for stations.
- dbt manifest relation names: dbt-duckdb’s compiled manifest tags every model with a
database: <catalog>field, but canonic’s DuckDB connector introspects relations without a catalog prefix. The build nullsdatabaseon every model node so the two tiers reconcile correctly.
scripts/build.sh for the full regeneration recipe (requires network access and uv).