Requêtes relationnelles (jointures) dans les bases documentaires

Mis à jour : septembre 2026

Une requête relationnelle en base documentaire est une jointure faite par références et lookups au lieu de clés étrangères — ou évitée en embarquant. Ce “ou” est tout le sujet : les bases documentaires vous offrent trois façons de relier des données, et la décision de conception porte sur le moment où la jointure a lieu — à l’écriture (embarquement), au moment de la requête côté serveur (lookup), ou au moment de la requête côté application (références plus une récupération de suivi).

Points clés

QuestionRéponse
Les bases documentaires savent-elles joindre ?Oui — les lookups font des left outer joins, qui renvoient des tableaux, pas des lignes
La vraie décisionEmbarquer vs. référencer — quand la jointure a-t-elle lieu ?
Règle par défautEmbarquez ce qui se lit ensemble et reste borné ; référencez ce qui vit seul ou grossit
La vérité sur la performanceLes lookups côté serveur sont des jointures en boucles imbriquées — bien par requête, inadaptés à l’analytique
Le bug classiqueLes requêtes N+1 — corrigées par lot, par include ou par embarquement

Les trois façons de relier des documents

// 1 · Embarquer — la "jointure" a eu lieu à l'écriture
{ _id: 1, title: "Dune", author: { name: "Frank Herbert", born: 1920 } }

// 2 · Référence + $lookup — la jointure a lieu à la requête, côté serveur
db.books.aggregate([
  { $lookup: { from: "authors", localField: "authorId",
               foreignField: "_id", as: "author" } },  // left outer join → tableau
  { $unwind: "$author" }                               // aplatir → inner join
])

// 3 · Référence + jointure côté application — la voie ODM/SDK (ci-dessous)

La troisième voie est celle où vit l’essentiel du code applicatif — des références typées parcourues par chargement anticipé (eager loading) en une seule requête, pour que les documents liés arrivent ensemble sans pipeline :

// JavaScript / Node.js — Back4app JS SDK
// A join in a document database: Pointer + include, one request
const query = new Parse.Query('Comment');
query.equalTo('post', postPointer);   // Comment.post is a Pointer<Post>
query.include('author');              // "join" the author document in
const comments = await query.find();

const name = comments[0].get('author').get('username'); // already loaded — no N+1

Embarquer vs. référencer : la décision

Arbre de décision entre embarquer et référencerLes données toujours lues avec leur parent et de taille bornée doivent être embarquées ; les données partagées, mises à jour indépendamment ou non bornées doivent être référencées, en inversant le sens de la référence pour les ensembles d'enfants énormes.

non

oui

non

oui

oui

non

oui

Toujours lues avec
le parent ?

Référencer

Taille bornée ?
(pas de croissance sans fin)

Partagées avec
d'autres parents ?

Embarquer

Nombre d'enfants énorme ?

Inversez : stockez la
réf. du parent dans chaque enfant

Les données toujours lues avec leur parent et de taille bornée doivent être embarquées ; les données partagées, mises à jour indépendamment ou non bornées doivent être référencées, en inversant le sens de la référence pour les ensembles d'enfants énormes.
DimensionEmbarquerRéférencer
Pattern de lectureToujours récupéré avec le parentRécupéré seul ou à la demande
Pattern d’écritureMis à jour avec le parent, de façon atomiqueMis à jour indépendamment
CardinalitéUn-à-quelquesUn-à-plusieurs et au-delà
CroissanceBornée (les adresses d’une personne)Non bornée (les commentaires d’un post)
PartageAppartient à un seul parentPartagé entre plusieurs parents
Le coût de la jointureNul — payé à l’écriturePayé à chaque requête — lookup, include ou lot

Les paliers de cardinalité donnent la même table sous forme de règles empiriques : un-à-quelques embarque le tableau, un-à-plusieurs référence par ID, un-à-énormément inverse le sens — l’enfant stocke la référence au parent, car aucun document parent ne peut contenir un tableau qui grossit sans cesse. Ce qui désigne les deux anti-patterns derrière la plupart des incidents de modélisation documentaire : les tableaux non bornés et le plafond de 16 Mo par document qu’ils finissent par menacer. Les correctifs standard — référencer à la place, embarquer un sous-ensemble borné (les N plus récents avec une collection de débordement), ou regrouper les enfants dans des documents-buckets — sont tous des variantes de “empêcher le document de grossir”.

Le lookup, en toute honnêteté

$lookup est une vraie jointure, avec deux astérisques honnêtes. La forme : il renvoie les correspondances sous forme de tableau embarqué pour chaque document en entrée — $unwind l’aplatit en lignes et, sans la sémantique de conservation des vides, transforme le comportement de left join en inner join. La performance : les moteurs documentaires n’exécutent que des jointures en boucles imbriquées, et les benchmarks publics sont sans appel — joindre un million de documents a pris des dizaines de secondes avec index, contre une demi-seconde pour l’équivalent embarqué. Les règles opérationnelles qui en découlent : indexez toujours le champ étranger (sans index, chaque document en entrée déclenche un parcours de collection), utilisez les lookups pour des jointures par requête sur une poignée de documents, et ne construisez jamais de fan-out analytique dessus — cette charge revient à un entrepôt de données ou à un moteur relationnel.

Le problème N+1 et ses quatre issues

Le bug classique : une requête pour N parents, puis une boucle qui émet une requête par parent pour ses enfants — N+1 allers-retours qui augmentent avec la taille de la page. Les issues, de la meilleure à la moins bonne : par lot (batch) — collectez les ID des parents et récupérez tous les enfants en une seule requête de type contained-in (le populate des bons ODM le fait pour vous) ; include — un eager loading au niveau du SDK qui renvoie les documents référencés dans la même requête, comme dans les onglets ci-dessus ; lookup — un seul pipeline côté serveur ; embarquement — la jointure cesse d’exister. Ce qui transforme le N+1 de bug en problème d’architecture, c’est de ne pas le remarquer : il passe sans encombre sur dix enregistrements de test et s’effondre à mille — la pathologie complète a sa propre entrée dans le registre de ce glossaire.

Cas d’usage courants

  • Contenu avec auteurs. Posts, commentaires, auteurs — des références avec includes pour les vues en liste, de l’embarquement pour les champs d’instantané affichés en lecture seule.
  • Catalogues et commandes. La référence étendue canonique : une commande embarque le nom et le prix du produit au moment de la vente (instantané immuable) plus une référence au produit vivant.
  • Flux d’activité et d’événements. Un-à-énormément — des documents enfants qui portent la référence au parent, jamais de tableaux sur le parent.
  • Profils utilisateur. La vitrine de l’embarquement : adresses, préférences, paramètres — lus ensemble, bornés, possédés.
  • Graphes sociaux. Des tableaux de références plusieurs-à-plusieurs — et le signal honnête qu’au-delà d’un certain point, cette forme réclame un moteur relationnel ou orienté graphe.

Embarquer, référencer ou changer de moteur ? Matrice de décision

Embarquez quand…Référencez quand…Prenez une base relationnelle quand…
Lu et mis à jour ensembleConsulté indépendammentLes jointures sont la charge de travail, pas l’exception
Petit et bornéNon borné ou à forte cardinalitéAnalytique ad hoc entre entités
Possédé par un seul parentPartagé entre plusieurs parentsIntégrité référentielle stricte exigée
Les mises à jour atomiques avec le parent comptentCycles de mise à jour indépendantsTransactions complexes sur plusieurs lignes
Le cas typique : les champs de profilLe cas typique : les commentairesLe cas typique : les domaines chargés en plusieurs-à-plusieurs

La troisième colonne est la section que les pages des éditeurs n’écriront pas : si chaque écran a besoin de trois lookups et que l’intégrité référentielle vous empêche de dormir, vos données réclament des tables — les modèles documentaires gagnent quand les patterns d’accès sont connus et hiérarchiques, pas comme remplacement universel.

Limites et trade-offs

  • La dénormalisation est un instrument de dette. Les champs dupliqués suppriment des jointures mais se remboursent à la mise à jour — écritures en fan-out, fenêtres de données périmées et code de cohérence (transactions ou triggers sur change streams) en sont les intérêts.
  • Pas de clés étrangères, pas de filet de sécurité. Les références ne garantissent pas l’existence ; supprimer un auteur laisse en silence des références orphelines dans les livres, à moins que votre plateforme ou votre code ne fasse le ménage.
  • Les lookups ne s’optimisent pas. Aucun réordonnancement des jointures, aucune stratégie hash — l’ordre du pipeline est votre plan de requête.
  • Les migrations entre formes sont de vrais projets. Passer de l’embarqué au référencé (le sauvetage du tableau qui grossit) implique de remplir rétroactivement des collections et de réécrire des requêtes — modélisez pour la cardinalité de demain, pas celle d’aujourd’hui.
  • Le plafond de 16 Mo est une falaise, pas un avertissement. Les schémas de croissance qui s’en approchent dégradent la performance bien avant de l’atteindre.

Les requêtes relationnelles sur Back4app

Back4app est une plateforme open-source de Backend as a Service (BaaS) qui combine une base de données gérée, des API REST et GraphQL générées automatiquement, l’authentification, le stockage de fichiers et des fonctions serverless avec Cloud Code. Sa base de données documentaire parle un vocabulaire relationnel par conception : les Pointers déclarent les arêtes un-à-plusieurs, les Relations gèrent le plusieurs-à-plusieurs, et include() parcourt les arêtes en une seule requête — l’issue au N+1 intégrée au SDK, comme le montrent les onglets de code ci-dessus. L’API GraphQL générée automatiquement imbrique gratuitement les objets liés en une seule requête, et les suppressions peuvent se propager en cascade via des triggers Cloud Code — le filet de sécurité d’intégrité référentielle que les moteurs documentaires laissent de côté.

Questions fréquentes

MongoDB prend-il en charge les jointures ?

Pas les jointures façon SQL — mais oui, des jointures utiles d'un point de vue relationnel. L'étape d'agrégation $lookup réalise un left outer join, en attachant les documents correspondants d'une autre collection sous forme de tableau embarqué plutôt que de lignes à plat. Ajouter $unwind la fait passer à une sémantique d'inner join. Ce qui diffère de SQL, c'est la forme du résultat, le profil de performance, et le fait que les jointures y sont l'exception plutôt que la règle.

Faut-il embarquer ou référencer les données liées ?

La règle de consensus : embarquez ce que vous lisez, mettez à jour et archivez ensemble — des données petites, bornées, étroitement couplées. Référencez ce qui existe par soi-même : partagé entre plusieurs parents, mis à jour indépendamment, à forte cardinalité ou non borné. Les recommandations de l'éditeur disent de privilégier l'embarquement sauf raison impérieuse, et les raisons impérieuses sont précisément ces quatre-là.

Comment la cardinalité change-t-elle la décision de modélisation ?

Les paliers classiques : un-à-quelques (les adresses d'une personne) — embarquez le tableau. Un-à-plusieurs (des centaines à des milliers) — un tableau de références. Un-à-énormément (les événements de log d'une machine) — inversez le sens et stockez la référence au parent dans chaque enfant, car le parent ne peut pas contenir un tableau qui grossit sans fin. Plusieurs-à-plusieurs — des tableaux de références, parfois des deux côtés.

$lookup est-il lent ?

Comparé aux jointures relationnelles, oui, de façon constante : les moteurs documentaires exécutent des jointures en boucles imbriquées (nested loop), sans les stratégies merge et hash dont disposent les optimiseurs relationnels. Des benchmarks publics joignant un million de documents ont mesuré des dizaines de secondes même avec index, contre une demi-seconde pour l'équivalent embarqué. Les règles opérationnelles : indexez toujours le champ étranger, réservez $lookup aux chemins par requête qui joignent une poignée de documents, et ne construisez jamais de fan-out analytique dessus.

Qu'est-ce que la limite de 16 Mo par document et pourquoi compte-t-elle ici ?

Un plafond strict sur la taille d'un document — et la raison pour laquelle "tout embarquer, point" échoue. Les tableaux embarqués non bornés (commentaires, logs, événements) grossissent vers ce plafond et dégradent l'efficacité du cache et des index bien avant de l'atteindre. Les correctifs standard : passer aux références, n'embarquer qu'un sous-ensemble borné (les N plus récents) avec une collection de débordement, ou regrouper les enfants dans des documents-buckets.

Qu'est-ce que le problème N+1 dans les bases documentaires ?

Récupérer N parents en une requête, puis émettre une requête de plus par parent pour ses données liées — N+1 allers-retours qui croissent avec le jeu de résultats. Les correctifs, par ordre de préférence : regrouper la seconde étape en une seule requête sur les ID collectés, utiliser un lookup côté serveur dans un seul pipeline, récupérer les documents liés en une requête via le mécanisme include du SDK, ou embarquer pour que la "jointure" ait eu lieu à l'écriture.

Comment les ODM et les SDK backend expriment-ils les relations ?

Sous forme de références typées avec un opérateur de chargement anticipé (eager loading). Les ODM documentaires déclarent des champs de référence et les peuplent via des requêtes de suivi groupées ; les SDK backend utilisent des Pointers — une référence typée vers un autre objet — et un opérateur include qui récupère les documents référencés dans la même requête, plus des types Relation pour les grands ensembles plusieurs-à-plusieurs. La même idée partout : déclarez l'arête, puis choisissez quand la parcourir.

Quand une base relationnelle est-elle tout simplement le meilleur choix ?

Quand les jointures sont la charge de travail plutôt que l'exception : domaines chargés en plusieurs-à-plusieurs, analytique ad hoc entre entités, intégrité référentielle stricte et transactions complexes sur plusieurs lignes. Les modèles documentaires gagnent quand les patterns d'accès sont connus et hiérarchiques — un document par écran de données. Si chaque requête a besoin de trois lookups, vos données vous disent qu'elles veulent des tables.

Termes associés

À comparer avec

Lectures recommandées

Prêt à construire votre backend ?

Lancez votre projet sur Back4app en quelques minutes — base de données, authentification, API et Cloud Code inclus. Sans carte bancaire.

Écrit et révisé par Back4app Engineering, Back4app Engineering · Publié le 2026-09-11