Salesforce model

Opportunity pipeline

Every opportunity with its outcome, stage, account and owner.

Grain
one row per opportunity (salesforce_org x opportunity_id)
Built from
  1. 4
    Raw tables
  2. 4
    Staging
  3. 1
    Core
  4. 1
    Analytics
Reads from Salesforce
accountopportunityopportunity_stageuser
Ships as
Weld and dbt SQL, open source on GitHub
core.salesforce.opportunity_pipeline
1-- core.salesforce.opportunity_pipeline
2-- Every opportunity with its outcome, stage, account and owner.
3-- Grain: one row per opportunity (salesforce_org x opportunity_id).
4-- Depends on: staging.salesforce.opportunity, .opportunity_stage, .account, .user
5--
6-- OUTCOME comes from Salesforce, not from stage names. is_closed and is_won are
7-- set by the stage's Type and Probability settings in Setup, so custom stages are
8-- classified the way Salesforce classifies them and there is nothing to maintain.
9--
10-- CURRENCY. In a single-currency org currency_code is ORG_DEFAULT and every amount
11-- is in the org currency. In a multi-currency org (see staging.salesforce.opportunity)
12-- amounts are in each opportunity's own currency: group by currency_code before
13-- summing. Converting to a corporate currency needs Salesforce's CurrencyType or
14-- DatedConversionRate objects and is not done here.
15--
16-- CLOSE DATE. For an open opportunity close_date is the expected close date. For a
17-- closed one it is whatever the rep left in the field: Salesforce does not set it
18-- automatically on close. core.salesforce.opportunity_stage_history has the
19-- moment the stage actually changed.
20--
21-- Current state only. For the pipeline as it stood on an earlier date, use the
22-- stage history model or materialise this one on a schedule and keep the runs.
23
24WITH stages AS (
25    -- One row per stage name, so a stage can never fan an opportunity out.
26    SELECT *
27    FROM {{staging.salesforce.opportunity_stage}}
28    QUALIFY ROW_NUMBER() OVER (
29        PARTITION BY salesforce_org, stage_name ORDER BY stage_is_active DESC, stage_id
30    ) = 1
31)
32
33SELECT
34    o.salesforce_org,
35    o.opportunity_id,
36    o.opportunity_name,
37    o.opportunity_type,
38    o.lead_source,
39
40    o.stage_name,

This model and the 5 it depends on. Deploy them together; refs like {{staging.…}} resolve to the matching folders in Weld, and the dbt view uses source() and ref().

Notes

How it counts. And where it stops.

OUTCOME comes from Salesforce, not from stage names. is_closed and is_won are set by the stage's Type and Probability settings in Setup, so custom stages are classified the way Salesforce classifies them and there is nothing to maintain. CURRENCY. In a single-currency org currency_code is ORG_DEFAULT and every amount is in the org currency. In a multi-currency org (see staging.salesforce.opportunity) amounts are in each opportunity's own currency: group by currency_code before summing. Converting to a corporate currency needs Salesforce's CurrencyType or DatedConversionRate objects and is not done here. CLOSE DATE. For an open opportunity close_date is the expected close date. For a closed one it is whatever the rep left in the field: Salesforce does not set it automatically on close. core.salesforce.opportunity_stage_history has the moment the stage actually changed. Current state only. For the pipeline as it stood on an earlier date, use the stage history model or materialise this one on a schedule and keep the runs.

Raw tables it reads

  • raw.salesforce.account
  • raw.salesforce.opportunity
  • raw.salesforce.opportunity_stage
  • raw.salesforce.user

Put Salesforce in front
of your agents.

Connect Salesforce, deploy the models, and ask questions in Claude, Cursor or ChatGPT. Free for 14 days, no credit card.