Skip to content

Zoho CRM SQL templates - #2

Open
jthordal wants to merge 2 commits into
mainfrom
zoho-crm-templates
Open

Zoho CRM SQL templates#2
jthordal wants to merge 2 commits into
mainfrom
zoho-crm-templates

Conversation

@jthordal

Copy link
Copy Markdown

Four core models over Weld's Zoho CRM connector, in both Weld and dbt dialects, following the same four-layer shape as shopify/.

Model Grain
deal_pipeline deal — stage classified, account + owner joined on
deal_flow_by_month month × owner — created / won / lost, win rate
rep_activity day × owner — calls, meetings, tasks
account_360 account — people, pipeline, last touch

9 staging models, 4 analytics contracts, 4 tests. 42 files.

The schema constrains what is buildable, and the models say so

Rather than papering over the gaps, each one is documented in the file header and in zoho-crm/README.md:

  • Activities have no parent record. Zoho's Calls, Events and Tasks modules carry What_Id / Who_Id; neither is synced. owner_id is the only foreign key on all three, so activity is reported per rep and never per deal. Notes are the exception — Parent_Id is synced, which is the only reason account_360 can report a last-touch date.
  • No won/lost flag. Zoho ships no is_won or is_closed, so the outcome is read out of the stage string with LIKE '%won%' / '%lost%' — a pattern rather than an equality list, because Zoho's own defaults include Closed-Lost to Competition. assert_deal_stages_are_classified surfaces custom closed stages that would otherwise sit in open pipeline; this is the one edit almost every org has to make.
  • No history. No history tables and no stage-change audit, so nothing here can reconstruct a past pipeline. The README points at materialising deal_pipeline daily instead.
  • call_duration is a string that Zoho's API reference documents as hh:mm while the CRM UI renders mm:ss, with no seconds column to settle it. Parsed as documented and flagged as unverified; the test guards the shape, not the unit.

Verification

All 38 SQL files parse as BigQuery, and every raw column referenced in the staging models was checked against the connector's published ERD. Not run end to end against a warehouse — reconcile open pipeline against Zoho's own Deals view before relying on the output.

Companion website PR adds these four to /templates and to the connector docs page, alongside a walkthrough post.

🤖 Generated with Claude Code

Copilot AI lite review requested due to automatic review settings August 26, 2026 08:27

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new zoho-crm/ template package to this SQL templates library, providing a consistent 4-layer (raw → staging → core → analytics) reporting shape for Weld’s Zoho CRM connector, plus a dbt dialect mirror and accompanying docs/tests.

Changes:

  • Introduces 9 Zoho CRM staging models (casts/renames/blank-normalization) for both Weld and dbt.
  • Adds 4 core reporting models (deal pipeline, deal flow by month, rep activity, account 360) plus thin analytics contracts in both dialects.
  • Documents connector limitations and includes dbt “assert_*” SQL tests to surface key data-quality/assumption breaks.

Reviewed changes

Copilot reviewed 42 out of 42 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
README.md Adds Zoho CRM templates to the repo’s top-level index.
zoho-crm/README.md Documents model set, constraints of the connector, and layering approach.
zoho-crm/weld/staging/account.sql Staging wrapper for account (casts/renames/blank→NULL).
zoho-crm/weld/staging/call.sql Staging wrapper for call, including duration parsing.
zoho-crm/weld/staging/contact.sql Staging wrapper for contact, including full_name.
zoho-crm/weld/staging/deal.sql Staging wrapper for deal, including account_id rename.
zoho-crm/weld/staging/event.sql Staging wrapper for event, including computed duration.
zoho-crm/weld/staging/lead.sql Staging wrapper for lead, including full_name.
zoho-crm/weld/staging/note.sql Staging wrapper for note, preserving parent_id.
zoho-crm/weld/staging/task.sql Staging wrapper for task, derives is_completed.
zoho-crm/weld/staging/user.sql Staging wrapper for user, derives is_active.
zoho-crm/weld/core/deal_pipeline.sql Core deal-grain pipeline model with stage classification + joins.
zoho-crm/weld/core/deal_flow_by_month.sql Core month × owner flow metrics with month spine.
zoho-crm/weld/core/rep_activity.sql Core day × owner activity rollup (calls/events/tasks).
zoho-crm/weld/core/account_360.sql Core account-grain “360” view combining people/pipeline/notes.
zoho-crm/weld/analytics/deal_pipeline.sql Analytics contract passthrough for deal pipeline.
zoho-crm/weld/analytics/deal_flow_by_month.sql Analytics contract passthrough for monthly flow.
zoho-crm/weld/analytics/rep_activity.sql Analytics contract passthrough for rep activity.
zoho-crm/weld/analytics/account_360.sql Analytics contract passthrough for account 360.
zoho-crm/dbt/README.md Explains dbt drop-in layout and key usage notes.
zoho-crm/dbt/models/staging/sources.yml Defines Zoho CRM source tables and guidance for schema/orgs.
zoho-crm/dbt/models/staging/stg_zoho_crm__account.sql dbt staging wrapper for account.
zoho-crm/dbt/models/staging/stg_zoho_crm__call.sql dbt staging wrapper for call with duration parsing.
zoho-crm/dbt/models/staging/stg_zoho_crm__contact.sql dbt staging wrapper for contact with full_name.
zoho-crm/dbt/models/staging/stg_zoho_crm__deal.sql dbt staging wrapper for deal including account_id rename.
zoho-crm/dbt/models/staging/stg_zoho_crm__event.sql dbt staging wrapper for event with computed duration.
zoho-crm/dbt/models/staging/stg_zoho_crm__lead.sql dbt staging wrapper for lead with full_name.
zoho-crm/dbt/models/staging/stg_zoho_crm__note.sql dbt staging wrapper for note preserving parent_id.
zoho-crm/dbt/models/staging/stg_zoho_crm__task.sql dbt staging wrapper for task with is_completed.
zoho-crm/dbt/models/staging/stg_zoho_crm__user.sql dbt staging wrapper for user with is_active.
zoho-crm/dbt/models/core/core_zoho_crm__deal_pipeline.sql dbt core deal pipeline (materialized table) with stage classification.
zoho-crm/dbt/models/core/core_zoho_crm__deal_flow_by_month.sql dbt core monthly flow with month spine.
zoho-crm/dbt/models/core/core_zoho_crm__rep_activity.sql dbt core rep activity rollup with date spine.
zoho-crm/dbt/models/core/core_zoho_crm__account_360.sql dbt core account 360 view with notes/pipeline/contacts.
zoho-crm/dbt/models/analytics/analytics__zoho_crm_deal_pipeline.sql dbt analytics contract view for deal pipeline.
zoho-crm/dbt/models/analytics/analytics__zoho_crm_deal_flow_by_month.sql dbt analytics contract view for monthly flow.
zoho-crm/dbt/models/analytics/analytics__zoho_crm_rep_activity.sql dbt analytics contract view for rep activity.
zoho-crm/dbt/models/analytics/analytics__zoho_crm_account_360.sql dbt analytics contract view for account 360.
zoho-crm/dbt/tests/assert_owner_ids_resolve.sql Test surfacing deals with unresolved owner_id.
zoho-crm/dbt/tests/assert_no_duplicate_deals.sql Test guarding against duplicate deals per org.
zoho-crm/dbt/tests/assert_deal_stages_are_classified.sql Test listing stages that fall through to Open.
zoho-crm/dbt/tests/assert_call_duration_parses.sql Test listing call durations that fail parsing.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +66 to +73
date_spine AS (
SELECT day AS activity_date
FROM UNNEST(GENERATE_DATE_ARRAY(
(SELECT MIN(activity_date) FROM rolled_up),
CURRENT_DATE(),
INTERVAL 1 DAY
)) AS day
),
Comment on lines +66 to +73
date_spine AS (
SELECT day AS activity_date
FROM UNNEST(GENERATE_DATE_ARRAY(
(SELECT MIN(activity_date) FROM rolled_up),
CURRENT_DATE(),
INTERVAL 1 DAY
)) AS day
),
Comment on lines +24 to +35
WITH month_spine AS (
SELECT month
FROM UNNEST(GENERATE_DATE_ARRAY(
-- Start the spine at the first deal rather than a hardcoded date, so the
-- series never carries years of empty leading months.
(SELECT DATE_TRUNC(MIN(created_date), MONTH) FROM {{core.zoho_crm.deal_pipeline}}),
-- End a year out: closing_date is a forecast, so pipeline legitimately
-- sits in the future and must not be truncated away.
DATE_TRUNC(DATE_ADD(CURRENT_DATE(), INTERVAL 1 YEAR), MONTH),
INTERVAL 1 MONTH
)) AS month
),
Comment on lines +24 to +35
WITH month_spine AS (
SELECT month
FROM UNNEST(GENERATE_DATE_ARRAY(
-- Start the spine at the first deal rather than a hardcoded date, so the
-- series never carries years of empty leading months.
(SELECT DATE_TRUNC(MIN(created_date), MONTH) FROM {{ ref('core_zoho_crm__deal_pipeline') }}),
-- End a year out: closing_date is a forecast, so pipeline legitimately
-- sits in the future and must not be truncated away.
DATE_TRUNC(DATE_ADD(CURRENT_DATE(), INTERVAL 1 YEAR), MONTH),
INTERVAL 1 MONTH
)) AS month
),
Comment on lines +67 to +73
-- Open deals whose expected close date has already passed: the cheapest
-- pipeline-hygiene number there is, and usually the first thing a sales lead
-- asks for.
d.stage_status = 'Open' AND d.closing_date < CURRENT_DATE() AS is_overdue,
CASE
WHEN d.stage_status = 'Open'
THEN DATE_DIFF(d.closing_date, CURRENT_DATE(), DAY)
Comment on lines +67 to +73
DAY
) AS age_days,
-- Open deals whose expected close date has already passed: the cheapest
-- pipeline-hygiene number there is, and usually the first thing a sales lead
-- asks for.
d.stage_status = 'Open' AND d.closing_date < CURRENT_DATE() AS is_overdue,
CASE
Four core models over Weld's Zoho CRM connector, in both Weld and dbt dialects:
deal pipeline (deal grain, stage classified), deal flow by month, rep activity
and account 360.

The connector's schema constrains what is buildable, and the models say so
rather than papering over it:

- Calls, tasks and events lose Zoho's What_Id / Who_Id, so activity is reported
  per rep and never per deal. Notes keep Parent_Id, which is why account_360 can
  report a last-touch date at all.
- No is_won / is_closed flag, so won and lost are read out of the stage string.
  assert_deal_stages_are_classified surfaces custom closed stages that would
  otherwise sit in open pipeline.
- No history tables and no stage-change audit, so nothing can reconstruct a past
  pipeline; the README points at materialising deal_pipeline daily instead.
- call_duration is a string that Zoho documents as hh:mm and its UI renders as
  mm:ss, with no seconds column to settle it. Parsed as documented, flagged as
  unverified.

All 38 files parse as BigQuery, and every raw column referenced was checked
against the connector's published ERD.
@jthordal
jthordal force-pushed the zoho-crm-templates branch from 3343e4d to b473467 Compare August 27, 2026 12:26
Addresses Copilot review on #2.

closing_date is nullable, and NULL < CURRENT_DATE() is NULL rather than
FALSE, so is_overdue was tri-valued: a downstream WHERE NOT is_overdue
silently dropped every open deal with no expected close date. Wrapped in
COALESCE(..., FALSE) — no close date means not overdue.

Same defect class in account_360.is_stale_with_open_pipeline, which went
NULL when an account had neither a note nor a created_time. Fixed the same
way rather than left inconsistent.

Also reworded the rep_activity header note that named the raw column
parent_id_id while the surrounding model talks in staging names; it now
says parent_id_id in raw, parent_id after staging.

Verified against BigQuery: the old expression returns NULL for an open
deal with a null closing_date, the new one returns FALSE, and the three
other cases are unchanged.

NOT changed — the six review comments about GENERATE_DATE_ARRAY erroring on
a NULL start date. BigQuery returns NULL for a NULL argument rather than
raising, and UNNEST(NULL) yields zero rows, so an empty source already
produces an empty result set. Confirmed by query:
GENERATE_DATE_ARRAY(NULL, CURRENT_DATE(), INTERVAL 1 DAY) IS NULL -> true,
and the UNNEST row count is 0. The spines are correct as written.
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.

3 participants