$lookup în MongoDB & NoSQL

Agregare & Analytics MongoDB 3.2

Etapa $lookup aduce documente dintr-o altă colecție, ca un LEFT JOIN, și le pune într-un câmp tablou.

Sintaxă
{ $lookup: { from: <colectie>, localField: <camp>, foreignField: <camp>, as: <tablou> } }
{ $lookup: { from, let, pipeline: [ ... ], as } }

Ce face $lookup

$lookup caută în colecția from documentele la care foreignField este egal cu localField din documentul curent și le adaugă în tabloul as. Dacă nu găsește nimic, tabloul este gol, dar documentul rămâne (ca la LEFT JOIN în SQL).

Din MongoDB 3.6 există și forma cu pipeline (și let pentru variabile), care permite condiții mai complexe și filtrarea documentelor aduse; din 5.0 poți combina localField/foreignField cu un pipeline. Pune un index pe foreignField, altfel fiecare căutare scanează toată colecția străină.

Parametri

Parametru Descriere
from Colecția din care se aduc documente (aceeași bază de date).
localField / foreignField Câmpurile comparate pentru egalitate.
as Numele câmpului tablou cu rezultatele.
let / pipeline Variabile și pipeline pentru condiții complexe (MongoDB 3.6+).

Ce returnează

Documentele de intrare, fiecare cu un tablou nou (eventual gol).

Exemple

Exemplele de pe această pagină pornesc de la datele de mai jos. Rulează mai întâi acest cod, apoi exemplul.

use magazin
db.comenzi.insertMany([
  { _id: 1, client: 'Ana', oras: 'Iași', total: 120, status: 'livrata', produse: ['caiet', 'pix'] },
  { _id: 2, client: 'Dan', oras: 'Cluj', total: 85, status: 'noua', produse: ['carte'] },
  { _id: 3, client: 'Ioana', oras: 'Iași', total: 240, status: 'livrata', produse: ['rucsac', 'caiet', 'pix'] },
  { _id: 4, client: 'Mihai', oras: 'Brașov', total: 60, status: 'anulata', produse: [] }
])
db.clienti.insertMany([
  { _id: 'Ana', email: 'ana@exemplu.ro', nivel: 'gold' },
  { _id: 'Dan', email: 'dan@exemplu.ro', nivel: 'standard' }
])

Exemplul 1: Datele clientului lângă comandă

Pentru fiecare comandă se caută clientul cu _id egal cu numele din comandă.

db.comenzi.aggregate([
  { $match: { _id: { $in: [1, 2] } } },
  { $lookup: { from: 'clienti', localField: 'client', foreignField: '_id', as: 'detaliiClient' } },
  { $project: { client: 1, total: 1, detaliiClient: 1 } }
])

Ieșire în mongosh

[
  {
    _id: 1,
    client: 'Ana',
    total: 120,
    detaliiClient: [ { _id: 'Ana', email: 'ana@exemplu.ro', nivel: 'gold' } ]
  },
  {
    _id: 2,
    client: 'Dan',
    total: 85,
    detaliiClient: [ { _id: 'Dan', email: 'dan@exemplu.ro', nivel: 'standard' } ]
  }
]

Exemplul 2: Fără potrivire: tablou gol

Ioana nu există în clienti, dar comanda ei rămâne în rezultat.

db.comenzi.aggregate([{ $match: { _id: 3 } }, { $lookup: { from: 'clienti', localField: 'client', foreignField: '_id', as: 'detaliiClient' } }, { $project: { client: 1, detaliiClient: 1 } }])

Ieșire în mongosh

[ { _id: 3, client: 'Ioana', detaliiClient: [] } ]

Exemplul 3: Un singur câmp din documentul adus

Cu $unwind transformăm tabloul într-un obiect, apoi luăm doar emailul.

db.comenzi.aggregate([
  { $match: { _id: 1 } },
  { $lookup: { from: 'clienti', localField: 'client', foreignField: '_id', as: 'c' } },
  { $unwind: '$c' },
  { $project: { client: 1, email: '$c.email' } }
])

Ieșire în mongosh

[ { _id: 1, client: 'Ana', email: 'ana@exemplu.ro' } ]

Greșeli frecvente

  • Fără index pe foreignField, $lookup devine foarte lent pe colecții mari.
  • Rezultatul este mereu un tablou, chiar și când există un singur document potrivit; folosește $unwind sau $first ca să-l scoți din tablou.
  • Dacă ai nevoie mereu de datele împreună, ia în calcul să le ții în același document (modelare încorporată) în loc de $lookup la fiecare citire.

Comenzi înrudite

  • $unwind: Etapa $unwind desface un tablou: produce câte un document pentru fiecare element, ca să-l poți grupa sau filtra.
  • $match: Etapa $match păstrează în pipeline doar documentele care se potrivesc filtrului, cu aceeași sintaxă ca find().
  • $expr: Operatorul $expr permite expresii de agregare în find(), de exemplu compararea a două câmpuri din același document.