Skip to content

refactor/dbt-clickhouse - #164

Open
iobruno wants to merge 8 commits into
masterfrom
refactor/dbt-clickhouse
Open

iobruno wants to merge 8 commits into
masterfrom
refactor/dbt-clickhouse

Conversation

@iobruno

@iobruno iobruno commented Feb 10, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Updates staging models (stg_green_, stg_yellow_, stg_fhv_)
    • Replaces deduplication CTE on stg_yellow/stg_green for QUALIFY on main SELECT stmt
    • Adds deduplication step (with QUALIFY) on stg_fhv for consistency
  • Updates dimension models (dim_fhv_trips, dim_taxi_trips)
    • extracted year, quarter, and month fields from pickup/dropoff timestamps
      to support downstream aggregations; shortened join aliases for readability
  • Adds core models:
    • fct_fhv_monthly_zone_traveltime_p90: computes the P90 travel time (in seconds)
      per pickup/dropoff zone pair per month for FHV trips, ranked by longest travel time
    • fct_taxi_trips_monthly_fare_p95: calculates P97, P95, and P90 fare percentiles
      by service type per month, filtering for valid cash/credit card trips
    • fct_taxi_trips_quarterly_revenue: aggregates quarterly revenue and trip counts
      by service type with year-over-year growth percentage
  • Fix indentation issues across all models
  • Adds exhaustive clause (else) on resolve_payment_type_desc macro
  • Updates Dockerfile to use astral-sh/uv:python3.13-trixie-slim as base image for builder stage and python3.13-slim as base image for the runner stage
  • Updates README
  • Updates Docker Images on compose.yml
    • Bumps ClickHouse to 26.1-jammy
    • Bumps Postgres to 18.1-alpine

Test plan

  • Run dbt build to validate all models compile and execute against BigQuery
  • Verify the new fact models produce expected output for known date ranges
  • Confirm dim_fhv_trips and dim_taxi_trips still pass existing tests after the schema changes

🤖 Generated with Claude Code

* Update project dependencies
* Bump dbt_utils to 1.3.3
* Fix materialization config on dbt_project.yml to preent warnings
* `fct_fhv_monthly_zone_traveltime_p90`: computes the P90 travel time (in seconds)
   per pickup/dropoff zone pair per month for FHV trips, ranked by longest travel time

* `fct_taxi_trips_monthly_fare_p95`: calculates P97, P95, and P90 fare percentiles
   by service type per month, filtering for valid cash/credit card trips

* `fct_taxi_trips_quarterly_revenue`: aggregates quarterly revenue and trip counts
   by service type with year-over-year growth percentage
@iobruno
iobruno force-pushed the refactor/dbt-clickhouse branch from 6422b8b to 673cdd6 Compare February 10, 2026 03:22
* Adds exhaustive clause (else) on resolve_payment_type_desc
* Minor update on var name
@iobruno
iobruno force-pushed the master branch 2 times, most recently from 8adbdca to 153db80 Compare February 14, 2026 03:36
@iobruno

iobruno commented Feb 16, 2026

Copy link
Copy Markdown
Owner Author

@claude code review it

@claude

claude Bot commented Feb 16, 2026 •

Copy link
Copy Markdown

Claude finished @iobruno's task in 2m 45s —— View job


Code Review Complete

Tasks

  • Read repository guidelines (CLAUDE.md)
  • Review staging model changes (stg_fhv, stg_green, stg_yellow)
  • Review dimension model changes (dim_fhv_trips, dim_taxi_trips)
  • Review new fact models (3 files)
  • Review macro changes
  • Review Docker and infrastructure updates
  • Review documentation updates
  • Compile and post comprehensive review feedback

Review Feedback

This refactor significantly improves the dbt-clickhouse project with cleaner SQL patterns, better consistency, and useful new analytical models. Here's my detailed review:

✅ Strengths

1. Excellent Use of QUALIFY for Deduplication

The refactor from CTE-based deduplication to QUALIFY is a great improvement:

  • stg_green_tripdata.sql:40-41 and stg_yellow_tripdata.sql:40-41: Using QUALIFY row_number() over(partition by ...) = 1 is more efficient and readable than the previous CTE approach
  • stg_fhv_tripdata.sql:25-26: Good consistency by adding QUALIFY here as well, ensuring all staging models follow the same pattern

2. Smart Schema Denormalization in Dimension Models

Adding temporal fields to dimension models is excellent for downstream analytics:

  • dim_fhv_trips.sql:28-35: Extracting year, quarter, month from both pickup and dropoff timestamps enables efficient time-based aggregations without repeated function calls
  • dim_taxi_trips.sql:27-37: Same pattern applied consistently

3. Well-Designed Fact Models

The three new fact models demonstrate good analytical modeling:

fct_fhv_monthly_zone_traveltime_p90.sql:

  • Uses window functions effectively for percentile calculations
  • The ranking by longest travel time provides actionable insights
  • Good use of CTEs for readability

fct_taxi_trips_monthly_fare_p95.sql:

  • Calculating multiple percentiles (P90, P95, P97) in one pass is efficient
  • Filters for valid payment types (cash, credit card) ensure data quality
  • Using any_value() with group by is appropriate since percentiles are constant within groups

fct_taxi_trips_quarterly_revenue.sql:

  • Year-over-year growth calculation using LAG() window function is elegant
  • Using safe_divide() prevents division by zero errors
  • The partition by service_type, quarter and order by year correctly aligns comparable periods

4. Macro Improvement

  • macros/resolve_payment_type_desc.sql:15: Adding the else 'Unknown' clause makes the macro exhaustive and prevents null handling issues

5. Docker Modernization

  • Dockerfile: Using astral-sh/uv:python3.13-trixie-slim as builder and python:3.13-slim as runner follows best practices for multi-stage builds
  • Byte-compilation with UV_COMPILE_BYTECODE=1 improves runtime performance
  • compose.yaml: Bumping ClickHouse to 26.1-jammy and Postgres to 18.1-alpine keeps dependencies current

⚠️ Issues & Concerns

1. Critical: Incorrect Window Function Usage in fct_fhv_monthly_zone_traveltime_p90.sql

Location: fct_fhv_monthly_zone_traveltime_p90.sql:11-12

percentile_cont(timestamp_diff(dropoff_datetime, pickup_datetime, SECOND), 0.90)
    over (partition by pickup_year, pickup_month, pickup_zone, dropoff_zone) as timedelta_p90

Problem: This calculates the P90 percentile for every row in each partition, which:

  • Creates redundant calculations (same percentile repeated for all rows in partition)
  • Then you use max(timedelta_p90) in line 24, which is confusing since all values should be identical
  • The count(1) on line 23 counts rows in the first CTE, not actual trips

Recommended Fix: Move the percentile calculation into the aggregation query:

with fhv_timedelta as (
    select
        pickup_year         as year,
        pickup_month        as month,
        pickup_zone         as pickup_zone,
        dropoff_zone        as dropoff_zone,
        timestamp_diff(dropoff_datetime, pickup_datetime, SECOND) as trip_duration_seconds
    from
        {{ ref('dim_fhv_trips') }}
),

fhv_rnk_timedelta as (
    select
        year                                        as year,
        month                                       as month,
        pickup_zone                                 as pickup_zone,
        dropoff_zone                                as dropoff_zone,
        count(1)                                    as num_trips,
        percentile_cont(trip_duration_seconds, 0.90) as timedelta_p90,
        dense_rank() over (partition by year, month, pickup_zone 
                          order by percentile_cont(trip_duration_seconds, 0.90) desc) as rnk
    from
        fhv_timedelta
    group by
        year,
        month,
        pickup_zone,
        dropoff_zone
)

Note: I'm uncertain if ClickHouse allows percentile_cont() in both SELECT and window ORDER BY clauses simultaneously. You may need to use a nested CTE to calculate percentiles first, then rank them in a second step.

Fix this →

2. Potential Data Quality Issue: Missing Validation Filters

Locations:

  • fct_fhv_monthly_zone_traveltime_p90.sql:14 (dim_fhv_trips source)
  • fct_taxi_trips_quarterly_revenue.sql:13 (dim_taxi_trips source)

Problem: These fact models don't filter for data quality issues:

  • Negative trip durations (dropoff before pickup)
  • Zero or negative revenue amounts
  • Extremely long durations that might be data errors

Recommendation: Add WHERE clauses to filter invalid data:

-- For fct_fhv_monthly_zone_traveltime_p90.sql
from
    {{ ref('dim_fhv_trips') }}
where
    dropoff_datetime > pickup_datetime  -- Valid trip duration
    and timestamp_diff(dropoff_datetime, pickup_datetime, SECOND) < 86400  -- Less than 24 hours

-- For fct_taxi_trips_quarterly_revenue.sql
from
    {{ ref('dim_taxi_trips') }}
where
    total_amount > 0  -- Valid revenue

3. Inconsistency: Missing Data Quality Filters in One Model

Location: fct_taxi_trips_monthly_fare_p95.sql:16-19

Observation: This model filters for:

where
    fare_amount > 0
    and trip_distance > 0
    and lower(payment_type_description) in ('cash', 'credit card')

This is good! However, the other fact model (fct_taxi_trips_quarterly_revenue.sql) doesn't have similar filters. For consistency, consider whether quarterly revenue should also exclude invalid trips.

4. Minor: ClickHouse Function Compatibility

Location: fct_taxi_trips_quarterly_revenue.sql:38

round(safe_divide(revenue - prev_year_revenue, prev_year_revenue) * 100, 2) as growth

Concern: I'm not certain if safe_divide() is a native ClickHouse function. You may need to verify this works, or use:

round(if(prev_year_revenue > 0, (revenue - prev_year_revenue) / prev_year_revenue * 100, NULL), 2) as growth

Or ClickHouse's divide() function if available.

5. Documentation: Outdated Comments

Location: stg_fhv_tripdata.sql:28-30

-- Run as:
--  dbt build --select stg_fhv_tripdata --vars 'is_test_run: true'
--  dbt run --select stg_fhv_tripdata --vars 'is_test_run: false'

The second command is redundant since is_test_run defaults to false. Consider simplifying to:

-- Run as:
--  dbt build --select stg_fhv_tripdata
--  dbt build --select stg_fhv_tripdata --vars 'is_test_run: true'  # For testing

Same applies to stg_green_tripdata.sql:43-45 and stg_yellow_tripdata.sql:43-45.

6. README: Inconsistent Environment Variable Names

Location: README.md:41

export DBT_CLICKHOUSE_FQDN_NYC_TAXI=fqdb_nyc_taxi

vs. README.md:97

-e DBT_CLICKHOUSE_FQDN_NYC_TAXI=fqdb_nyc_taxi

vs. Dockerfile:38 (missing this env var entirely)

Problem: The Dockerfile doesn't declare this environment variable, but the README shows it's needed. Consider adding it to the Dockerfile's ENV declarations or clarifying which variables are optional.

Also, the README shows DBT_CLICKHOUSE_FQDN_NYC_TAXI but the Dockerfile uses DBT_CLICKHOUSE_FQ_PGDATA_SCHEMA (line 38) - these seem related but have different names. Please verify consistency.

📝 Minor Suggestions

  1. Consider Adding Tests: Since you're adding new fact models, consider adding dbt tests to validate:

    • Non-null checks on key fields
    • Referential integrity (e.g., relationships tests)
    • Accepted values for categorical fields
    • Data quality checks (e.g., percentiles should be >= 0)
  2. Performance: The new temporal fields in dimension models are great. If these tables grow large, consider adding indexes on the new year, quarter, month columns for even better query performance.

  3. Naming Consistency: You have fct_fhv_monthly_zone_traveltime_p90 and fct_taxi_trips_monthly_fare_p95. Consider using a consistent prefix pattern like:

    • fct_fhv_* vs fct_taxi_* (current - good!)
    • But within taxi, you have fct_taxi_trips_monthly_* and fct_taxi_monthly_zone_* - consider standardizing

🎯 Summary

Approve with suggested fixes:

  • ✅ The refactoring improves code quality and follows ClickHouse best practices
  • ✅ New fact models provide valuable analytical insights
  • ⚠️ Critical: Fix the window function usage in fct_fhv_monthly_zone_traveltime_p90.sql (issue Migrate 'additional calculations' to dim_monthly_zone_statistics #1)
  • ⚠️ Consider adding data quality filters to remaining fact models
  • ⚠️ Verify ClickHouse function compatibility (safe_divide())
  • 📝 Clean up documentation inconsistencies

Overall, this is a solid refactor that moves the project in the right direction. The main issue to address is the percentile calculation pattern in the FHV model.


@iobruno

iobruno commented Aug 10, 2026

Copy link
Copy Markdown
Owner Author

@claude apply all suggestions made above

@claude

claude Bot commented Aug 10, 2026 •

Copy link
Copy Markdown

Claude finished @iobruno's task in 3m 9s —— View job


Applied suggestions from the review

  • Read repo files (models, macros, README, Dockerfile)
  • Fix window function usage in fct_fhv_monthly_zone_traveltime_p90.sql
  • Add data quality filters to fct_fhv_monthly_zone_traveltime_p90.sql
  • Add data quality filters to fct_taxi_trips_quarterly_revenue.sql
  • Verify safe_divide() ClickHouse compatibility, fix if needed
  • Clean up outdated dbt run/dbt build comments in staging models
  • Resolve README/Dockerfile env var inconsistency (DBT_CLICKHOUSE_FQDN_NYC_TAXI)
  • Commit and push changes

Summary of changes (commit 4658a3e)

  1. fct_fhv_monthly_zone_traveltime_p90.sql — moved percentile_cont() from a per-row window function into the group by aggregation (matching the reviewer's suggested restructure), and moved dense_rank() into the final select over the already-aggregated result, avoiding any ambiguity around a percentile function inside a window order by. Also added where dropoff_datetime > pickup_datetime and timestamp_diff(...) < 86400 to filter out invalid/outlier trip durations.
  2. fct_taxi_trips_quarterly_revenue.sql — added where total_amount > 0 for consistency with the filtering already present in fct_taxi_trips_monthly_fare_p95.sql, and replaced the bare safe_divide(...) call (not a native ClickHouse function) with {{ dbt_utils.safe_divide(...) }}, which is cross-database safe and already available via the dbt_utils package in packages.yml.
  3. stg_fhv_tripdata.sql, stg_green_tripdata.sql, stg_yellow_tripdata.sql — simplified the redundant dbt run --vars 'is_test_run: false' comment line (the default is already false) to a single "for testing" variant.
  4. Dockerfile — replaced the unused DBT_CLICKHOUSE_FQ_PGDATA_SCHEMA env var declaration with DBT_CLICKHOUSE_FQDN_NYC_TAXI, which is the variable actually referenced in models/staging/sources.yml and documented in the README — FQ_PGDATA_SCHEMA was not referenced anywhere else in the codebase.

Not applied (advisory/out of scope for this change)

  • New dbt tests (non-null, accepted-values, relationships) — the project currently has no .yml schema/test files for any model, staging or core, so adding tests only for the 3 new fact models would introduce an inconsistent, one-off pattern. This is better addressed as its own follow-up covering all models.
  • Indexing suggestion on year/month/quarter columns — ClickHouse table engine/ordering-key choices aren't currently configured for any model in this project (all default table engine), so this would also be a separate, broader change.
  • Fact model naming standardization (fct_taxi_trips_monthly_* vs fct_taxi_monthly_zone_*) — this affects an existing merged model (fct_taxi_monthly_zone_revenue) as well as the new ones; renaming is a breaking change for anything downstream and seemed best left for a deliberate decision rather than folded into this fix-up.

Caveats

I don't have a running ClickHouse instance or dbt/uv installed in this environment, so I could not run dbt compile/dbt build to validate these changes execute correctly. The percentile/rank restructure and dbt_utils.safe_divide usage are logically sound and follow the reviewer's suggestions, but should still be validated with dbt build before merging, per the PR's own test plan.
·

- Rework fct_fhv_monthly_zone_traveltime_p90 to compute percentile_cont
  as a group-by aggregate instead of a redundant per-row window function,
  and rank in the outer select
- Add data quality filters (valid trip duration, positive revenue) to
  fct_fhv_monthly_zone_traveltime_p90 and fct_taxi_trips_quarterly_revenue
- Replace non-portable safe_divide() call with dbt_utils.safe_divide macro
- Simplify redundant is_test_run run instructions in staging model comments
- Fix Dockerfile env var to match DBT_CLICKHOUSE_FQDN_NYC_TAXI used in
  sources.yml instead of the unused DBT_CLICKHOUSE_FQ_PGDATA_SCHEMA

Co-authored-by: Bruno Oliveira <901651+iobruno@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant