Dans les articles précédents, on a vu comment déposer des fichiers sur un stage, définir un file format, puis charger les données en table avec COPY.
Ça marche très bien quand tes fichiers ressemblent à une table.

Sauf que, dans la vraie vie, tu récupères souvent :

  • Du semi-structuré (JSON, Parquet, XML etc ....) donc il y a une structure, mais elle varie.
  • Du non structuré (PDF, images, audio etc ...).

Snowflake gère les deux, mais pas avec la même logique.

1) Données semi-structurées

Le type VARIANT est la porte d’entrée la plus simple, car tu charges ton JSON (et plus largement du semi-structuré comme Parquet, XML etc...), "tel quel" puis tu utilises SQL pour lire les données.

Ce qui est intéressant, c'est que Snowflake ne stocke pas tout ça comme un gros blob illisible mais quand tu insères du semi-structuré dans une colonne VARIANT, Snowflake applique des règles de subcolumnarization pour extraire un maximum d'éléments afin que ça reste requêtable efficacement. Le reste est conservé dans une structure semi-structurée parsée.
Mais sans s’en douter, il y a des limites car par défaut, Snowflake extrait au maximum 200 éléments par partition (micro-partition), par table. Si tu veux augmenter cette limite, ça passe par le support Snowflake.

Exemple concret : charger du JSON dans une table

Comme d'habitude rien de mieux qu'un exemple pour comprendre le concept.

On part d'un stage, et d’un file format JSON. Puis on charge les données dans une table avec une colonne VARIANT.

CREATE OR REPLACE FILE FORMAT FF_JSON
  TYPE = JSON;

CREATE OR REPLACE STAGE STG_EVENTS
  FILE_FORMAT = FF_JSON;

(Ensuite on import le fichier events.json dans @STG_EVENTS avec un PUT)

{"event_id":"evt_0001","event_time":"2026-01-12T10:14:22Z","event_type":"order_created","customer":{"id":"c_1029","country":"FR"},"order":{"order_id":123,"channel":"web","currency":"EUR"},"amount":"129.90","items":[{"sku":"A1","qty":2,"unit_price":"39.95"},{"sku":"B7","qty":1,"unit_price":"49.00"}],"metadata":{"source":"app","ip":"192.0.2.10"}}
{"event_id":"evt_0002","event_time":"2026-01-12T10:18:01Z","event_type":"order_paid","customer":{"id":"c_1029","country":"FR"},"order":{"order_id":123,"payment_method":"card","currency":"EUR"},"amount":129.90,"items":[{"sku":"A1","qty":2,"unit_price":39.95},{"sku":"B7","qty":1,"unit_price":49.00}],"metadata":{"source":"web"}}
{"event_id":"evt_0003","event_time":"2026-01-12T11:02:44Z","event_type":"order_shipped","customer":{"id":"c_2044","country":"BE"},"order":{"order_id":124,"carrier":"DHL","tracking":"JD000225566","currency":"EUR"},"items":[{"sku":"C3","qty":1,"unit_price":"19.99"}],"metadata":{"source":"ops","warehouse":"WH-2"}}
{"event_id":"evt_0004","event_time":"2026-01-12T12:30:05Z","event_type":"order_created","customer":{"id":"c_7777","country":"FR"},"order":{"order_id":125,"channel":"mobile","currency":"EUR"},"amount":"59.00","items":[],"metadata":{"source":"app"}}
{"event_id":"evt_0005","event_time":"2026-01-12T13:05:11Z","event_type":"order_created","customer":{"id":"c_8888","country":"ES"},"order":{"order_id":126,"channel":"web","currency":"EUR"},"amount":"not_a_number","items":[{"sku":"Z9","qty":"1","unit_price":"9.99"}],"metadata":{"source":"web","note":"bad amount/qty types"}}

C'est possible de l'importer avec snowsight

Snowsight : importer un fichier dans un stage
Snowsight : importer un fichier dans un stage

On garde le payload brut

CREATE OR REPLACE TABLE RAW_EVENTS (
  payload VARIANT,
  load_ts TIMESTAMP_NTZ DEFAULT CURRENT_TIMESTAMP()
);

On charge le JSON dans VARIANT

COPY INTO RAW_EVENTS(payload)
FROM (SELECT $1 FROM @STG_EVENTS)
FILE_FORMAT = (FORMAT_NAME = 'FF_JSON');

C'est la magique du variant à ce stade on ne va pas s'occuper des types du Schéma.

VARIANT FIELD
VARIANT FIELD

Lire un champ dans un VARIANT

Tu accèdes aux champs avec la notation JSON (souvent :), puis un cast avec le type du champ

SELECT
  payload:event_id::STRING            AS event_id,
  payload:event_time::TIMESTAMP_NTZ   AS event_time,
  payload:customer:id::STRING         AS customer_id,
  payload:event_type::STRING        AS event_type
FROM RAW_EVENTS;
Lire un champ dans un VARIANT
Lire un champ dans un VARIANT

Comme on voit dans la capture ci-dessus on tombe souvent sur des champs manquants ou mal typés et l'utilisation de TRY_ peut être pratique dans certain cas :

SELECT
  payload:event_id::STRING                         AS event_id,
  TRY_TO_TIMESTAMP_NTZ(payload:event_time::STRING) AS event_time,
  payload:customer:id::STRING                      AS customer_id,
  TRY_TO_NUMBER(payload:amount::STRING)            AS amount
FROM RAW_EVENTS;
L'utilisation de TRY_
L'utilisation de TRY_

Quand tu as des structures imbriquées, tu continues à descendre pareil. Et si tu as des tableaux (arrays), on peut utiliser la fonction FLATTEN

{
  ........
  "order": {
    "order_id": 123,
    "channel": "web",
    "currency": "EUR"
  },
  "amount": "129.90",
  "items": [
    {
      "sku": "A1",
      "qty": 2,
      "unit_price": "39.95"
    },
    {
      "sku": "B7",
      "qty": 1,
      "unit_price": "49.00"
    }
  ],
  .......
}

Pour récupérer les items

SELECT
  e.payload:order:order_id::NUMBER AS order_id,
  f.value:sku::STRING        AS sku,
  f.value:qty::NUMBER        AS qty
FROM RAW_EVENTS e,
LATERAL FLATTEN(input => e.payload:items) f;

FLATTEN explose tout simplement un ARRAY/OBJECT/VARIANT en plusieurs lignes

FLATTEN fonction Snowflake
FLATTEN fonction Snowflake

2) Données non structurées

Non structuré = PDF, images, audio.. donc ici, l'objectif n'est pas de transformer les fichiers en lignes tout de suite. L'objectif, c'est :

  1. Stocker les fichiers dans un stage
  2. Avoir un catalogue de ces fichiers
  3. Générer des URLs sécurisées pour y accéder (et éventuellement partager ces URLs)
  4. Utiliser ces fichiers en data science ou en IA (je vais dédier un article à ce sujet prochainement 😉)

C’est exactement le rôle des directory tables sur un stage.

Exemple concret : un stage avec directory table

CREATE OR REPLACE STAGE docs_stage
  DIRECTORY = (ENABLE = TRUE);

On ajoute des fichiers dedans avec un put ou via l'interface puis on va scanner le fichier du stage

ALTER STAGE DOCS_STAGE REFRESH;

SELECT *
FROM DIRECTORY(@DOCS_STAGE);
DIRECTORY
DIRECTORY

Générer une URL sécurisée vers un fichier

Tu as trois fonctions qui reviennent souvent :

  • BUILD_SCOPED_FILE_URL : URL scopée (valable pour l’appelant, sur une durée liée au cache des résultats ; doc indique ~24h).
  • GET_PRESIGNED_URL : URL pré-signée avec une expiration en secondes que tu choisis (pratique pour donner un accès très court).
  • BUILD_STAGE_FILE_URL : génère une URL de fichier (usage via API fichier).

Exemple :

CREATE OR REPLACE SECURE VIEW V_DOCS_URLS AS
SELECT
  relative_path,
  GET_PRESIGNED_URL(@DOCS_STAGE, relative_path, 60) AS url_demo_60s
FROM DIRECTORY(@DOCS_STAGE);

Select relative_path, url_demo_60s from V_DOCS_URLS;

GET_PRESIGNED_URL
GET_PRESIGNED_URL

Aller plus loin : Formation Snowflake

J’ai regroupé tous mes articles Snowflake dans un parcours complet.

👉 Accéder à la Formation Snowflake

Vous voulez que je vous accompagne sur votre projet data (Snowflake, ingestion, modélisation, performance, coûts, gouvernance) ?
👉 Réserver un appel de 30 minutes