Labs4Change

dbt vs Looker: Where Should Your Business Logic Live?

A decision framework for what belongs in dbt and what belongs in Looker. Stop duplicating logic across your data stack — here's where each transformation should go.

Every team using dbt and Looker together hits the same question: where does the business logic go? Do you calculate revenue in a dbt model or a LookML measure? Do you define "active customer" in SQL or in an Explore filter?

The wrong answer leads to duplicated logic, conflicting numbers, and a data team spending half their time debugging discrepancies. Here's the framework we use across every engagement.


The Core Principle: Single Source of Truth

If a calculation can be defined once and used everywhere, it should be. The question is where "once" lives.

dbt is your transformation layer. It owns the data model — cleaning, joining, deduplicating, and structuring data into tables that any downstream tool can consume.

Looker is your presentation layer. It owns the user experience — labels, permissions, drill paths, caching, and simple aggregations that map directly to business questions.

When you put transformation logic in Looker (derived tables, complex SQL in dimensions), you've coupled your data model to your BI tool. When you put presentation logic in dbt (field labels, format strings), you've coupled your data model to a specific consumer.


What Belongs in dbt

Data Transformations

  • Table joins and relationships
  • Data cleaning and standardization
  • Type casting and conversions
  • Data deduplication
  • Window functions
  • Historical data handling (SCD Type 2, snapshots)

Business Logic

  • Core business rules (e.g., "what counts as an active customer")
  • Complex CASE statements
  • Derived calculations (e.g., LTV, churn risk score)
  • Common metric definitions
  • Data flattening and nested field handling

Data Quality

  • Schema tests (not_null, unique, accepted_values)
  • Custom data tests
  • Data validation rules
  • Null handling
  • Referential integrity checks

Documentation

  • Table and column descriptions (these flow to BigQuery and then to Looker)
  • Business logic documentation
  • Data lineage
  • Source documentation

Example: Defining "active customer" in dbt

-- models/marts/customers.sql
SELECT
  customer_id,
  email,
  first_order_date,
  last_order_date,
  total_orders,
  total_revenue,
  CASE
    WHEN last_order_date >= DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAY) THEN 'Active'
    WHEN last_order_date >= DATE_SUB(CURRENT_DATE(), INTERVAL 365 DAY) THEN 'At Risk'
    ELSE 'Churned'
  END AS customer_status
FROM {{ ref('stg_customers') }}

Now every tool — Looker, a Python notebook, a Streamlit app — gets the same definition of "active customer."


What Belongs in Looker

User Experience

  • Field labels and descriptions (only when overriding dbt descriptions)
  • Field grouping (group_label)
  • Explore labels and descriptions
  • Dashboard layouts

Access Control

  • Row-level security (access_filter)
  • Column-level security (access_grant)
  • Model access and permission sets
  • User attributes

Query-Time Aggregations

  • Simple measures: SUM, COUNT, AVG, MIN, MAX
  • Count distinct
  • Running totals and percent-of-total
  • Drill fields and links

Visualization

  • Chart configurations
  • Conditional formatting
  • Drill-down behavior
  • Dashboard design

Example: Simple aggregation in Looker

measure: total_revenue {
  type: sum
  sql: ${revenue} ;;
  value_format_name: usd
  drill_fields: [order_id, created_date, revenue, customer_name]
}

This is a pure aggregation — it doesn't define what "revenue" is (that's dbt's job), it just sums the column and adds formatting and drill paths.


The Gray Areas

Derived Tables: Almost Always Wrong in Looker

If you're writing a derived_table in LookML, ask yourself: could this be a dbt model? The answer is almost always yes.

# Don't do this in Looker
view: customer_order_summary {
  derived_table: {
    sql:
      SELECT
        customer_id,
        COUNT(*) AS total_orders,
        SUM(amount) AS total_revenue
      FROM orders
      GROUP BY customer_id ;;
  }
}
-- Do this in dbt instead
-- models/marts/customer_order_summary.sql
SELECT
  customer_id,
  COUNT(*) AS total_orders,
  SUM(amount) AS total_revenue
FROM {{ ref('stg_orders') }}
GROUP BY customer_id

Why dbt is better here:

  • Tested with dbt tests
  • Version controlled in dbt
  • Materialized as a table (faster queries)
  • Documented in dbt docs
  • Available to other tools, not just Looker

The only valid exception: Looker-specific PDTs that handle user-facing pivots or performance optimizations that don't make sense as part of the core data model.

Descriptions: dbt Wins

Write descriptions in dbt. They flow to BigQuery column descriptions, which Looker inherits automatically:

# dbt schema.yml
models:
  - name: orders
    description: "All completed and pending orders"
    columns:
      - name: order_id
        description: "Unique identifier for each order"
      - name: status
        description: "Current status: Processing, Shipped, or Delivered"

Don't duplicate these in LookML. Only add Looker-specific context if needed (e.g., "Filter by this field first — unfiltered queries are expensive").

Simple CASE Statements: It Depends

A simple bucketing dimension can live in Looker if it's purely for presentation:

dimension: revenue_tier {
  type: string
  sql: CASE
    WHEN ${revenue} >= 10000 THEN 'Enterprise'
    WHEN ${revenue} >= 1000 THEN 'Mid-Market'
    ELSE 'SMB'
  END ;;
}

But if other teams or tools need the same bucketing, it belongs in dbt.


Decision Framework

QuestiondbtLooker
Is this a data transformation?Yes
Is this a business rule used by multiple tools?Yes
Does this involve joining tables?Yes
Is this a simple aggregation (SUM, COUNT)?Yes
Is this about access control or permissions?Yes
Is this about how fields appear to users?Yes
Is this a derived table?Almost alwaysRare exception
Is this a description?Yes (flows to Looker)Only for overrides

The Anti-Patterns

1. Duplicated Revenue Definitions

-- In dbt
total_revenue = SUM(amount) WHERE status != 'cancelled'

-- In Looker (different definition!)
measure: total_revenue {
  type: sum
  sql: ${amount} ;;  # Missing the cancelled filter
}

Now finance sees one number, the dashboard shows another. Fix: define revenue as a column in dbt with the business rule applied. Looker just sums it.

2. Complex SQL in LookML

dimension: customer_segment {
  sql: CASE
    WHEN ${lifetime_value} > 10000
      AND ${days_since_last_order} < 30
      AND ${order_count} > 5 THEN 'VIP'
    WHEN ${lifetime_value} > 5000 THEN 'High Value'
    WHEN ${days_since_last_order} > 365 THEN 'Churned'
    ELSE 'Standard'
  END ;;
}

This is business logic that belongs in dbt. What happens when the marketing team wants to use customer segments in their email tool? They can't — it's locked in Looker.

3. Presentation Logic in dbt

-- Don't put this in dbt
SELECT
  revenue,
  CONCAT('$', FORMAT(revenue, 2)) AS formatted_revenue,
  'Revenue' AS field_label
FROM orders

Formatting and labeling are Looker's job.


Getting Started

If your Looker instance has derived tables and complex SQL scattered across LookML files, here's the migration path:

  1. Inventory your derived tables — list every derived_table in your LookML project
  2. Identify the dbt candidates — anything that joins, transforms, or applies business rules
  3. Build the dbt models — one model per derived table, tested and documented
  4. Update LookML — point views at the new dbt-materialized tables
  5. Delete the derived tables — once validated, remove them from LookML

We've done this migration dozens of times. The typical result: 40-60% fewer lines of LookML, faster query performance (dbt materializes tables vs. Looker's runtime SQL), and a single source of truth for every metric.

Get the LookML Best Practices Guide + AI Skill

Our guide covers the dbt/Looker boundary, project structure, caching, and more — the patterns we've refined across 200+ implementations.


Labs4Change builds modern data stacks with dbt, BigQuery, and Looker. Book a free architecture review — we'll audit where your business logic lives and how to clean it up.