> ## Content Index
> Fetch the complete content index at: https://www.idriss-benbassou.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Les snapshots dbt : garder l'historique avec le SCD Type 2
- URL: https://www.idriss-benbassou.com/dbt-snapshots-scd-type-2/
- Published: 2026-06-07T08:00:54.000Z
- Updated: 2026-06-07T08:00:54.000Z
- Author: Idriss BENBASSOU
- Tags: dbt, #rating-2

Dans [l'article précédent](https://www.idriss-benbassou.com/dbt-incremental-is-incremental-unique-key/), on a vu le chargement incremental, comment charger efficacement les nouvelles lignes, et mettre à jour les anciennes. Mais cette mise à jour a un défaut pour certains besoins : **elle écrase l'ancienne valeur**.

Un client déménage de Paris à Lyon. Avec un upsert classique, sa ligne passe à Lyon et Paris disparaît à jamais. Or parfois, savoir qu'il habitait Paris jusqu'au 15 mars compte vraiment (analyses historiques, conformité, reporting daté etc...). C'est le rôle du SCD Type 2, et dbt l'implémente nativement avec les snapshots. La théorie du SCD est dans mon [article dédié](https://www.idriss-benbassou.com/scd-slowly-changing-dimensions-type-1-2-3-6-sql-dbt-snowflake/) , donc ici, on l'implémente sans écrire une ligne de SQL d'historisation.

## Ce qu'est un snapshot

Un snapshot, c'est une photo de l'état d'une table prise à chaque exécution. dbt compare la photo du jour à la précédente, détecte les lignes qui ont changé, expire l'ancienne version et insère la nouvelle.

Pour gérer ça, dbt ajoute quatre colonnes techniques à la table de snapshot :

- `dbt_valid_from` : depuis quand cette version est valide
- `dbt_valid_to` : jusqu'à quand. La version courante a un `dbt_valid_to` à `NULL`
- `dbt_scd_id` : un identifiant unique par version
- `dbt_updated_at` : la date et time de la capture

Avec ces colonnes, tu as un historique complet, donc chaque ligne du passé est conservée, et la ligne active est celle dont `dbt_valid_to` est `NULL`.

## Les deux stratégies de détection

dbt propose deux façons de repérer qu'une ligne a changé :

- **Timestamp** : tu indiques une colonne qui change à chaque modification (un `updated_at` par exemple). dbt compare cette date. C'est la stratégie la plus performante, à utiliser dès que la source a une date d'update fiable.
- **Check** : tu listes les colonnes à surveiller (`check_cols`). dbt compare leurs valeurs d'une exécution à l'autre. À utiliser quand la source n'a pas d'`updated_at` fiable.

Notre `raw.customers` n'a pas de colonne de mise à jour propre, donc on part sur la stratégie **check** mais le principe reste le même avec le timestamp.

## Notre premier snapshot

Crée un fichier `snapshots/customers_snapshot.yml` :

```yaml
snapshots:
  - name: customers_snapshot
    relation: source('raw', 'customers')
    config:
      unique_key: id
      strategy: check
      check_cols: ['first_name', 'last_name', 'email']

```

L'idée est de dire à dbt "surveille la table source `raw.customers`, identifie chaque client par `id`, et considère qu'une ligne a changé si `first_name`, `last_name` ou `email` change".

💡

Si tu as vu ailleurs des snapshots écrits dans un fichier `.sql` avec un bloc `{% snapshot %}` et une `config()` Jinja, c'est l'ancienne méthode. Depuis dbt 1.9, elle est considérée comme legacy. La bonne façon aujourd'hui, c'est le fichier YAML avec la clé `snapshots:` et le champ `relation`, comme ci-dessus.

On remarque aussi qu'on snapshote la source brute (`source('raw', 'customers')`), pas un modèle de staging. C'est la bonne pratique car on historise la donnée au plus près de son arrivée, avant toute transformation.

💡

En projet réel, on peut aussi snapshotter un modèle propre, par exemple un modèle de staging, si la source brute est difficile à exploiter directement.

Lance la capture :

```powershell
dbt snapshot

```

dbt crée la table `customers_snapshot` avec tes trois clients et les quatre colonnes techniques. Va voir dans Snowsight : chaque ligne a un `dbt_valid_from` rempli et un `dbt_valid_to` à `NULL`, puisque ce sont les versions courantes.

![](https://storage.ghost.io/c/60/f1/60f18be4-df79-4956-8e4a-c2fa80212e93/content/images/2026/06/image-4.png)

snapshot dbt

💡

Si ta table source avait une colonne mise à jour à chaque modification (un vrai `updated_at`), tu utiliserais la stratégie `timestamp` à la place de `check`. Tu remplaces alors `check_cols` par `updated_at`, qui indique la colonne à surveiller et la strategy 'check' par 'timestamp'.

## Voir l'historique se créer

C'est maintenant que la magie opère. Simule un changement, par exemple le client 1 change d'email.

```sql
UPDATE raw.customers
SET email = 'marc.dubois@nouvelle-adresse.fr'
WHERE id = 1;

```

Relance la capture :

```powershell
dbt snapshot

```

Va interroger la table de snapshot pour le client 1 :

```sql
select id, email, dbt_valid_from, dbt_valid_to
from analytics.dev.customers_snapshot
where id = 1
order by dbt_valid_from;

```

Tu as maintenant deux lignes pour le client 1 :

![](https://storage.ghost.io/c/60/f1/60f18be4-df79-4956-8e4a-c2fa80212e93/content/images/2026/06/image-5.png)

- L'ancienne, avec l'ancien email, dont le `dbt_valid_to` est désormais rempli (la version est expirée),
- La nouvelle, avec le nouvel email, dont le `dbt_valid_to` est à `NULL` (la version courante).

Voilà ton SCD Type 2\. Tu n'as écrit aucune logique d'expiration ni d'insertion, dbt a tout fait à partir de ton YAML. Et tu peux maintenant répondre par exemple à "quel était l'email de ce client avant le 15 mars ?".

💡

Attention : avec la stratégie `check`, les dates `dbt_valid_from` et `dbt_valid_to` correspondent au moment où dbt observe le changement, pas forcément à la vraie date métier du changement.

## Gérer les suppressions

Une question revient vite, c'est que se passe-t-il si une ligne disparaît de la source ? Par défaut, dbt la laisse telle quelle dans le snapshot, avec son `dbt_valid_to` toujours à `NULL`, comme si elle était encore active. Si tu veux qu'une disparition soit traitée comme une fin de validité, ajoute la config `hard_deletes` :

```yaml
    config:
      unique_key: id
      strategy: check
      check_cols: ['first_name', 'last_name', 'email']
      hard_deletes: invalidate

```

Avec `invalidate`, une ligne supprimée de la source voit son `dbt_valid_to` rempli à la prochaine capture. C'est la config moderne qui remplace l'ancien `invalidate_hard_deletes`.

```sql
delete from raw.customers where id=1;

```

Relance :

```powershell
dbt snapshot

```

![](https://storage.ghost.io/c/60/f1/60f18be4-df79-4956-8e4a-c2fa80212e93/content/images/2026/06/image-6.png)

## Sous le capot, et quand l'utiliser

Comme le chargement incremental, un snapshot s'appuie sur un `MERGE` généré par dbt pour expirer et insérer les versions. Si tu veux comprendre la mécanique, c'est le même principe que j'ai détaillé dans l'article [MERGE SQL](https://www.idriss-benbassou.com/merge-sql-insert-update-delete-en-une-requete-snowflake-dbt-bigquery/).

Si tu te poses la question, c'est snapshot ou incrémental ? La réponse c'est ni l'un ni l'autre car ce sont deux besoins différents.

| Besoin                                                                             | Outil              |
| ---------------------------------------------------------------------------------- | ------------------ |
| Charger efficacement de gros volumes qui grossissent par ajout (faits, événements) | Modèle incremental |
| Garder l'historique des changements d'une dimension qui évolue (SCD Type 2)        | snapshot           |

L'incremental optimise le chargement. Le snapshot capture l'histoire. On les utilise souvent ensemble dans un même projet, sur des tables différentes.

Côté exécution, le snapshot a sa propre commande, `dbt snapshot`, mais il est aussi inclus quand tu lances `dbt build`. En prod, on le planifie au bon rythme car trop espacé, tu risques de rater des changements entre deux captures.

## Ce qu'on a fait, et la suite

Récap. de cet article :

- Comprendre pourquoi un upsert classique écrase l'historique, et ce que le SCD Type 2 résout
- Définir un snapshot en YAML avec la stratégie `check` (dbt 1.9+),
- Voir une deuxième version de ligne se créer après un changement, avec ses dates de validité
- Gérer les suppressions avec `hard_deletes`
- Poser la distinction claire entre snapshot (historiser) et incremental (charger).

Tu sais maintenant historiser tes dimensions proprement. Dans le prochain article, on attaque le sujet qui fait passer de quelqu'un qui écrit du SQL dans dbt à un vrai engineer : Jinja et les macros, pour arrêter de te répéter et factoriser ta logique.

---

## Aller plus loin

Cet article fait partie de la formation dbt complète, du premier modèle au déploiement en production.

👉 [Suivre toute la formation dbt](https://www.idriss-benbassou.com/formation-dbt/)

dbt tourne sur Snowflake dans ce parcours. Pour maîtriser le socle (warehouses, rôles, ingestion) :

👉 [Accéder à la Formation Snowflake](https://www.idriss-benbassou.com/formation-snowflake/)

Et pour t'entraîner en conditions d'examen, la certification dbt Analytics Engineering teste précisément les snapshots et le SCD Type 2.

👉 [Préparer la certification dbt sur DataCertification.fr](https://datacertification.fr/certifications/dbt?ref=idriss-benbassou.com)

Tu veux que je t'accompagne sur ton projet data (Snowflake, dbt, modélisation, industrialisation) ?

👉 [Réserver un appel de 30 minutes](https://calendly.com/idriss-benbassou-datavio/30min?ref=idriss-benbassou.com)

---

## Questions fréquentes

#### C'est quoi un snapshot dbt ?

Un snapshot est un mécanisme dbt qui capture l'état d'une table à chaque exécution pour en garder l'historique. Quand une ligne change, dbt expire l'ancienne version et insère la nouvelle, avec des dates de validité. C'est l'implémentation native du SCD Type 2 donc on conserve chaque version d'une donnée au lieu de l'écraser.

#### Quelle différence entre la stratégie timestamp et check ?

La stratégie timestamp s'appuie sur une colonne horodatée fiable (un updated\_at) qui change à chaque modification et donc dbt compare cette date. La stratégie check compare les valeurs des colonnes que tu listes dans check\_cols. On utilise timestamp quand la source a un horodatage fiable, et check quand elle n'en a pas.

#### C'est quoi les colonnes dbt\_valid\_from et dbt\_valid\_to ?

Ce sont deux colonnes que dbt ajoute automatiquement à un snapshot pour gérer l'historique. dbt\_valid\_from indique depuis quand une version de la ligne est valide, et dbt\_valid\_to jusqu'à quand. La version courante d'une ligne a un dbt\_valid\_to à NULL. C'est ce couple de dates qui permet de retrouver l'état d'une donnée à n'importe quelle date passée.

#### Comment définir un snapshot en dbt 1.9 et plus ?

Dans un fichier YAML placé dans le dossier snapshots, avec la clé snapshots, le champ relation (la source ou le modèle à historiser) et un bloc config (unique\_key, strategy, et selon la stratégie updated\_at ou check\_cols). L'ancienne méthode avec un bloc Jinja snapshot dans un fichier .sql est devenue legacy depuis dbt 1.9.

#### Snapshot ou modèle incremental, quand utiliser quoi ?

Un modèle incremental sert à charger efficacement de gros volumes qui grossissent par ajout, comme des faits ou des événements. Un snapshot sert à garder l'historique des changements d'une dimension qui évolue, c'est-à-dire le SCD Type 2\. L'incremental optimise le chargement, le snapshot capture l'histoire. Les deux coexistent souvent dans un même projet sur des tables différentes.