Zoho CRM SQL templates - #2
Open
jthordal wants to merge 2 commits into
Open
Conversation
There was a problem hiding this comment.
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
force-pushed
the
zoho-crm-templates
branch
from
August 27, 2026 12:26
3343e4d to
b473467
Compare
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four core models over Weld's Zoho CRM connector, in both Weld and dbt dialects, following the same four-layer shape as
shopify/.deal_pipelinedeal_flow_by_monthrep_activityaccount_3609 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:What_Id/Who_Id; neither is synced.owner_idis the only foreign key on all three, so activity is reported per rep and never per deal. Notes are the exception —Parent_Idis synced, which is the only reasonaccount_360can report a last-touch date.is_wonoris_closed, so the outcome is read out of the stage string withLIKE '%won%'/'%lost%'— a pattern rather than an equality list, because Zoho's own defaults includeClosed-Lost to Competition.assert_deal_stages_are_classifiedsurfaces custom closed stages that would otherwise sit in open pipeline; this is the one edit almost every org has to make.deal_pipelinedaily instead.call_durationis a string that Zoho's API reference documents ashh:mmwhile the CRM UI rendersmm: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
/templatesand to the connector docs page, alongside a walkthrough post.🤖 Generated with Claude Code