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.
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érateur | Signification | SQL | Exemple |
|---|---|---|---|
$eq | égal (implicite le plus souvent) | = | { etat: { $eq: 'CL' } }, ou { etat: 'CL' } |
$ne | différent | <> | { etat: { $ne: 'CR' } } |
$gt, $gte | supérieur, supérieur ou égal | >, >= | { montantValide: { $gte: 500 } } |
$lt, $lte | inférieur, inférieur ou égal | <, <= | { dateEmbauche: { $lt: ISODate('2020-01-01') } } |
$in | dans la liste | IN (...) | { region: { $in: ['Isère', 'Savoie'] } } |
$nin | hors de la liste | NOT IN (...) | { etat: { $nin: ['CR', 'CL'] } } |
// 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 })
[
{ 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 :
// 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érateur | Rôle | Quand l'utiliser |
|---|---|---|
| (implicite) | ET entre les champs d'un même objet | Presque toujours |
$and | ET explicite | Pour deux conditions qui ont la même clé (deux $or, par exemple), qu'un objet JavaScript ne peut pas contenir deux fois |
$or | OU | Conditions sur des champs différents |
$nor | Aucune des conditions | { $nor: [ { etat: 'CR' }, { etat: 'CL' } ] } : ni en cours, ni clôturée |
$not | Né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
| Filtre | Trouve 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.
// Les fiches qui ont au moins une ligne de nuitée
db.fiches.find({ 'lignesForfait.code': 'NUI' }, { _id: 0, visiteur: 1, mois: 1 })
[
{ 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.
// 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 :
db.fiches.find({ lignesForfait: { $elemMatch: { code: 'NUI', quantite: { $gte: 5 } } } })
// [] : aucune ligne de nuitée n'atteint 5 nuits
Les autres opérateurs de tableau :
| Opérateur | Trouve les documents dont le tableau… | Exemple |
|---|---|---|
| (égalité) | contient cette valeur | { 'lignesForfait.code': 'NUI' } |
$all | contient toutes ces valeurs, dans n'importe quel ordre | { 'lignesForfait.code': { $all: ['NUI', 'REP'] } } |
$size | a exactement ce nombre d'éléments | { lignesForfait: { $size: 0 } } : fiche sans ligne |
$elemMatch | a un élément qui satisfait toutes les conditions | voir ci-dessus |
$ne | ne 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.
// 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.
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 })
[
{ mois: '2026-09', fraisHorsForfait: [ { libelle: 'Repas client à Lyon' } ] },
{ mois: '2026-09', fraisHorsForfait: [ { libelle: 'Taxi Lyon Part-Dieu' } ] }
]
| SQL | MongoDB |
|---|---|
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 :
// 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.
- Les fiches validées, mises en paiement ou remboursées.
- Les visiteurs embauchés à partir de 2020 qui travaillent dans le Rhône.
- Les fiches qui ont une ligne kilométrique de plus de 300 km.
- Les fiches qui n'ont aucune ligne de nuitée.
- Les fiches qui ont au moins un frais hors forfait de plus de 30 €.
- Les fiches qui ont exactement 4 lignes de frais forfaitisés.
Voir la réponseMasquer la réponse de l'exercice 1
// 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.
db.fiches.find({ mois: { $gte: '2026-09' }, mois: { $lte: '2026-09' } }): l'auteur veut les fiches de septembre.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.db.fiches.find({ fraisHorsForfait: { libelle: 'Parking gare de Grenoble' } })db.visiteurs.find({ $or: [ { region: 'Rhône' }, { region: 'Isère' } ] })
Voir la réponseMasquer la réponse de l'exercice 2
- La clé
moisapparaî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' }. - 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). - É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' }. - Elle fonctionne, mais un
$orsur 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.
SELECT nom, prenom FROM visiteurs WHERE role = 'visiteur' AND region IN ('Rhône', 'Savoie') ORDER BY nom;SELECT COUNT(*) FROM fiches WHERE etat <> 'CR' AND mois BETWEEN '2026-08' AND '2026-09';SELECT matricule FROM visiteurs WHERE email LIKE '%.martin@%';
Voir la réponseMasquer la réponse de l'exercice 3
// 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é ?