ServicesÀ proposNotesContact Me contacter →
EN FR
Note

Résolution de conflits multi-sources

Trois patterns pour résoudre les données conflictuelles lors de la fusion d'enregistrements provenant de plusieurs systèmes sources — résolution basée sur la priorité, la récence et les champs spécifiques à la source.

Planté
dbtbigquerydata modelingdata engineering

Quand des enregistrements de plusieurs systèmes sources sont fusionnés en une entité unifiée — un modèle Customer 360, un catalogue produits venant de plusieurs fournisseurs, un grand livre de transactions consolidé — des données conflictuelles apparaissent inévitablement. Le titre de poste d’un contact pourrait être « VP Sales » dans Salesforce et « Vice President, Sales » dans HubSpot. Son email pourrait avoir été mis à jour dans le CRM mais pas dans la base de données produit. Son numéro de téléphone pourrait exister dans un système et être null dans un autre.

Une stratégie s’impose pour définir quel système remporte le conflit, et cette stratégie doit être explicite, cohérente et auditable. La résolution implicite des conflits — ce qui se trouve en premier dans une jointure — produit des résultats imprévisibles et une dégradation silencieuse de la qualité des données.

Trois patterns gèrent la grande majorité des cas.

Résolution basée sur la priorité

Sélectionner depuis une liste classée de sources. Le système le plus autoritaire gagne, quel que soit le moment de mise à jour des données.

COALESCE(crm.email, product.email, ga4.email) AS customer__email,
COALESCE(crm.company_name, product.company_name) AS customer__company_name,
COALESCE(crm.phone, product.phone) AS customer__phone

COALESCE retourne la première valeur non-null. L’ordre des arguments définit la priorité : les données CRM l’emportent sur les données produit, qui l’emportent sur les données GA4.

Quand l’utiliser : quand un système est clairement le système de référence pour un champ donné. Le CRM est la source de vérité pour les coordonnées de contact. Le système de facturation est la source de vérité pour les informations de paiement. La base de données produit est la source de vérité pour l’utilisation des fonctionnalités. La priorité doit refléter le processus métier qui maintient chaque champ.

Forces : Simple. Facile à comprendre, à auditer et à déboguer. Quand une partie prenante demande « d’où vient l’email ? », la réponse tient en une ligne : « CRM d’abord, puis produit, puis GA4. »

Faiblesses : Un email CRM exact il y a deux ans l’emporte sur un email produit mis à jour hier. La priorité ignore la récence, ce qui peut poser problème pour les champs qui changent fréquemment.

Priorité par champ

L’ordre de priorité n’a pas à être le même pour chaque champ. En pratique, il devrait rarement l’être :

-- Le CRM fait autorité pour les coordonnées
COALESCE(crm.email, product.email) AS customer__email,
COALESCE(crm.company_name, product.company_name) AS customer__company_name,
-- Le produit fait autorité pour les données d'usage
COALESCE(product.last_login_at, crm.last_activity_at) AS customer__last_active_at,
-- La facturation fait autorité pour les données de paiement
COALESCE(billing.plan_name, product.plan_name) AS customer__plan_name

Documentez ces priorités par champ dans la description YAML du modèle ou dans un bloc de docs dbt. Les futurs mainteneurs (y compris soi-même) doivent comprendre pourquoi le CRM gagne pour l’email mais le produit gagne pour les horodatages d’activité.

Résolution basée sur la récence

Prendre la valeur qui a été mise à jour le plus récemment, quelle que soit la source. L’hypothèse est que la mise à jour la plus récente d’un champ est la plus précise.

CASE
WHEN crm.updated_at >= COALESCE(product.updated_at, TIMESTAMP('1970-01-01'))
THEN crm.job_title
ELSE product.job_title
END AS customer__job_title

Quand l’utiliser : pour les champs où la mise à jour la plus récente est probablement la plus précise — titre de poste, numéro de téléphone, adresse, nom d’entreprise. Ces données évoluent, et le système qui a capturé le changement le plus récemment a probablement raison.

Forces : S’adapte automatiquement. Si un contact met à jour son titre de poste dans les paramètres de profil du produit, il n’est pas nécessaire d’une synchronisation CRM pour récupérer le changement. La valeur la plus récente gagne.

Faiblesses : Nécessite des horodatages updated_at de chaque source, et ces horodatages doivent être fiables. Si un système utilise la date de création de l’enregistrement au lieu de la date de dernière modification, la comparaison de récence n’a aucun sens. Plus complexe aussi que COALESCE, surtout avec trois sources ou plus.

Passage à l’échelle avec plusieurs sources

Pour plus de deux sources, un pattern utilisant ARRAY_AGG avec tri évite les instructions CASE profondément imbriquées :

(
SELECT value
FROM UNNEST([
STRUCT(crm.job_title AS value, crm.updated_at AS updated_at),
STRUCT(product.job_title AS value, product.updated_at AS updated_at),
STRUCT(billing.job_title AS value, billing.updated_at AS updated_at)
])
WHERE value IS NOT NULL
ORDER BY updated_at DESC
LIMIT 1
) AS customer__job_title

C’est une syntaxe spécifique à BigQuery. Sur Snowflake, il faudrait utiliser un lateral flatten ou un CTE avec ROW_NUMBER. Le principe est identique : collecter toutes les valeurs non-null avec leurs horodatages, sélectionner la plus récente.

Champs spécifiques à la source

Contourner entièrement le conflit. Au lieu de choisir un email, les conserver tous :

crm.email AS customer__crm_email,
product.email AS customer__product_email,
ga4.email AS customer__ga4_email

Quand l’utiliser : quand les consommateurs en aval ont besoin de connaître la provenance d’une valeur. Un reverse ETL vers le CRM doit utiliser l’email CRM, pas un email produit qui pourrait différer. Une plateforme d’email marketing pourrait vouloir essayer toutes les adresses connues. Un tableau de bord qualité des données pourrait signaler les enregistrements où les emails divergent.

Forces : Pas de perte d’information. La valeur de chaque source est préservée. Les consommateurs font leur propre choix sur lequel utiliser.

Faiblesses : Élargit la table. Trois colonnes email au lieu d’une. Pour un Customer 360 avec cinq systèmes sources et vingt champs conflictuels, cela produit cent colonnes. Reporte également la décision de résolution au consommateur, ce qui peut mener à une logique en aval incohérente si différentes équipes choisissent des colonnes différentes.

Un hybride pragmatique

En pratique, la plupart des équipes combinent les patterns. Utiliser un champ résolu principal (basé sur la priorité ou la récence) aux côtés des champs spécifiques à la source pour la provenance :

-- Le champ résolu pour un usage général
COALESCE(crm.email, product.email) AS customer__email,
-- Champs sources pour la provenance quand nécessaire
crm.email AS customer__crm_email,
product.email AS customer__product_email

Cela donne aux consommateurs en aval un défaut sensé tout en préservant la capacité à surcharger quand des exigences spécifiques l’imposent.

Choisir un pattern par type de champ

Type de champPattern recommandéRationale
Email, téléphoneBasé sur la prioritéLe système de référence est généralement clair
Titre, entrepriseBasé sur la récenceÉvolue dans le temps ; la valeur la plus récente est généralement correcte
AdresseBasé sur la récenceIdentique au titre
Plan/abonnementBasé sur la priorité (système de facturation)Le système financier fait autorité
Date de dernière activitéBasé sur la récence (la plus récente entre les sources)Utiliser GREATEST() sur tous les horodatages
Métriques d’usageSpécifiques à la sourceDifférents systèmes mesurent des choses différentes
Données financièresBasé sur la priorité (système de facturation)Un seul système financier de référence

Clés de substitution pour les entités multi-sources

Lors de la construction d’entités unifiées depuis plusieurs sources, la clé de substitution doit encoder à la fois le système source et l’ID source :

{{ dbt_utils.generate_surrogate_key(['source_system', 'source_id']) }} AS customer__surrogate_key

Cela évite les collisions entre systèmes qui pourraient réutiliser les mêmes IDs internes (les deux systèmes pourraient avoir un contact avec l’ID 12345). Résolvez les clés de substitution vers un customer_id canonique via le pont d’identité.

Documentation et tests

La logique de résolution de conflits est facile à bien faire initialement et facile à casser lors des refactorisations. Protégez-la avec :

  • Des descriptions YAML sur chaque champ résolu qui documentent l’ordre de priorité ou la stratégie de résolution
  • Des tests dbt qui vérifient que le champ résolu n’est jamais null quand au moins une source a une valeur (détecte les bugs d’ordre COALESCE)
  • Des contrôles qualité des données qui signalent les enregistrements où les sources sont en désaccord, afin de surveiller la fréquence réelle des conflits et si la stratégie de résolution produit des résultats sensés

La stratégie de résolution doit être une décision consciente et documentée — pas un artefact de l’ordre des jointures.