[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6F1C0RT3J5JC4HD47DD57WK":3,"skill-same:01M6F1C0RT3J5JC4HD47DD57WK":83},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":28,"publishedAt":49,"linkOnly":50,"markdownContent":51,"markdownBody":52,"frontMatter":53,"refName":54,"commitSha":55,"skillDirectory":56,"forksCount":57,"repositoryHtmlUrl":58,"repositoryHomepageUrl":59,"ownerHtmlUrl":60,"ownerBlogUrl":6,"ownerDescription":61,"updatedAt":62,"install":63,"resources":76},"01M6F1C0RT3J5JC4HD47DD57WK","dbt-transformation-patterns",null,"Master dbt (data build tool) for analytics engineering with model organization, testing, documentation, and incremental strategies. Use when building data transformations, creating data models, or implementing analytics engineering best practices.","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",67,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fwshobson\u002Fdbt-transformation-patterns\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000033","数据分析师","PROFESSION","适用职业",[29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48],"dbt","transformation","patterns","dbt transformation patterns","models","with","with model","strategies","organization","analytics engineering","build","testing","master","use","model","tool","data","best practices","documentation","analytics","2026-11-10 10:12:30",false,"---\nname: dbt-transformation-patterns\ndescription: Master dbt (data build tool) for analytics engineering with model organization, testing, documentation, and incremental strategies. Use when building data transformations, creating data models, or implementing analytics engineering best practices.\n---\n\n# dbt Transformation Patterns\n\nProduction-ready patterns for dbt (data build tool) including model organization, testing strategies, documentation, and incremental processing.\n\n## When to Use This Skill\n\n- Building data transformation pipelines with dbt\n- Organizing models into staging, intermediate, and marts layers\n- Implementing data quality tests\n- Creating incremental models for large datasets\n- Documenting data models and lineage\n- Setting up dbt project structure\n\n## Core Concepts\n\n### 1. Model Layers (Medallion Architecture)\n\n```\nsources\u002F          Raw data definitions\n    ↓\nstaging\u002F          1:1 with source, light cleaning\n    ↓\nintermediate\u002F     Business logic, joins, aggregations\n    ↓\nmarts\u002F            Final analytics tables\n```\n\n### 2. Naming Conventions\n\n| Layer | Prefix | Example |\n|-------|--------|---------|\n| Staging | `stg_` | `stg_stripe__payments` |\n| Intermediate | `int_` | `int_payments_pivoted` |\n| Marts | `dim_`, `fct_` | `dim_customers`, `fct_orders` |\n\n## Quick Start\n\n```yaml\n# dbt_project.yml\nname: 'analytics'\nversion: '1.0.0'\nprofile: 'analytics'\n\nmodel-paths: [\"models\"]\nanalysis-paths: [\"analyses\"]\ntest-paths: [\"tests\"]\nseed-paths: [\"seeds\"]\nmacro-paths: [\"macros\"]\n\nvars:\n  start_date: '2020-01-01'\n\nmodels:\n  analytics:\n    staging:\n      +materialized: view\n      +schema: staging\n    intermediate:\n      +materialized: ephemeral\n    marts:\n      +materialized: table\n      +schema: analytics\n```\n\n```\n# Project structure\nmodels\u002F\n├── staging\u002F\n│   ├── stripe\u002F\n│   │   ├── _stripe__sources.yml\n│   │   ├── _stripe__models.yml\n│   │   ├── stg_stripe__customers.sql\n│   │   └── stg_stripe__payments.sql\n│   └── shopify\u002F\n│       ├── _shopify__sources.yml\n│       └── stg_shopify__orders.sql\n├── intermediate\u002F\n│   └── finance\u002F\n│       └── int_payments_pivoted.sql\n└── marts\u002F\n    ├── core\u002F\n    │   ├── _core__models.yml\n    │   ├── dim_customers.sql\n    │   └── fct_orders.sql\n    └── finance\u002F\n        └── fct_revenue.sql\n```\n\n## Patterns\n\n### Pattern 1: Source Definitions\n\n```yaml\n# models\u002Fstaging\u002Fstripe\u002F_stripe__sources.yml\nversion: 2\n\nsources:\n  - name: stripe\n    description: Raw Stripe data loaded via Fivetran\n    database: raw\n    schema: stripe\n    loader: fivetran\n    loaded_at_field: _fivetran_synced\n    freshness:\n      warn_after: {count: 12, period: hour}\n      error_after: {count: 24, period: hour}\n    tables:\n      - name: customers\n        description: Stripe customer records\n        columns:\n          - name: id\n            description: Primary key\n            tests:\n              - unique\n              - not_null\n          - name: email\n            description: Customer email\n          - name: created\n            description: Account creation timestamp\n\n      - name: payments\n        description: Stripe payment transactions\n        columns:\n          - name: id\n            tests:\n              - unique\n              - not_null\n          - name: customer_id\n            tests:\n              - not_null\n              - relationships:\n                  to: source('stripe', 'customers')\n                  field: id\n```\n\n### Pattern 2: Staging Models\n\n```sql\n-- models\u002Fstaging\u002Fstripe\u002Fstg_stripe__customers.sql\nwith source as (\n    select * from {{ source('stripe', 'customers') }}\n),\n\nrenamed as (\n    select\n        -- ids\n        id as customer_id,\n\n        -- strings\n        lower(email) as email,\n        name as customer_name,\n\n        -- timestamps\n        created as created_at,\n\n        -- metadata\n        _fivetran_synced as _loaded_at\n\n    from source\n)\n\nselect * from renamed\n```\n\n```sql\n-- models\u002Fstaging\u002Fstripe\u002Fstg_stripe__payments.sql\n{{\n    config(\n        materialized='incremental',\n        unique_key='payment_id',\n        on_schema_change='append_new_columns'\n    )\n}}\n\nwith source as (\n    select * from {{ source('stripe', 'payments') }}\n\n    {% if is_incremental() %}\n    where _fivetran_synced > (select max(_loaded_at) from {{ this }})\n    {% endif %}\n),\n\nrenamed as (\n    select\n        -- ids\n        id as payment_id,\n        customer_id,\n        invoice_id,\n\n        -- amounts (convert cents to dollars)\n        amount \u002F 100.0 as amount,\n        amount_refunded \u002F 100.0 as amount_refunded,\n\n        -- status\n        status as payment_status,\n\n        -- timestamps\n        created as created_at,\n\n        -- metadata\n        _fivetran_synced as _loaded_at\n\n    from source\n)\n\nselect * from renamed\n```\n\n### Pattern 3: Intermediate Models\n\n```sql\n-- models\u002Fintermediate\u002Ffinance\u002Fint_payments_pivoted_to_customer.sql\nwith payments as (\n    select * from {{ ref('stg_stripe__payments') }}\n),\n\ncustomers as (\n    select * from {{ ref('stg_stripe__customers') }}\n),\n\npayment_summary as (\n    select\n        customer_id,\n        count(*) as total_payments,\n        count(case when payment_status = 'succeeded' then 1 end) as successful_payments,\n        sum(case when payment_status = 'succeeded' then amount else 0 end) as total_amount_paid,\n        min(created_at) as first_payment_at,\n        max(created_at) as last_payment_at\n    from payments\n    group by customer_id\n)\n\nselect\n    customers.customer_id,\n    customers.email,\n    customers.created_at as customer_created_at,\n    coalesce(payment_summary.total_payments, 0) as total_payments,\n    coalesce(payment_summary.successful_payments, 0) as successful_payments,\n    coalesce(payment_summary.total_amount_paid, 0) as lifetime_value,\n    payment_summary.first_payment_at,\n    payment_summary.last_payment_at\n\nfrom customers\nleft join payment_summary using (customer_id)\n```\n\n### Pattern 4: Mart Models (Dimensions and Facts)\n\n```sql\n-- models\u002Fmarts\u002Fcore\u002Fdim_customers.sql\n{{\n    config(\n        materialized='table',\n        unique_key='customer_id'\n    )\n}}\n\nwith customers as (\n    select * from {{ ref('int_payments_pivoted_to_customer') }}\n),\n\norders as (\n    select * from {{ ref('stg_shopify__orders') }}\n),\n\norder_summary as (\n    select\n        customer_id,\n        count(*) as total_orders,\n        sum(total_price) as total_order_value,\n        min(created_at) as first_order_at,\n        max(created_at) as last_order_at\n    from orders\n    group by customer_id\n),\n\nfinal as (\n    select\n        -- surrogate key\n        {{ dbt_utils.generate_surrogate_key(['customers.customer_id']) }} as customer_key,\n\n        -- natural key\n        customers.customer_id,\n\n        -- attributes\n        customers.email,\n        customers.customer_created_at,\n\n        -- payment metrics\n        customers.total_payments,\n        customers.successful_payments,\n        customers.lifetime_value,\n        customers.first_payment_at,\n        customers.last_payment_at,\n\n        -- order metrics\n        coalesce(order_summary.total_orders, 0) as total_orders,\n        coalesce(order_summary.total_order_value, 0) as total_order_value,\n        order_summary.first_order_at,\n        order_summary.last_order_at,\n\n        -- calculated fields\n        case\n            when customers.lifetime_value >= 1000 then 'high'\n            when customers.lifetime_value >= 100 then 'medium'\n            else 'low'\n        end as customer_tier,\n\n        -- timestamps\n        current_timestamp as _loaded_at\n\n    from customers\n    left join order_summary using (customer_id)\n)\n\nselect * from final\n```\n\n```sql\n-- models\u002Fmarts\u002Fcore\u002Ffct_orders.sql\n{{\n    config(\n        materialized='incremental',\n        unique_key='order_id',\n        incremental_strategy='merge'\n    )\n}}\n\nwith orders as (\n    select * from {{ ref('stg_shopify__orders') }}\n\n    {% if is_incremental() %}\n    where updated_at > (select max(updated_at) from {{ this }})\n    {% endif %}\n),\n\ncustomers as (\n    select * from {{ ref('dim_customers') }}\n),\n\nfinal as (\n    select\n        -- keys\n        orders.order_id,\n        customers.customer_key,\n        orders.customer_id,\n\n        -- dimensions\n        orders.order_status,\n        orders.fulfillment_status,\n        orders.payment_status,\n\n        -- measures\n        orders.subtotal,\n        orders.tax,\n        orders.shipping,\n        orders.total_price,\n        orders.total_discount,\n        orders.item_count,\n\n        -- timestamps\n        orders.created_at,\n        orders.updated_at,\n        orders.fulfilled_at,\n\n        -- metadata\n        current_timestamp as _loaded_at\n\n    from orders\n    left join customers on orders.customer_id = customers.customer_id\n)\n\nselect * from final\n```\n\n### Pattern 5: Testing and Documentation\n\n```yaml\n# models\u002Fmarts\u002Fcore\u002F_core__models.yml\nversion: 2\n\nmodels:\n  - name: dim_customers\n    description: Customer dimension with payment and order metrics\n    columns:\n      - name: customer_key\n        description: Surrogate key for the customer dimension\n        tests:\n          - unique\n          - not_null\n\n      - name: customer_id\n        description: Natural key from source system\n        tests:\n          - unique\n          - not_null\n\n      - name: email\n        description: Customer email address\n        tests:\n          - not_null\n\n      - name: customer_tier\n        description: Customer value tier based on lifetime value\n        tests:\n          - accepted_values:\n              values: ['high', 'medium', 'low']\n\n      - name: lifetime_value\n        description: Total amount paid by customer\n        tests:\n          - dbt_utils.expression_is_true:\n              expression: \">= 0\"\n\n  - name: fct_orders\n    description: Order fact table with all order transactions\n    tests:\n      - dbt_utils.recency:\n          datepart: day\n          field: created_at\n          interval: 1\n    columns:\n      - name: order_id\n        tests:\n          - unique\n          - not_null\n      - name: customer_key\n        tests:\n          - not_null\n          - relationships:\n              to: ref('dim_customers')\n              field: customer_key\n```\n\n### Pattern 6: Macros and DRY Code\n\n```sql\n-- macros\u002Fcents_to_dollars.sql\n{% macro cents_to_dollars(column_name, precision=2) %}\n    round({{ column_name }} \u002F 100.0, {{ precision }})\n{% endmacro %}\n\n-- macros\u002Fgenerate_schema_name.sql\n{% macro generate_schema_name(custom_schema_name, node) %}\n    {%- set default_schema = target.schema -%}\n    {%- if custom_schema_name is none -%}\n        {{ default_schema }}\n    {%- else -%}\n        {{ default_schema }}_{{ custom_schema_name }}\n    {%- endif -%}\n{% endmacro %}\n\n-- macros\u002Flimit_data_in_dev.sql\n{% macro limit_data_in_dev(column_name, days=3) %}\n    {% if target.name == 'dev' %}\n        where {{ column_name }} >= dateadd(day, -{{ days }}, current_date)\n    {% endif %}\n{% endmacro %}\n\n-- Usage in model\nselect * from {{ ref('stg_orders') }}\n{{ limit_data_in_dev('created_at') }}\n```\n\n### Pattern 7: Incremental Strategies\n\n```sql\n-- Delete+Insert (default for most warehouses)\n{{\n    config(\n        materialized='incremental',\n        unique_key='id',\n        incremental_strategy='delete+insert'\n    )\n}}\n\n-- Merge (best for late-arriving data)\n{{\n    config(\n        materialized='incremental',\n        unique_key='id',\n        incremental_strategy='merge',\n        merge_update_columns=['status', 'amount', 'updated_at']\n    )\n}}\n\n-- Insert Overwrite (partition-based)\n{{\n    config(\n        materialized='incremental',\n        incremental_strategy='insert_overwrite',\n        partition_by={\n            \"field\": \"created_date\",\n            \"data_type\": \"date\",\n            \"granularity\": \"day\"\n        }\n    )\n}}\n\nselect\n    *,\n    date(created_at) as created_date\nfrom {{ ref('stg_events') }}\n\n{% if is_incremental() %}\nwhere created_date >= dateadd(day, -3, current_date)\n{% endif %}\n```\n\n## dbt Commands\n\n```bash\n# Development\ndbt run                          # Run all models\ndbt run --select staging         # Run staging models only\ndbt run --select +fct_orders     # Run fct_orders and its upstream\ndbt run --select fct_orders+     # Run fct_orders and its downstream\ndbt run --full-refresh           # Rebuild incremental models\n\n# Testing\ndbt test                         # Run all tests\ndbt test --select stg_stripe     # Test specific models\ndbt build                        # Run + test in DAG order\n\n# Documentation\ndbt docs generate                # Generate docs\ndbt docs serve                   # Serve docs locally\n\n# Debugging\ndbt compile                      # Compile SQL without running\ndbt debug                        # Test connection\ndbt ls --select tag:critical     # List models by tag\n```\n\n## Best Practices\n\n### Do's\n- **Use staging layer** - Clean data once, use everywhere\n- **Test aggressively** - Not null, unique, relationships\n- **Document everything** - Column descriptions, model descriptions\n- **Use incremental** - For tables > 1M rows\n- **Version control** - dbt project in Git\n\n### Don'ts\n- **Don't skip staging** - Raw → mart is tech debt\n- **Don't hardcode dates** - Use `{{ var('start_date') }}`\n- **Don't repeat logic** - Extract to macros\n- **Don't test in prod** - Use dev target\n- **Don't ignore freshness** - Monitor source data\n\n## Resources\n\n- [dbt Documentation](https:\u002F\u002Fdocs.getdbt.com\u002F)\n- [dbt Best Practices](https:\u002F\u002Fdocs.getdbt.com\u002Fguides\u002Fbest-practices)\n- [dbt-utils Package](https:\u002F\u002Fhub.getdbt.com\u002Fdbt-labs\u002Fdbt_utils\u002Flatest\u002F)\n- [dbt Discourse](https:\u002F\u002Fdiscourse.getdbt.com\u002F)\n","# dbt Transformation Patterns\n\nProduction-ready patterns for dbt (data build tool) including model organization, testing strategies, documentation, and incremental processing.\n\n## When to Use This Skill\n\n- Building data transformation pipelines with dbt\n- Organizing models into staging, intermediate, and marts layers\n- Implementing data quality tests\n- Creating incremental models for large datasets\n- Documenting data models and lineage\n- Setting up dbt project structure\n\n## Core Concepts\n\n### 1. Model Layers (Medallion Architecture)\n\n```\nsources\u002F          Raw data definitions\n    ↓\nstaging\u002F          1:1 with source, light cleaning\n    ↓\nintermediate\u002F     Business logic, joins, aggregations\n    ↓\nmarts\u002F            Final analytics tables\n```\n\n### 2. Naming Conventions\n\n| Layer | Prefix | Example |\n|-------|--------|---------|\n| Staging | `stg_` | `stg_stripe__payments` |\n| Intermediate | `int_` | `int_payments_pivoted` |\n| Marts | `dim_`, `fct_` | `dim_customers`, `fct_orders` |\n\n## Quick Start\n\n```yaml\n# dbt_project.yml\nname: 'analytics'\nversion: '1.0.0'\nprofile: 'analytics'\n\nmodel-paths: [\"models\"]\nanalysis-paths: [\"analyses\"]\ntest-paths: [\"tests\"]\nseed-paths: [\"seeds\"]\nmacro-paths: [\"macros\"]\n\nvars:\n  start_date: '2020-01-01'\n\nmodels:\n  analytics:\n    staging:\n      +materialized: view\n      +schema: staging\n    intermediate:\n      +materialized: ephemeral\n    marts:\n      +materialized: table\n      +schema: analytics\n```\n\n```\n# Project structure\nmodels\u002F\n├── staging\u002F\n│   ├── stripe\u002F\n│   │   ├── _stripe__sources.yml\n│   │   ├── _stripe__models.yml\n│   │   ├── stg_stripe__customers.sql\n│   │   └── stg_stripe__payments.sql\n│   └── shopify\u002F\n│       ├── _shopify__sources.yml\n│       └── stg_shopify__orders.sql\n├── intermediate\u002F\n│   └── finance\u002F\n│       └── int_payments_pivoted.sql\n└── marts\u002F\n    ├── core\u002F\n    │   ├── _core__models.yml\n    │   ├── dim_customers.sql\n    │   └── fct_orders.sql\n    └── finance\u002F\n        └── fct_revenue.sql\n```\n\n## Patterns\n\n### Pattern 1: Source Definitions\n\n```yaml\n# models\u002Fstaging\u002Fstripe\u002F_stripe__sources.yml\nversion: 2\n\nsources:\n  - name: stripe\n    description: Raw Stripe data loaded via Fivetran\n    database: raw\n    schema: stripe\n    loader: fivetran\n    loaded_at_field: _fivetran_synced\n    freshness:\n      warn_after: {count: 12, period: hour}\n      error_after: {count: 24, period: hour}\n    tables:\n      - name: customers\n        description: Stripe customer records\n        columns:\n          - name: id\n            description: Primary key\n            tests:\n              - unique\n              - not_null\n          - name: email\n            description: Customer email\n          - name: created\n            description: Account creation timestamp\n\n      - name: payments\n        description: Stripe payment transactions\n        columns:\n          - name: id\n            tests:\n              - unique\n              - not_null\n          - name: customer_id\n            tests:\n              - not_null\n              - relationships:\n                  to: source('stripe', 'customers')\n                  field: id\n```\n\n### Pattern 2: Staging Models\n\n```sql\n-- models\u002Fstaging\u002Fstripe\u002Fstg_stripe__customers.sql\nwith source as (\n    select * from {{ source('stripe', 'customers') }}\n),\n\nrenamed as (\n    select\n        -- ids\n        id as customer_id,\n\n        -- strings\n        lower(email) as email,\n        name as customer_name,\n\n        -- timestamps\n        created as created_at,\n\n        -- metadata\n        _fivetran_synced as _loaded_at\n\n    from source\n)\n\nselect * from renamed\n```\n\n```sql\n-- models\u002Fstaging\u002Fstripe\u002Fstg_stripe__payments.sql\n{{\n    config(\n        materialized='incremental',\n        unique_key='payment_id',\n        on_schema_change='append_new_columns'\n    )\n}}\n\nwith source as (\n    select * from {{ source('stripe', 'payments') }}\n\n    {% if is_incremental() %}\n    where _fivetran_synced > (select max(_loaded_at) from {{ this }})\n    {% endif %}\n),\n\nrenamed as (\n    select\n        -- ids\n        id as payment_id,\n        customer_id,\n        invoice_id,\n\n        -- amounts (convert cents to dollars)\n        amount \u002F 100.0 as amount,\n        amount_refunded \u002F 100.0 as amount_refunded,\n\n        -- status\n        status as payment_status,\n\n        -- timestamps\n        created as created_at,\n\n        -- metadata\n        _fivetran_synced as _loaded_at\n\n    from source\n)\n\nselect * from renamed\n```\n\n### Pattern 3: Intermediate Models\n\n```sql\n-- models\u002Fintermediate\u002Ffinance\u002Fint_payments_pivoted_to_customer.sql\nwith payments as (\n    select * from {{ ref('stg_stripe__payments') }}\n),\n\ncustomers as (\n    select * from {{ ref('stg_stripe__customers') }}\n),\n\npayment_summary as (\n    select\n        customer_id,\n        count(*) as total_payments,\n        count(case when payment_status = 'succeeded' then 1 end) as successful_payments,\n        sum(case when payment_status = 'succeeded' then amount else 0 end) as total_amount_paid,\n        min(created_at) as first_payment_at,\n        max(created_at) as last_payment_at\n    from payments\n    group by customer_id\n)\n\nselect\n    customers.customer_id,\n    customers.email,\n    customers.created_at as customer_created_at,\n    coalesce(payment_summary.total_payments, 0) as total_payments,\n    coalesce(payment_summary.successful_payments, 0) as successful_payments,\n    coalesce(payment_summary.total_amount_paid, 0) as lifetime_value,\n    payment_summary.first_payment_at,\n    payment_summary.last_payment_at\n\nfrom customers\nleft join payment_summary using (customer_id)\n```\n\n### Pattern 4: Mart Models (Dimensions and Facts)\n\n```sql\n-- models\u002Fmarts\u002Fcore\u002Fdim_customers.sql\n{{\n    config(\n        materialized='table',\n        unique_key='customer_id'\n    )\n}}\n\nwith customers as (\n    select * from {{ ref('int_payments_pivoted_to_customer') }}\n),\n\norders as (\n    select * from {{ ref('stg_shopify__orders') }}\n),\n\norder_summary as (\n    select\n        customer_id,\n        count(*) as total_orders,\n        sum(total_price) as total_order_value,\n        min(created_at) as first_order_at,\n        max(created_at) as last_order_at\n    from orders\n    group by customer_id\n),\n\nfinal as (\n    select\n        -- surrogate key\n        {{ dbt_utils.generate_surrogate_key(['customers.customer_id']) }} as customer_key,\n\n        -- natural key\n        customers.customer_id,\n\n        -- attributes\n        customers.email,\n        customers.customer_created_at,\n\n        -- payment metrics\n        customers.total_payments,\n        customers.successful_payments,\n        customers.lifetime_value,\n        customers.first_payment_at,\n        customers.last_payment_at,\n\n        -- order metrics\n        coalesce(order_summary.total_orders, 0) as total_orders,\n        coalesce(order_summary.total_order_value, 0) as total_order_value,\n        order_summary.first_order_at,\n        order_summary.last_order_at,\n\n        -- calculated fields\n        case\n            when customers.lifetime_value >= 1000 then 'high'\n            when customers.lifetime_value >= 100 then 'medium'\n            else 'low'\n        end as customer_tier,\n\n        -- timestamps\n        current_timestamp as _loaded_at\n\n    from customers\n    left join order_summary using (customer_id)\n)\n\nselect * from final\n```\n\n```sql\n-- models\u002Fmarts\u002Fcore\u002Ffct_orders.sql\n{{\n    config(\n        materialized='incremental',\n        unique_key='order_id',\n        incremental_strategy='merge'\n    )\n}}\n\nwith orders as (\n    select * from {{ ref('stg_shopify__orders') }}\n\n    {% if is_incremental() %}\n    where updated_at > (select max(updated_at) from {{ this }})\n    {% endif %}\n),\n\ncustomers as (\n    select * from {{ ref('dim_customers') }}\n),\n\nfinal as (\n    select\n        -- keys\n        orders.order_id,\n        customers.customer_key,\n        orders.customer_id,\n\n        -- dimensions\n        orders.order_status,\n        orders.fulfillment_status,\n        orders.payment_status,\n\n        -- measures\n        orders.subtotal,\n        orders.tax,\n        orders.shipping,\n        orders.total_price,\n        orders.total_discount,\n        orders.item_count,\n\n        -- timestamps\n        orders.created_at,\n        orders.updated_at,\n        orders.fulfilled_at,\n\n        -- metadata\n        current_timestamp as _loaded_at\n\n    from orders\n    left join customers on orders.customer_id = customers.customer_id\n)\n\nselect * from final\n```\n\n### Pattern 5: Testing and Documentation\n\n```yaml\n# models\u002Fmarts\u002Fcore\u002F_core__models.yml\nversion: 2\n\nmodels:\n  - name: dim_customers\n    description: Customer dimension with payment and order metrics\n    columns:\n      - name: customer_key\n        description: Surrogate key for the customer dimension\n        tests:\n          - unique\n          - not_null\n\n      - name: customer_id\n        description: Natural key from source system\n        tests:\n          - unique\n          - not_null\n\n      - name: email\n        description: Customer email address\n        tests:\n          - not_null\n\n      - name: customer_tier\n        description: Customer value tier based on lifetime value\n        tests:\n          - accepted_values:\n              values: ['high', 'medium', 'low']\n\n      - name: lifetime_value\n        description: Total amount paid by customer\n        tests:\n          - dbt_utils.expression_is_true:\n              expression: \">= 0\"\n\n  - name: fct_orders\n    description: Order fact table with all order transactions\n    tests:\n      - dbt_utils.recency:\n          datepart: day\n          field: created_at\n          interval: 1\n    columns:\n      - name: order_id\n        tests:\n          - unique\n          - not_null\n      - name: customer_key\n        tests:\n          - not_null\n          - relationships:\n              to: ref('dim_customers')\n              field: customer_key\n```\n\n### Pattern 6: Macros and DRY Code\n\n```sql\n-- macros\u002Fcents_to_dollars.sql\n{% macro cents_to_dollars(column_name, precision=2) %}\n    round({{ column_name }} \u002F 100.0, {{ precision }})\n{% endmacro %}\n\n-- macros\u002Fgenerate_schema_name.sql\n{% macro generate_schema_name(custom_schema_name, node) %}\n    {%- set default_schema = target.schema -%}\n    {%- if custom_schema_name is none -%}\n        {{ default_schema }}\n    {%- else -%}\n        {{ default_schema }}_{{ custom_schema_name }}\n    {%- endif -%}\n{% endmacro %}\n\n-- macros\u002Flimit_data_in_dev.sql\n{% macro limit_data_in_dev(column_name, days=3) %}\n    {% if target.name == 'dev' %}\n        where {{ column_name }} >= dateadd(day, -{{ days }}, current_date)\n    {% endif %}\n{% endmacro %}\n\n-- Usage in model\nselect * from {{ ref('stg_orders') }}\n{{ limit_data_in_dev('created_at') }}\n```\n\n### Pattern 7: Incremental Strategies\n\n```sql\n-- Delete+Insert (default for most warehouses)\n{{\n    config(\n        materialized='incremental',\n        unique_key='id',\n        incremental_strategy='delete+insert'\n    )\n}}\n\n-- Merge (best for late-arriving data)\n{{\n    config(\n        materialized='incremental',\n        unique_key='id',\n        incremental_strategy='merge',\n        merge_update_columns=['status', 'amount', 'updated_at']\n    )\n}}\n\n-- Insert Overwrite (partition-based)\n{{\n    config(\n        materialized='incremental',\n        incremental_strategy='insert_overwrite',\n        partition_by={\n            \"field\": \"created_date\",\n            \"data_type\": \"date\",\n            \"granularity\": \"day\"\n        }\n    )\n}}\n\nselect\n    *,\n    date(created_at) as created_date\nfrom {{ ref('stg_events') }}\n\n{% if is_incremental() %}\nwhere created_date >= dateadd(day, -3, current_date)\n{% endif %}\n```\n\n## dbt Commands\n\n```bash\n# Development\ndbt run                          # Run all models\ndbt run --select staging         # Run staging models only\ndbt run --select +fct_orders     # Run fct_orders and its upstream\ndbt run --select fct_orders+     # Run fct_orders and its downstream\ndbt run --full-refresh           # Rebuild incremental models\n\n# Testing\ndbt test                         # Run all tests\ndbt test --select stg_stripe     # Test specific models\ndbt build                        # Run + test in DAG order\n\n# Documentation\ndbt docs generate                # Generate docs\ndbt docs serve                   # Serve docs locally\n\n# Debugging\ndbt compile                      # Compile SQL without running\ndbt debug                        # Test connection\ndbt ls --select tag:critical     # List models by tag\n```\n\n## Best Practices\n\n### Do's\n- **Use staging layer** - Clean data once, use everywhere\n- **Test aggressively** - Not null, unique, relationships\n- **Document everything** - Column descriptions, model descriptions\n- **Use incremental** - For tables > 1M rows\n- **Version control** - dbt project in Git\n\n### Don'ts\n- **Don't skip staging** - Raw → mart is tech debt\n- **Don't hardcode dates** - Use `{{ var('start_date') }}`\n- **Don't repeat logic** - Extract to macros\n- **Don't test in prod** - Use dev target\n- **Don't ignore freshness** - Monitor source data\n\n## Resources\n\n- [dbt Documentation](https:\u002F\u002Fdocs.getdbt.com\u002F)\n- [dbt Best Practices](https:\u002F\u002Fdocs.getdbt.com\u002Fguides\u002Fbest-practices)\n- [dbt-utils Package](https:\u002F\u002Fhub.getdbt.com\u002Fdbt-labs\u002Fdbt_utils\u002Flatest\u002F)\n- [dbt Discourse](https:\u002F\u002Fdiscourse.getdbt.com\u002F)\n",{"name":5,"description":7},"main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002Faiskillstore\u002Fmarketplace\u002Fwshobson\u002Fdbt-transformation-patterns",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":64,"prompt":74,"zipAvailable":75},[65,68,71],{"tool":66,"command":67},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill dbt-transformation-patterns",{"tool":69,"command":70},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill dbt-transformation-patterns",{"tool":72,"command":73},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill dbt-transformation-patterns","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fwshobson\u002Fdbt-transformation-patterns\u002FSKILL.md",true,[77],{"path":78,"resourceType":79,"resourceTypeDesc":80,"sizeBytes":81,"sourceHtmlUrl":82},"skill-report.json","REFERENCE","参考文档",13139,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faiskillstore\u002Fmarketplace\u002Fwshobson\u002Fdbt-transformation-patterns\u002Fskill-report.json",[84,95,105,114,120,127],{"skillNo":85,"name":86,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":87,"categories":88,"keywords":92,"publishedAt":49},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[89],{"categoryNo":90,"categoryName":91,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[43,93,94],"model hierarchy","model hierarchy skill",{"skillNo":96,"name":97,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":98,"categories":99,"keywords":101,"publishedAt":49},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[100],{"categoryNo":90,"categoryName":91,"taxonomyType":21,"taxonomyTypeDesc":22},[102,103,104],"MCP","zoom","zoom mcp",{"skillNo":106,"name":107,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":108,"categories":109,"keywords":110,"publishedAt":49},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[111,112,113,103],"sdk","zoom apps sdk","apps",{"skillNo":115,"name":116,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":117,"categories":118,"keywords":119,"publishedAt":49},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[116],{"skillNo":121,"name":122,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":123,"categories":124,"keywords":126,"publishedAt":49},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[125],{"categoryNo":90,"categoryName":91,"taxonomyType":21,"taxonomyTypeDesc":22},[122],{"skillNo":128,"name":129,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":130,"categories":131,"keywords":132,"publishedAt":49},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[129]]