Étape IV : Exemple de pipeline complet

On assemble les quatre étapes précédentes dans un scénario réaliste de réponse à la demande :

« On nous demande la valeur moyenne d’un indicateur par région, pour l’année 2024. La donnée source vit dans PostgreSQL. Le livrable doit être un fichier Parquet déposé sur S3. »

Le script ci-dessous reprend, dans l’ordre :

  1. connexion à DuckDB ;
  2. connexion à PostgreSQL et export en Parquet ;
  3. manipulation des données (ici en dplyr, mais le SQL natif fonctionnerait aussi bien) ;
  4. export du résultat final sur S3 ;
  5. fermeture de la connexion.
library(DBI)
library(duckdb)
library(dplyr)
library(glue)

# --- Étape 0 : connexion -----------------------------------------------

con <- DBI::dbConnect(duckdb::duckdb())

# --- Configuration S3 (nécessaire pour les étapes I et III) ------------

DBI::dbExecute(con, "INSTALL httpfs; LOAD httpfs;")

DBI::dbExecute(con, glue::glue("
  CREATE OR REPLACE SECRET secret_s3 (
    TYPE s3,
    PROVIDER credential_chain,
    ENDPOINT '{Sys.getenv(\"AWS_S3_ENDPOINT\")}',
    REGION '{Sys.getenv(\"AWS_DEFAULT_REGION\")}',
    URL_STYLE 'path'
  );
"))

# --- Étape I : chargement (PostgreSQL -> Parquet) -----------------------

DBI::dbExecute(con, "INSTALL postgres; LOAD postgres;")

# Le mot de passe est lu depuis une variable d'environnement car ce script
# est pensé pour tourner sans interaction (en session interactive, on peut
# le remplacer par rstudioapi::askForPassword())
DBI::dbExecute(con, glue::glue("
  CREATE OR REPLACE SECRET secret_pg (
    TYPE postgres,
    HOST '{Sys.getenv(\"PG_HOST\")}',
    PORT {Sys.getenv(\"PG_PORT\")},
    DATABASE '{Sys.getenv(\"PG_DB\")}',
    USER '{Sys.getenv(\"PG_USER\")}',
    PASSWORD '{Sys.getenv(\"PG_PASSWORD\")}'
  );
  ATTACH '' AS pg (TYPE postgres, SECRET secret_pg, READ_ONLY);
"))

# On extrait la table source vers un Parquet de travail sur S3
DBI::dbExecute(con, "
  COPY pg.ma_table
  TO 's3://mon-bucket/mon-etude/brut/ma_table.parquet'
  (FORMAT parquet)
")

# On la relit pour la suite du pipeline (vue paresseuse, rien n'est chargé en mémoire)
DBI::dbExecute(con, "
  CREATE OR REPLACE VIEW ma_table AS
  SELECT * FROM 's3://mon-bucket/mon-etude/brut/ma_table.parquet'
")

# PostgreSQL n'est plus nécessaire pour la suite : on ferme la connexion
DBI::dbExecute(con, "DETACH pg")

# --- Étape II : manipulation (ici en dplyr) -----------------------------

resultat <- dplyr::tbl(con, "ma_table") |>
  dplyr::filter(annee == 2024) |>
  dplyr::group_by(region) |>
  dplyr::summarise(valeur_moyenne = mean(valeur, na.rm = TRUE)) |>
  dplyr::collect()

# On enregistre le résultat comme vue DuckDB pour pouvoir l'exporter en SQL
# (duckdb_register ne copie pas les données, contrairement à dbWriteTable)
duckdb::duckdb_register(con, "resultat_final", resultat)

# --- Étape III : export du livrable -------------------------------------

DBI::dbExecute(con, "
  COPY resultat_final
  TO 's3://mon-bucket/mon-etude/livrable/resultat_regions_2024.parquet'
  (FORMAT parquet)
")

duckdb::duckdb_unregister(con, "resultat_final")

# --- Fermeture -----------------------------------------------------------

DBI::dbDisconnect(con, shutdown = TRUE)
Note

Ce script mélange volontairement dplyr (étape II) et SQL natif (étapes I et III) pour montrer que les deux cohabitent sans problème dans un même pipeline : c’est toujours DuckDB qui exécute le calcul.

Astuce

Si toute la manipulation avait été écrite en SQL natif, l’étape II et l’enregistrement intermédiaire via duckdb::duckdb_register() auraient pu être remplacés par une seule requête COPY (SELECT ...) TO ..., comme montré à l’étape III.

Pour aller plus loin

Retour au sommet