Business Intelligence · Power BI · Snowflake

AT&T Business Intelligence & Self-Service Analytics Platform

25+ Power BI Dashboards · 45s → 3s Query Optimization · 150+ Self-Service Users · 60% IT Request Reduction

Executive Summary

Built AT&T Marketing Operations' Business Intelligence platform, consolidating data from Workfront (project management), Salesforce (CRM), and ZoomInfo (contact data) via Fivetran into Snowflake and powering self-service analytics for 150+ users. Designed and built 25+ Power BI dashboards for executive leadership tracking campaign performance, workforce costs, budget allocations, and operational KPIs. Developed DAX measures for YoY growth, rolling averages, cohort analysis, and variance-to-budget calculations. Reduced dashboard query times from 45 seconds to 3 seconds (93% improvement) through optimized dimensional models. Reduced IT data request tickets by 60% and manual data retrieval by 40%.

The Problem Statement: When I joined AT&T's Marketing Technology team in March 2024, the analytics workflow was entirely manual. Campaign managers exported data from Workfront, pasted it into Excel, manually joined it with Salesforce exports, and produced reports that were outdated by the time they were distributed. 200+ concurrent campaigns, zero single source of truth.

The Decision: Rather than building another one-off dashboard, I proposed a proper BI platform: a unified Snowflake reporting layer fed by Fivetran, with a Power BI semantic layer on top enabling self-service analytics. The key question was always "who is consuming this data and what decisions are they making?" — every dimension, every measure, and every dashboard layout was designed with the business user in mind first.

The Result: 12 months later: 25+ Power BI dashboards in production, 150+ self-service users, and the analytics team no longer spends 40% of their time pulling data manually.

Key Outcomes

  • 25+ Power BI dashboards deployed to production for executive leadership
  • 150+ self-service users on Power BI — up from a team of 3 analysts
  • 60% reduction in IT data request tickets
  • 40% reduction in manual data retrieval across marketing operations
  • 45s → 3s Power BI query latency improvement (query optimization)
  • 200+ concurrent campaigns tracked via incremental dbt models

BI Platform Architecture

Workfront

Project Tasks
Timelines
Resource Plans

Salesforce

CRM Contacts
Opportunities
Account Data

ZoomInfo

Company Data
Contact Enrichment
Intent Signals

Fivetran → Snowflake → dbt

Analytics Engineering Platform

Fivetran syncs all three sources into Snowflake raw schemas on a scheduled cadence. dbt staging models clean and normalize the raw data; intermediate models apply business logic (campaign attribution, SCD Type 2 organizational tracking, date spine joins); mart models produce the final star schema consumed by Power BI.

Key Performance Indicators

dbt Models Deployed
80+
↑ Staging / Int / Marts
Query Latency
45s → 3s
↑ 93% improvement
Self-Service Users
150+
↑ Power BI consumers
IT Request Reduction
60%
↑ Tickets eliminated
Manual Data Reduction
40%
Hours reclaimed weekly
Campaigns Tracked
200+
Incremental models

Technical Deep Dive

Dimensional Data Model Architecture

Before: Manual Process

  • Workfront CSV exports → manual Excel joins
  • Salesforce reports downloaded ad hoc
  • No version control, no lineage, no tests
  • 3 analysts spending 40% of time on data prep
  • Power BI queries timing out at 45+ seconds
  • Each team had its own definition of "active campaign"

After: dbt + Snowflake Platform

  • Fivetran → Snowflake raw → dbt staging models
  • Intermediate models: business logic, SCD Type 2, attribution
  • Mart models: star schema facts + conformed dimensions
  • 150+ users consuming self-service Power BI
  • Sub-3-second query performance via Snowflake clustering
  • Single conformed definition for every business concept

Incremental Models for Campaign Data

With 200+ concurrent campaigns producing daily volume metrics, full-refresh models were impractical. Each incremental dbt model uses a unique_key composite (campaign_id + date) and a max(updated_at) filter → meaning only new or changed records are processed per run. This pattern scaled campaign fact tables to handle daily Workfront sync without warehouse timeouts.

dbt Incremental Model → fct_campaign_daily.sql:

-- models/marts/marketing/fct_campaign_daily.sql
-- Incremental model: processes only new/updated campaign records each run
-- Grain: one row per campaign per day

{{
    config(
        materialized='incremental',
        unique_key='campaign_day_key',
        on_schema_change='sync_all_columns',
        cluster_by=['campaign_date', 'department_key']
    )
}}

with workfront_tasks as (
    select * from {{ ref('int_workfront_tasks_normalized') }}
    {% if is_incremental() %}
        where updated_at > (select max(loaded_at) from {{ this }})
    {% endif %}
),

salesforce_campaigns as (
    select * from {{ ref('int_salesforce_campaigns_active') }}
),

date_spine as (
    -- reusable macro: generates daily date records for a rolling window
    {{ dbt_utils.date_spine(
        datepart   = "day",
        start_date = "cast('2024-01-01' as date)",
        end_date   = "current_date()"
    ) }}
),

campaign_daily as (
    select
        {{ dbt_utils.generate_surrogate_key([
            'wt.campaign_id', 'ds.date_day'
        ]) }}                                               as campaign_day_key,

        ds.date_day                                         as campaign_date,
        wt.campaign_id,
        wt.campaign_name,
        wt.department_key,
        wt.owner_employee_key,
        sc.campaign_type,
        sc.budget_amount,

        -- calculated daily metrics
        sum(wt.planned_hours)                               as planned_hours_day,
        sum(wt.actual_hours)                                as actual_hours_day,
        count(distinct wt.task_id)                          as task_count,
        count(distinct case when wt.status = 'COMPLETE'
              then wt.task_id end)                          as tasks_completed,

        -- budget variance
        coalesce(sc.budget_amount, 0) -
            coalesce(sc.actual_spend_to_date, 0)            as budget_variance,

        current_timestamp()                                 as loaded_at

    from date_spine ds
    join workfront_tasks wt
        on ds.date_day between wt.planned_start_date and wt.planned_end_date
    left join salesforce_campaigns sc
        on wt.campaign_id = sc.campaign_id

    group by 1,2,3,4,5,6,7,8
)

select * from campaign_daily

SCD Type 2 → Organizational Change Tracking

AT&T underwent multiple reorganizations during 2024. Department hierarchies, team assignments, and campaign ownership changed frequently. Rather than overwrite historical records, I implemented SCD Type 2 in a dbt macro to track changes with valid_from / valid_to effective dates, allowing Power BI to report on "who owned what, when" across reorg timelines.

dbt Macro → scd_type_2.sql (reusable across all dimensions):

-- macros/scd_type_2.sql
-- Tracks slowly changing dimension changes with effective dating
-- Usage: {{ scd_type_2(source_model='stg_salesforce_accounts', natural_key='account_id', tracked_columns=['department', 'owner_id', 'territory']) }}

{% macro scd_type_2(source_model, natural_key, tracked_columns) %}

with source as (
    select * from {{ ref(source_model) }}
),

changes_detected as (
    select
        {{ natural_key }},
        {% for col in tracked_columns %}
        {{ col }},
        {% endfor %}
        updated_at,

        -- hash all tracked columns to detect any change
        md5(concat_ws('|',
            {% for col in tracked_columns %}
            coalesce(cast({{ col }} as varchar), 'NULL')
            {{ ',' if not loop.last }}
            {% endfor %}
        ))                                              as row_hash,

        lag(md5(concat_ws('|',
            {% for col in tracked_columns %}
            coalesce(cast({{ col }} as varchar), 'NULL')
            {{ ',' if not loop.last }}
            {% endfor %}
        ))) over (partition by {{ natural_key }}
                  order by updated_at)                  as prev_row_hash

    from source
),

versioned as (
    select
        *,
        updated_at                                      as valid_from,
        coalesce(
            lead(updated_at) over (partition by {{ natural_key }}
                                   order by updated_at),
            '9999-12-31'::date
        )                                               as valid_to,
        case when lead(updated_at) over (partition by {{ natural_key }}
                                         order by updated_at) is null
             then true else false end                   as is_current

    from changes_detected
    where row_hash != coalesce(prev_row_hash, '')
       or prev_row_hash is null
)

select * from versioned

{% endmacro %}

The Discovery That Prompted a 60% IT Ticket Reduction

Three months into building the dbt platform, I analyzed the IT data request queue. Of the 400+ monthly tickets, 62% were variations of the same five questions: "How many campaigns are active right now?", "What's the budget utilization for Q3?", "Which campaigns are overrunning?", "Who owns Salesforce account X?", "What's the forecast for Y campaign?" Every analyst was asking the same questions independently because there was no shared, trusted dataset. I built five mart models directly targeting these questions and published them to Power BI. The next month, the same five question types disappeared from the ticket queue entirely. The 60% IT request reduction wasn't a feature — it was a natural byproduct of having a single source of truth that people could access themselves. That experience crystallized why analytics engineering matters: it's not about building dashboards. It's about eliminating the need to ask.

Results & Business Impact

Manual Data Work Eliminated
40%
Hours reclaimed in marketing ops
Self-Service Power BI Users
150+
Up from 3 analysts
IT Request Tickets
-60%
Monthly ticket reduction
Query Performance
45s → 3s
Power BI dashboard loads

Technologies & Tools

BI & Data Modeling (Core)

dbt Core dbt Incremental Models dbt Macros dbt Tests dbt Docs SCD Type 2 dbt-utils

Data Infrastructure

Snowflake Fivetran Workfront Salesforce ZoomInfo GitHub Actions

BI & Analytics

Power BI DAX Power Query SQL (Advanced) Python