Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 516 minIntermédiaire

Opérateurs de requête MongoDB : $gt, $in, $or, $elemMatch, $regex

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • comparer avec $gt, $in, $ne et leurs variantes ;
  • combiner des conditions avec $or, $and et $nor ;
  • interroger les tableaux et les sous-documents, avec $elemMatch quand il le faut ;
  • chercher dans un texte et dans un intervalle de dates.

3 exercices corrigés1 schéma

Les opérateurs de requête MongoDB sont des mots-clés qui commencent par $ et s'écrivent dans le filtre d'un find, d'un updateOne ou d'un deleteMany pour exprimer autre chose qu'une égalité : une comparaison ($gt, $in), une combinaison de conditions ($or), une condition sur un tableau ($elemMatch) ou sur un texte ($regex). Ce chapitre les présente par famille, avec leur équivalent SQL, sur le jeu de données d'AppliFrais. Ce sont aussi ces opérateurs qu'un attaquant essaie de glisser dans tes filtres : garde-les en tête pour le chapitre 13.

La forme d'une condition

Une égalité s'écrit { champ: valeur }. Toute autre condition s'écrit { champ: { $operateur: valeur } } : l'opérateur se place à l'intérieur de l'objet du champ.

js
db.fiches.find({ etat: 'CL' })                          // etat = 'CL'
db.fiches.find({ montantValide: { $gt: 500 } })         // montantValide > 500
db.fiches.find({ mois: { $gte: '2026-09', $lt: '2026-10' } })   // deux conditions sur le même champ

Les opérateurs logiques ($or, $and, $nor) font exception : ils se placent au niveau du filtre et prennent un tableau de conditions.

Les opérateurs de comparaison

OpérateurSignificationSQLExemple
$eqégal (implicite le plus souvent)={ etat: { $eq: 'CL' } }, ou { etat: 'CL' }
$nedifférent<>{ etat: { $ne: 'CR' } }
$gt, $gtesupérieur, supérieur ou égal>, >={ montantValide: { $gte: 500 } }
$lt, $lteinférieur, inférieur ou égal<, <={ dateEmbauche: { $lt: ISODate('2020-01-01') } }
$indans la listeIN (...){ region: { $in: ['Isère', 'Savoie'] } }
$ninhors de la listeNOT IN (...){ etat: { $nin: ['CR', 'CL'] } }
js
// Les fiches d'au moins 500 € qui ne sont pas encore remboursées
db.fiches.find(
  { montantValide: { $gte: 500 }, etat: { $ne: 'RB' } },
  { _id: 0, mois: 1, montantValide: 1 }
).sort({ montantValide: -1 })
js
[
  { mois: '2026-09', montantValide: 898.9 },
  { mois: '2026-09', montantValide: 562.4 }
]

Les comparaisons respectent le type : une chaîne se compare dans l'ordre alphabétique ('2026-09' < '2026-10', c'est pour cela que le format AAAA-MM est pratique), une date dans l'ordre chronologique, un nombre dans l'ordre numérique. Une comparaison entre deux types différents ne donne rien : { montantValide: { $gt: '500' } } (une chaîne) ne trouve aucune fiche.

Les opérateurs logiques

Plusieurs champs dans un même filtre forment un ET. Pour un OU, $or prend un tableau de filtres :

js
// Les employés de l'Isère, ou embauchés avant 2020
db.visiteurs.find(
  { $or: [ { region: 'Isère' }, { dateEmbauche: { $lt: ISODate('2020-01-01') } } ] },
  { _id: 0, nom: 1 }
)
// [ { nom: 'Benali' }, { nom: 'Girard' }, { nom: 'Lambert' } ]
OpérateurRôleQuand l'utiliser
(implicite)ET entre les champs d'un même objetPresque toujours
$andET explicitePour deux conditions qui ont la même clé (deux $or, par exemple), qu'un objet JavaScript ne peut pas contenir deux fois
$orOUConditions sur des champs différents
$norAucune des conditions{ $nor: [ { etat: 'CR' }, { etat: 'CL' } ] } : ni en cours, ni clôturée
$notNégation d'un opérateur{ nom: { $not: /^[A-L]/ } } : nom qui ne commence pas par A à L

Quand toutes les branches d'un $or portent sur le même champ, $in est plus court et plus efficace : { etat: { $in: ['CR', 'CL'] } } plutôt que { $or: [ { etat: 'CR' }, { etat: 'CL' } ] }.

Présence, absence et null

FiltreTrouve les documents…
{ region: null }dont region vaut null ou qui n'ont pas de champ region
{ region: { $exists: false } }qui n'ont pas de champ region
{ region: { $exists: true } }qui ont un champ region, même à null
{ montantValide: { $type: 'null' } }dont montantValide existe et vaut null
{ montantValide: { $type: 'double' } }dont montantValide est un nombre à virgule

Dans le jeu de données, montantValide est présent dans toutes les fiches, à null tant que la fiche n'est pas validée : { montantValide: null } en trouve 5, { montantValide: { $exists: false } } aucune. $type sert aussi à traquer les incohérences, comme des montants enregistrés en chaîne par une ancienne version de l'application : { montantValide: { $type: 'string' } }.

Interroger les sous-documents et les tableaux

C'est ici que MongoDB diffère le plus de SQL. La notation pointée désigne un champ à l'intérieur d'un sous-document ou des éléments d'un tableau. Elle s'écrit entre guillemets : { lignesForfait.code: 'NUI' } sans guillemets est une erreur de syntaxe JavaScript.

js
// Les fiches qui ont au moins une ligne de nuitée
db.fiches.find({ 'lignesForfait.code': 'NUI' }, { _id: 0, visiteur: 1, mois: 1 })
js
[
  { visiteur: ObjectId('6a0000000000000000000001'), mois: '2026-09' },
  { visiteur: ObjectId('6a0000000000000000000002'), mois: '2026-09' },
  { visiteur: ObjectId('6a0000000000000000000003'), mois: '2026-09' }
]

Sur un tableau, une condition est vraie si au moins un élément la satisfait. C'est très pratique, avec un piège : quand tu poses plusieurs conditions, chacune peut être satisfaite par un élément différent.

js
// Intention : une ligne de nuitée d'au moins 5 nuits
db.fiches.find({ 'lignesForfait.code': 'NUI', 'lignesForfait.quantite': { $gte: 5 } })
// Renvoie 3 fiches… alors qu'aucune n'a 5 nuitées !

La fiche de septembre de Lucas Martin a bien une ligne NUI (2 nuits) et une ligne dont la quantité vaut au moins 5 (420 km) : les deux conditions sont vraies, mais pas sur la même ligne. Pour exiger qu'un même élément satisfasse toutes les conditions, il faut $elemMatch :

js
db.fiches.find({ lignesForfait: { $elemMatch: { code: 'NUI', quantite: { $gte: 5 } } } })
// [] : aucune ligne de nuitée n'atteint 5 nuits
Sans $elemMatch'lignesForfait.code': 'NUI','lignesForfait.quantite': { $gte: 5 }ETP quantite 3KM quantite 420NUI quantite 2REP quantite 5quantité ≥ 5code = NUIdeux lignes différentesla fiche est retenue à tortAvec $elemMatchlignesForfait: { $elemMatch:{ code: 'NUI', quantite: { $gte: 5 } } }ETP quantite 3KM quantite 420NUI quantite 2REP quantite 5code ✗ qté ✗code ✗qté ✗code ✗les deux conditions sur la même ligneaucune ligne : la fiche est écartée
Sans $elemMatch, les conditions peuvent porter sur des lignes différentes

Les autres opérateurs de tableau :

OpérateurTrouve les documents dont le tableau…Exemple
(égalité)contient cette valeur{ 'lignesForfait.code': 'NUI' }
$allcontient toutes ces valeurs, dans n'importe quel ordre{ 'lignesForfait.code': { $all: ['NUI', 'REP'] } }
$sizea exactement ce nombre d'éléments{ lignesForfait: { $size: 0 } } : fiche sans ligne
$elemMatcha un élément qui satisfait toutes les conditionsvoir ci-dessus
$nene contient aucun élément égal à la valeur{ 'lignesForfait.code': { $ne: 'KM' } } : aucune ligne KM

$size n'accepte qu'un nombre exact. Pour « au moins deux frais hors forfait », deux écritures : tester qu'un élément existe à l'indice 1, { 'fraisHorsForfait.1': { $exists: true } }, ou calculer la taille avec $expr (section suivante).

Comparer deux champs avec $expr

Les opérateurs ci-dessus comparent un champ à une valeur fixe. Pour comparer un champ à un autre, ou à un calcul, $expr permet d'utiliser les expressions de l'agrégation (chapitre 8) dans un filtre. Dans une expression, '$champ' désigne la valeur du champ.

js
// Les fiches qui ont plus d'un frais hors forfait
db.fiches.find(
  { $expr: { $gt: [ { $size: '$fraisHorsForfait' }, 1 ] } },
  { _id: 0, mois: 1, etat: 1 }
)
// [ { mois: '2026-09', etat: 'CL' } ]  (Inès Benali : parking et congrès)

$expr ne peut en général pas s'appuyer sur un index : réserve-le aux collections petites ou déjà filtrées par d'autres conditions.

Chercher dans un texte : $regex

$regex filtre avec une expression régulière, l'équivalent puissant de LIKE. mongosh accepte aussi la notation littérale de JavaScript, entre barres obliques, avec l'option i pour ignorer la casse.

js
db.visiteurs.find({ nom: { $regex: '^b', $options: 'i' } })      // nom qui commence par b ou B
db.fiches.find({ 'fraisHorsForfait.libelle': /lyon/i },
               { _id: 0, mois: 1, 'fraisHorsForfait.libelle': 1 })
js
[
  { mois: '2026-09', fraisHorsForfait: [ { libelle: 'Repas client à Lyon' } ] },
  { mois: '2026-09', fraisHorsForfait: [ { libelle: 'Taxi Lyon Part-Dieu' } ] }
]
SQLMongoDB
nom LIKE 'Mar%'{ nom: /^Mar/ }
nom LIKE '%tin'{ nom: /tin$/ }
libelle LIKE '%lyon%' (insensible à la casse selon l'interclassement){ libelle: /lyon/i }

Une expression ancrée au début et sensible à la casse (/^Mar/) peut utiliser un index ; /lyon/i parcourt toute la collection. Pour une vraie recherche dans des textes longs, MongoDB propose un index texte et l'opérateur $text, qui cherche des mots en tenant compte de la langue ; Atlas offre en plus Atlas Search, un moteur de recherche complet.

Les dates

Les dates se comparent comme des nombres. Pour « un jour » ou « un mois », filtre sur un intervalle :

js
// Les frais hors forfait engagés du 1er au 14 septembre 2026
db.fiches.find(
  { fraisHorsForfait: { $elemMatch: {
      date: { $gte: ISODate('2026-09-01'), $lt: ISODate('2026-09-15') }
  } } },
  { _id: 0, visiteur: 1, mois: 1 }
)

Les trois fiches de septembre ont un frais dans cet intervalle (le 14, le 8 et le 3). Écris la borne haute exclue ($lt le lendemain) plutôt qu'incluse avec une heure ($lte: ISODate('2026-09-14T23:59:59')), qui oublie la dernière seconde. ISODate('2026-09-01') est minuit UTC : en France, c'est 2 h du matin en été. Range les dates en UTC et convertis à l'affichage.

Exercices

Exercice 1 sur 3 Traduire en filtres Réussi À revoir

Écris les requêtes find correspondantes (sur le jeu de données d'origine) et donne le nombre de documents renvoyés.

  1. Les fiches validées, mises en paiement ou remboursées.
  2. Les visiteurs embauchés à partir de 2020 qui travaillent dans le Rhône.
  3. Les fiches qui ont une ligne kilométrique de plus de 300 km.
  4. Les fiches qui n'ont aucune ligne de nuitée.
  5. Les fiches qui ont au moins un frais hors forfait de plus de 30 €.
  6. Les fiches qui ont exactement 4 lignes de frais forfaitisés.
Voir la réponseMasquer la réponse de l'exercice 1
js
// 1. 4 fiches
db.fiches.find({ etat: { $in: ['VA', 'MP', 'RB'] } })
// 2. 1 visiteur : Lucas Martin (Thomas Girard est entré en 2019)
db.visiteurs.find({ region: 'Rhône', dateEmbauche: { $gte: ISODate('2020-01-01') } })
// 3. 4 fiches : Martin août (310) et septembre (420), Benali septembre (640), Girard octobre (380)
db.fiches.find({ lignesForfait: { $elemMatch: { code: 'KM', quantite: { $gt: 300 } } } })
// 4. 6 fiches
db.fiches.find({ 'lignesForfait.code': { $ne: 'NUI' } })
// 5. 2 fiches : Benali septembre (congrès, 150 €), Girard septembre (taxi, 32,40 €)
db.fiches.find({ 'fraisHorsForfait.montant': { $gt: 30 } })
// 6. 3 fiches
db.fiches.find({ lignesForfait: { $size: 4 } })

Pour la 3, $elemMatch est indispensable : sans lui, { 'lignesForfait.code': 'KM', 'lignesForfait.quantite': { $gt: 300 } } donnerait ici le même résultat par chance, mais retiendrait une fiche dont une ligne KM fait 50 km et une autre ligne a une quantité de 400. Pour la 4, $nin: ['NUI'] donne aussi 6. La fiche vide de Camille Roux en fait partie.

Tu avais trouvé ?

Exercice 2 sur 3 Trouver l'erreur Réussi À revoir

Chaque requête a un défaut. Explique-le et corrige-la.

  1. db.fiches.find({ mois: { $gte: '2026-09' }, mois: { $lte: '2026-09' } }) : l'auteur veut les fiches de septembre.
  2. db.fiches.find({ 'lignesForfait.code': 'KM', 'lignesForfait.quantite': { $lt: 100 } }) : l'auteur veut les fiches qui ont une ligne kilométrique de moins de 100 km.
  3. db.fiches.find({ fraisHorsForfait: { libelle: 'Parking gare de Grenoble' } })
  4. db.visiteurs.find({ $or: [ { region: 'Rhône' }, { region: 'Isère' } ] })
Voir la réponseMasquer la réponse de l'exercice 2
  1. La clé mois apparaît deux fois : la seconde écrase la première, et la requête devient « mois ≤ 2026-09 », soit 5 fiches (août et septembre) au lieu de 3. Correction : { mois: { $gte: '2026-09', $lte: '2026-09' } }, ou plus simplement { mois: '2026-09' }.
  2. Les deux conditions peuvent porter sur deux lignes différentes : une fiche qui a une ligne KM de 420 km et une ligne ETP de quantité 3 est retenue. La requête renvoie 8 fiches. Correction avec $elemMatch : { lignesForfait: { $elemMatch: { code: 'KM', quantite: { $lt: 100 } } } }, qui ne renvoie que la fiche d'octobre d'Inès Benali (95 km).
  3. Égalité sur un sous-document entier : elle exige tous les champs (date, libelle, montant) dans l'ordre. Il faut la notation pointée : { 'fraisHorsForfait.libelle': 'Parking gare de Grenoble' }.
  4. Elle fonctionne, mais un $or sur un seul champ s'écrit mieux avec $in : { region: { $in: ['Rhône', 'Isère'] } }.

Tu avais trouvé ?

Exercice 3 sur 3 Du SQL vers MongoDB Réussi À revoir

Traduis chaque requête SQL, en supposant des tables qui suivent la même structure que les collections.

  1. SELECT nom, prenom FROM visiteurs WHERE role = 'visiteur' AND region IN ('Rhône', 'Savoie') ORDER BY nom;
  2. SELECT COUNT(*) FROM fiches WHERE etat <> 'CR' AND mois BETWEEN '2026-08' AND '2026-09';
  3. SELECT matricule FROM visiteurs WHERE email LIKE '%.martin@%';
Voir la réponseMasquer la réponse de l'exercice 3
js
// 1. Girard, Martin, Roux
db.visiteurs.find(
  { role: 'visiteur', region: { $in: ['Rhône', 'Savoie'] } },
  { _id: 0, nom: 1, prenom: 1 }
).sort({ nom: 1 })

// 2. 5 : août (RB, VA) et septembre (VA, CL, MP)
db.fiches.countDocuments({ etat: { $ne: 'CR' }, mois: { $gte: '2026-08', $lte: '2026-09' } })

// 3. [ { matricule: 'a131' } ]
db.visiteurs.find({ email: /\.martin@/ }, { _id: 0, matricule: 1 })

Dans la 3, le point est échappé (\.) : dans une expression régulière, un point seul signifie « n'importe quel caractère ».

Tu avais trouvé ?

Chapitre terminé !

Fais les 3 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Modélisation MongoDB : imbriquer ou référencer ?Revoir le sommaire du cours