Graphe vs Relationnel¶
Ce document explique pourquoi ddigraph utilise une base de données graphe (Neo4j) plutôt qu'une base de données relationnelle traditionnelle, les avantages que cela apporte pour les métadonnées DDI, et les considérations pour les équipes évaluant cette approche.
Le défi : la complexité inhérente du DDI¶
DDI (Data Documentation Initiative) décrit les instruments d'enquête, les questionnaires, les variables et leurs relations. Cela crée une structure naturellement interconnectée :
- Les questions référencent des listes de codes et des catégories
- Les variables sont liées aux questions, concepts et univers
- Les construits de flux de contrôle (séquences, conditionnels, boucles) forment des chemins d'exécution
- Les instruments contiennent des hiérarchies imbriquées de construits
Une requête DDI typique¶
Considérez cette question : "Quelles questions utilisent une liste de codes particulière, et quelles variables sont dérivées de ces questions ?"
Cela nécessite de traverser :
CodeList → QuestionItem → QuestionConstruct → Variable
Approche base de données relationnelle¶
Conception du schéma relationnel¶
Dans une base de données relationnelle, vous pourriez modéliser cela avec des tables normalisées :
CREATE TABLE code_lists (
id VARCHAR PRIMARY KEY,
agency VARCHAR,
version VARCHAR,
label TEXT
);
CREATE TABLE question_items (
id VARCHAR PRIMARY KEY,
agency VARCHAR,
version VARCHAR,
label TEXT,
question_text TEXT,
code_list_id VARCHAR REFERENCES code_lists(id)
);
CREATE TABLE question_constructs (
id VARCHAR PRIMARY KEY,
question_item_id VARCHAR REFERENCES question_items(id)
);
CREATE TABLE variables (
id VARCHAR PRIMARY KEY,
label TEXT,
question_construct_id VARCHAR REFERENCES question_constructs(id)
);
Le problème des requêtes¶
Trouver toutes les variables utilisant une liste de codes spécifique nécessite plusieurs JOINs :
SELECT v.*
FROM variables v
JOIN question_constructs qc ON v.question_construct_id = qc.id
JOIN question_items qi ON qc.question_item_id = qi.id
WHERE qi.code_list_id = 'cl-age-groups';
Cela semble gérable, mais la complexité du DDI croît rapidement :
- Chemins de profondeur variable : Le flux d'enquête peut s'imbriquer arbitrairement (Sequence -> Sequence -> IfThenElse -> Sequence -> QuestionConstruct)
- Types de relations multiples : Une seule entité peut avoir de nombreuses relations différentes
- Références polymorphes : ControlConstructReference peut pointer vers Sequence, QuestionConstruct, IfThenElse, Loop, etc.
- Préoccupations transversales : Trouver tous les construits affectés par un changement de liste de codes nécessite de traverser plusieurs chemins
Limitations du relationnel¶
| Défi | Impact relationnel |
|---|---|
| Traversée de profondeur variable | Nécessite des CTE récursifs ou plusieurs requêtes |
| Recherche de chemins | Auto-jointures complexes, difficiles à optimiser |
| Évolution du schéma | L'ajout de types de relations nécessite ALTER TABLE |
| Références polymorphes | Nécessite des colonnes de type ou des clés étrangères multiples |
| Analyse d'impact | Scans multi-tables coûteux |
Exemple : requête de profondeur variable en SQL¶
Trouver tous les construits atteignables depuis un instrument :
WITH RECURSIVE reachable AS (
-- Base case: start from instrument
SELECT child_id, child_type, 1 as depth
FROM control_construct_refs
WHERE parent_id = 'instrument-1'
UNION ALL
-- Recursive case: follow references
SELECT ccr.child_id, ccr.child_type, r.depth + 1
FROM control_construct_refs ccr
JOIN reachable r ON ccr.parent_id = r.child_id
WHERE r.depth < 20 -- Safety limit
)
SELECT * FROM reachable;
Cette requête :
- Devient lente à mesure que la profondeur augmente
- Nécessite un réglage minutieux des index
- Peut atteindre les limites de récursion de la base de données
- Ne filtre pas facilement par type de relation
Approche base de données graphe¶
Conception du schéma graphe¶
Dans Neo4j, la même structure est exprimée sous forme de nœuds et de relations :
// Nodes
(:CodeList {fragment_id: "cl-1", label: "Age Groups"})
(:QuestionItem {fragment_id: "q-1", question_text: "What is your age?"})
(:QuestionConstruct {fragment_id: "qc-1"})
(:Variable {fragment_id: "v-1", label: "Age"})
// Relationships
(q:QuestionItem)-[:USES_CODELIST]->(cl:CodeList)
(qc:QuestionConstruct)-[:ASKS_QUESTION]->(q:QuestionItem)
(v:Variable)-[:DERIVED_FROM]->(qc:QuestionConstruct)
La même requête, simplifiée¶
Trouver toutes les variables utilisant une liste de codes spécifique :
MATCH (cl:CodeList {fragment_id: 'cl-age-groups'})
<-[:USES_CODELIST]-(q:QuestionItem)
<-[:ASKS_QUESTION]-(qc:QuestionConstruct)
<-[:DERIVED_FROM]-(v:Variable)
RETURN v
Traversée de profondeur variable¶
Trouver tous les construits atteignables depuis un instrument :
MATCH (inst:Instrument {fragment_id: 'instrument-1'})
-[:HAS_CONSTRUCT*]->(construct)
RETURN construct
L'opérateur * gère la profondeur arbitraire naturellement, sans limites de récursion ni CTE
complexes.
Avantages des bases de données graphe pour le DDI¶
1. Modèle de données naturel¶
Les métadonnées DDI sont un graphe. La structure XML définit explicitement des nœuds (fragments) et des arêtes (références) :
<QuestionConstruct>
<QuestionReference> <!-- This IS an edge -->
<ID>question-1</ID>
</QuestionReference>
</QuestionConstruct>
Les bases de données graphe représentent cela directement, tandis que les bases relationnelles doivent l'encoder indirectement.
2. Performance des requêtes pour les traversées¶
| Opération | Relationnel | Graphe |
|---|---|---|
| JOIN unique | Rapide | Rapide |
| 3-4 JOINs | Modéré | Rapide |
| Traversée de profondeur variable | Lent/Complexe | Rapide |
| Recherche de chemins | Très lent | Optimisé |
| Correspondance de motifs | Codage manuel | Support natif |
Les bases de données graphe utilisent l'adjacence sans index, ce qui signifie que chaque nœud référence directement ses voisins. Le temps de traversée est proportionnel à la taille du résultat, pas à la taille totale des données.
3. Flexibilité du schéma¶
Ajouter un nouveau type de relation dans une base relationnelle :
ALTER TABLE question_items
ADD COLUMN instruction_id VARCHAR REFERENCES instructions(id);
-- Plus migration scripts, ORM updates, etc.
Ajouter un nouveau type de relation dans Neo4j :
MATCH (q:QuestionItem {fragment_id: 'q-1'})
MATCH (i:Instruction {fragment_id: 'inst-1'})
CREATE (q)-[:HAS_INSTRUCTION]->(i)
Aucune migration de schéma requise. Les nouveaux types de relations peuvent coexister avec les données existantes.
4. Langage de requête expressif¶
Cypher exprime les motifs de graphe directement :
// Find questions with conditional branching that use a specific code list
MATCH (cl:CodeList {fragment_id: 'cl-yes-no'})
<-[:USES_CODELIST]-(q:QuestionItem)
<-[:ASKS_QUESTION]-(qc:QuestionConstruct)
<-[:HAS_CONSTRUCT]-(seq:Sequence)
<-[:THEN|ELSE]-(ite:IfThenElse)
RETURN q.fragment_id, q.question_text, ite.condition
5. Analyse d'impact¶
Comprendre ce qui est affecté par un changement :
// Find everything that depends on a code list
MATCH (cl:CodeList {fragment_id: 'cl-occupation'})
MATCH path = (cl)<-[*1..5]-(dependent)
RETURN DISTINCT labels(dependent)[0] as type,
dependent.fragment_id as id,
length(path) as distance
ORDER BY distance
Inconvénients potentiels et considérations¶
1. Courbe d'apprentissage¶
Défi : Les équipes habituées au SQL doivent apprendre Cypher et la pensée en graphe.
Atténuation :
- La syntaxe Cypher est intuitive pour la correspondance de motifs
- ddigraph fournit des requêtes prêtes à l'emploi dans les scripts d'audit
- La documentation inclut les motifs de requête courants
2. Complexité opérationnelle¶
Défi : L'exécution de Neo4j nécessite une infrastructure supplémentaire.
Atténuation :
- Neo4j Aura fournit un hébergement cloud géré
- Docker simplifie le déploiement local
- Neo4j Community Edition est gratuit pour un usage mono-instance
3. Écosystème d'outils¶
Défi : Moins d'outils de BI prennent en charge nativement les bases de données graphe.
Atténuation :
- Neo4j dispose de connecteurs JDBC/ODBC pour les outils de BI
- Le motif adaptateur de ddigraph permet l'export vers pandas, JSON, CSV
- Les résultats graphe peuvent être aplatis pour le reporting traditionnel
4. Sémantique transactionnelle¶
Défi : Les bases de données graphe optimisent les lectures ; les charges d'écriture intensives peuvent différer.
Atténuation :
- Les métadonnées DDI sont dominées par la lecture (charger une fois, interroger souvent)
- ddigraph utilise des écritures par lots avec UNWIND pour une ingestion efficace
- Neo4j prend en charge les transactions ACID
5. Requêtes d'agrégation¶
Défi : Certaines opérations d'agrégation sont plus naturelles en SQL.
Exemple : Compter les variables par jeu de données est plus simple en SQL :
SELECT dataset_id, COUNT(*) FROM variables GROUP BY dataset_id;
Équivalent Cypher :
MATCH (d:Dataset)<-[:IN_DATASET]-(v:Variable)
RETURN d.id, count(v)
Les deux fonctionnent, mais le GROUP BY de SQL peut sembler plus familier.
6. Considérations de volume de données¶
Défi : Les très grands jeux de données (millions de nœuds) nécessitent un réglage.
Atténuation :
- ddigraph crée des index sur les champs clés
- Les tailles de lots sont configurables
- Neo4j Enterprise offre le clustering pour la montée en charge
Quand utiliser chaque approche¶
La base de données graphe (Neo4j) est préférable quand¶
- Les requêtes traversent fréquemment des relations
- L'analyse de chemins est importante (accessibilité, impact)
- Le schéma évolue avec de nouveaux types de relations
- Vous devez visualiser la structure des métadonnées
- Intégration avec d'autres données graphe (SDMX, ontologies)
La base de données relationnelle peut être préférable quand¶
- Les requêtes sont principalement des agrégations et des comptages
- L'équipe n'a aucune expérience des bases de données graphe et un temps d'apprentissage limité
- L'infrastructure existante est purement relationnelle
- Le volume de données est faible et les requêtes sont simples
- La conformité exige des technologies de base de données spécifiques
Approches hybrides¶
Vous n'avez pas à choisir exclusivement. Les motifs courants incluent :
1. Graphe pour la navigation, relationnel pour le reporting¶
Utilisez Neo4j pour les requêtes structurelles et l'analyse de chemins, mais synchronisez les résumés vers un entrepôt de données relationnel pour les tableaux de bord BI.
2. Graphe comme couche de métadonnées¶
Conservez les données de réponse aux enquêtes dans une base relationnelle, mais utilisez Neo4j pour les métadonnées qui décrivent ces données. Liez-les via les identifiants de jeu de données/variable.
3. Export pour l'analyse¶
Utilisez le motif adaptateur de ddigraph pour exporter les données graphe vers des DataFrames pandas ou des fichiers CSV pour les équipes plus à l'aise avec ces outils :
python demo/load_pandas.py # Export to DataFrames
python demo/export_files.py # Export to JSON/CSV
Référence de comparaison des requêtes¶
| Tâche | SQL | Cypher |
|---|---|---|
| Recherche par ID | SELECT * FROM questions WHERE id = 'q1' |
MATCH (q:QuestionItem {fragment_id: 'q1'}) RETURN q |
| JOIN simple | SELECT * FROM questions q JOIN code_lists cl ON q.code_list_id = cl.id |
MATCH (q:QuestionItem)-[:USES_CODELIST]->(cl) RETURN q, cl |
| Profondeur variable | CTE récursif (complexe) | MATCH path = (a)-[*1..5]->(b) RETURN path |
| Plus court chemin | Non natif | MATCH p = shortestPath((a)-[*]-(b)) RETURN p |
| Tous les chemins | Très complexe | MATCH p = allShortestPaths((a)-[*]-(b)) RETURN p |
| Correspondance de motifs | JOINs multiples + conditions | MATCH (a)-[:R1]->(b)-[:R2]->(c) WHERE ... |
Conclusion¶
Pour les métadonnées DDI, les bases de données graphe offrent des avantages significatifs en expressivité des requêtes, performance de traversée et flexibilité du schéma. Les compromis (courbe d'apprentissage, complexité opérationnelle) sont gérables, notamment grâce à :
- La structure intrinsèquement graphe du DDI
- L'importance de la traversée des relations dans les cas d'usage des métadonnées
- Les outils matures et les options cloud de Neo4j
- Le motif adaptateur de ddigraph pour la flexibilité d'intégration
Les équipes devraient évaluer en fonction de leurs motifs de requête spécifiques, de leur infrastructure existante et de leur volonté d'investir dans les compétences en bases de données graphe.