ServicesÀ proposNotesContact Me contacter →
EN FR
Note

Modèles de données Salesforce vs HubSpot

Comment Salesforce et HubSpot structurent les données CRM différemment — modèles relationnels orientés métadonnées vs. associations many-to-many — et ce que cela implique pour la modélisation en entrepôt.

Planté
dbtbigquerydata modelingdata engineering

Salesforce et HubSpot adoptent des approches fondamentalement différentes pour la structure des données. Ces différences façonnent les modèles de base, les patterns de jointure, la logique d’enrichissement intermédiaire et la conception du schéma.

Salesforce : orienté métadonnées, relationnel

Salesforce utilise une architecture multi-tenant orientée métadonnées. En coulisses, toutes les données clients vivent dans des tables partagées, avec des métadonnées définissant le schéma pour chaque tenant (org). Mais du point de vue de la modélisation des données, ce qui compte est que les données Salesforce sont intrinsèquement relationnelles avec des clés étrangères bien définies.

Objets standards

Les objets standards suivent des schémas prévisibles et cohérents dans chaque org Salesforce :

  • Account — Entreprises ou organisations
  • Contact — Personnes associées aux comptes
  • Lead — Prospects pas encore associés aux comptes
  • Opportunity — Deals dans le pipeline
  • Case — Tickets de support
  • Campaign — Campagnes marketing et leurs membres

Chaque objet se mappe proprement vers une table brute dans votre entrepôt, puis vers un modèle de base, puis vers un ou plusieurs modèles intermédiaires ou mart.

Objets personnalisés

Les objets personnalisés portent un suffixe __c : Territory__c, Subscription__c, Product_Usage__c. Leurs champs portent également le suffixe : annual_revenue__c, renewal_date__c. Ce suffixe est préservé lors de l’extraction dans votre entrepôt, donc vos modèles de base doivent gérer leur renommage selon les conventions de nommage de votre projet.

-- base__salesforce__territory.sql
SELECT
id AS territory__id,
name AS territory__name,
region__c AS territory__region,
target_revenue__c AS territory__target_revenue,
owner_id AS territory__owner_id
FROM {{ source('salesforce', 'territory__c') }}
WHERE NOT _fivetran_deleted

Types de relations

Les relations Salesforce se déclinent en deux variantes :

Les relations Lookup créent un couplage lâche avec une clé étrangère nullable. Un Contact a un lookup vers Account via account_id, mais le Contact peut exister sans Account. Dans votre entrepôt, cela implique des LEFT JOINs — tous les Contacts n’auront pas de données Account correspondantes.

Les relations Master-Detail créent un couplage fort avec des suppressions en cascade. Si l’enregistrement maître est supprimé, tous les enregistrements de détail le sont également. La clé étrangère est obligatoire (non-nullable). Dans votre entrepôt, cela signifie que les INNER JOINs sont sûrs — chaque enregistrement de détail a un maître.

L’implication pratique : lors de l’écriture de vos modèles intermédiaires, vérifiez si une relation Salesforce est Lookup ou Master-Detail. Cela détermine si vous utilisez LEFT JOIN ou INNER JOIN, et si vous devez gérer les NULL dans la colonne de clé étrangère.

Relations polymorphiques

Salesforce a des champs polymorphiques comme WhoId sur Task et Event, qui peuvent référencer soit un Contact soit un Lead. Et WhatId, qui peut référencer un Account, une Opportunity ou un objet personnalisé. Ces champs nécessitent un routage basé sur CASE dans votre couche intermédiaire :

CASE
WHEN who_id LIKE '003%' THEN 'Contact'
WHEN who_id LIKE '00Q%' THEN 'Lead'
END AS who_type

Les préfixes d’ID Salesforce sont déterministes — 003 est toujours Contact, 00Q est toujours Lead, 001 est toujours Account, 006 est toujours Opportunity. Ce routage basé sur les préfixes est fiable mais non évident si vous n’avez pas travaillé avec Salesforce auparavant.

HubSpot : basé sur les associations, many-to-many

HubSpot adopte une approche fondamentalement différente. Au lieu de clés étrangères sur les objets eux-mêmes, HubSpot utilise un système d’associations séparé pour connecter les objets.

Objets de base

Les objets de base de HubSpot sont plus simples que les objets standards Salesforce :

  • Contacts — Personnes
  • Companies — Organisations
  • Deals — Opportunités commerciales
  • Tickets — Demandes de support

Les propriétés sont préfixées par property_ dans les données extraites : property_email, property_firstname, property_lifecyclestage. Vos modèles de base les renomment :

-- base__hubspot__contact.sql
SELECT
id AS contact__id,
property_email AS contact__email,
property_firstname AS contact__first_name,
property_lastname AS contact__last_name,
property_lifecyclestage AS contact__lifecycle_stage,
property_hs_lead_status AS contact__lead_status,
property_createdate AS contact__created_at
FROM {{ source('hubspot', 'contact') }}
WHERE NOT _fivetran_deleted

Le modèle d’associations

C’est là que HubSpot diverge nettement de Salesforce. Dans Salesforce, un Contact appartient à un Account (via account_id). Dans HubSpot, un Contact peut être associé à plusieurs Companies, et un Deal peut impliquer plusieurs Contacts dans des rôles différents.

Les associations vivent dans des tables de liaison séparées :

  • contact_company — Lie les contacts aux entreprises (many-to-many)
  • deal_contact — Lie les deals aux contacts (many-to-many)
  • deal_company — Lie les deals aux entreprises (many-to-many)

Chaque association peut porter un label optionnel comme « Decision Maker », « Billing Contact » ou « Primary Company ». Ces labels ajoutent une signification sémantique à la relation mais compliquent votre logique de jointure.

En pratique, cela signifie que vos modèles intermédiaires nécessitent des jointures explicites sur les tables de liaison :

-- int__deal_contact_enriched.sql
WITH
deals AS (
SELECT deal__id, deal__name, deal__amount, deal__stage
FROM {{ ref('base__hubspot__deal') }}
),
contacts AS (
SELECT contact__id, contact__email, contact__first_name
FROM {{ ref('base__hubspot__contact') }}
),
associations AS (
SELECT
deal_id AS deal__id,
contact_id AS contact__id,
label AS association__label
FROM {{ ref('base__hubspot__deal_contact') }}
)
SELECT
deals.deal__id,
deals.deal__name,
deals.deal__amount,
deals.deal__stage,
contacts.contact__id,
contacts.contact__email,
contacts.contact__first_name,
associations.association__label
FROM deals
INNER JOIN associations ON deals.deal__id = associations.deal__id
INNER JOIN contacts ON associations.contact__id = contacts.contact__id

La sortie est une ligne par paire deal-contact, pas une ligne par deal. C’est une granularité différente de celle que vous obtiendriez d’un modèle d’opportunités Salesforce, et la logique en aval doit en tenir compte.

Pattern d’association primaire

Quand vous avez besoin d’une seule entreprise par contact (pour correspondre à un modèle similaire à Salesforce), vous devez choisir une association « primaire ». Approches courantes :

  • Filtrer sur un label spécifique (par exemple, WHERE association__label = 'Primary')
  • Prendre l’association la plus ancienne par date de création
  • Prendre l’entreprise avec le plus de contacts associés (un proxy pour l’entreprise « principale »)

Aucune n’est parfaite. Le modèle de données de HubSpot supporte genuinement le many-to-many, donc le réduire à one-to-one implique toujours une décision métier sur quelle association prend la priorité.

Implications de modélisation

Les différences structurelles ont des conséquences directes pour votre projet dbt :

PréoccupationSalesforceHubSpot
Pattern de jointureJointures directes sur clé étrangèreJointures sur tables de liaison
Gestion de la granularitéDirecte — la FK préserve la granularitéRisque de fan-out du many-to-many
Nombre de modèles de baseUn par objetUn par objet + un par table de liaison
Relation primaireIntégrée (FK sur l’enfant)Doit être dérivée des associations
Champs personnalisésSuffixe __c, renommage dans la basePréfixe property_, renommage dans la base

La modélisation Salesforce finit par concerner principalement la gestion de la mutabilité, de la hiérarchie (Account → Contact → Opportunity) et des particularités d’extraction comme les champs de formule. La modélisation HubSpot concerne la résolution correcte des relations many-to-many et la décision sur comment aplatir les associations pour le reporting.

Les deux systèmes bénéficient de la même architecture dbt en trois couches, mais la couche intermédiaire fait un travail très différent pour chacun. Pour Salesforce, l’intermédiaire enrichit les entités avec des données liées. Pour HubSpot, l’intermédiaire résout la complexité des associations en jointures interrogeables.