HOWTO · MongoDB

Cómo unir documentos con una canalización en el operador `$lookup` de MongoDB

Usa una canalización de MongoDB `$lookup` para unir documentos por `shopId`, exigir que falte un campo y filtrar los resultados sin coincidencias.

En esta página

Usa una canalización dentro de la etapa $lookup de MongoDB para unir documentos de collection2 con documentos de collection1 cuando sus valores de shopId coinciden y el documento de collection1 no tiene el campo shopPosId. $lookup realiza una unión externa izquierda, por lo que todos los documentos de entrada de collection2 permanecen en el resultado; si una fila no encuentra coincidencias, su arreglo shopDescription queda vacío. Añade una etapa $match después de $lookup si quieres obtener solo las filas con un documento unido.

Qué es una canalización de agregación

Una canalización de agregación procesa documentos mediante etapas ordenadas. Cada etapa puede filtrar o transformar su entrada y pasa los resultados a la siguiente. En este ejemplo, $lookup añade los documentos coincidentes y una etapa $match posterior puede filtrar los resultados de la unión. MongoDB también ofrece operaciones de map-reduce y de agregación de propósito único, pero no hacen falta para esta unión.

Consulta la descripción general de las canalizaciones de agregación de MongoDB para obtener más información.

Qué es el operador $lookup en MongoDB

$lookup une documentos de otra colección de la misma base de datos y escribe las coincidencias en un campo de tipo arreglo. La opción from indica la colección que se consultará, mientras que as especifica el nombre del arreglo que se añade a cada documento de entrada. La forma que usa una canalización permite aplicar condiciones a los documentos que se van a unir.

La referencia de $lookup de MongoDB describe las sintaxis disponibles. El ejemplo siguiente usa una canalización correlacionada: let expone un valor del documento de entrada para que la canalización lo utilice, y $expr permite que $match compare campos con ese valor. Los comandos de la shell de MongoDB de este artículo son ejemplos para ejecutar en una base de datos MongoDB; el entorno de ejecución configurado para el artículo no ejecuta operaciones de base de datos.

Cómo unir documentos con condiciones mediante una canalización en $lookup de MongoDB

Crea collection1 y collection2 con los siguientes documentos. La configuración se muestra como una ilustración de la shell de MongoDB; ejecuta cada instrucción en mongosh con la base de datos que quieras usar.

db.createCollection('collection1'); db.createCollection('collection2'); db.collection1.insertMany([{"shopId":"001","shopPosId":"001","description":"description for 001"},{"shopId":"002","description":"description for 002"},{"shopId":"003","shopPosId":"003","description":"description for 003"},{"shopId":"004","description":"description for 004"}]); db.collection2.insertMany([{"shopId":"001","shopPosId":"0078","clientUid":"474192"},{"shopId":"002","shopPosId":"0012","clientUid":"474193"},{"shopId":"003","shopPosId":"0034","clientUid":"474194"},{"shopId":"004","shopPosId":"0056","clientUid":"474195"}]);

El siguiente ejemplo independiente en Python modela la misma coincidencia con los datos de muestra: las tiendas 002 y 004 reciben descripciones unidas porque sus documentos de collection1 tienen valores de shopId coincidentes y carecen de shopPosId. Es una ilustración de la lógica del resultado, no una consulta de MongoDB. Los textos visibles del ejemplo y de su salida, como «description for 002», se mantienen en inglés.

Ejecuta este ejemplo:

collection1 = [
    {"shopId": "001", "shopPosId": "001", "description": "description for 001"},
    {"shopId": "002", "description": "description for 002"},
    {"shopId": "003", "shopPosId": "003", "description": "description for 003"},
    {"shopId": "004", "description": "description for 004"},
]
collection2 = [
    {"shopId": "001", "shopPosId": "0078", "clientUid": "474192"},
    {"shopId": "002", "shopPosId": "0012", "clientUid": "474193"},
    {"shopId": "003", "shopPosId": "0034", "clientUid": "474194"},
    {"shopId": "004", "shopPosId": "0056", "clientUid": "474195"},
]

for source in collection2:
    matches = [
        item["description"]
        for item in collection1
        if item["shopId"] == source["shopId"] and "shopPosId" not in item
    ]
    print(f'{source["shopId"]}: {matches}')
001: []
002: ['description for 002']
003: []
004: ['description for 004']

En la canalización de MongoDB, configura let: {shopId: "$shopId"} para pasar el ID de cada fila de collection2 a la canalización de búsqueda. Compara los valores con $expr: {$eq: ["$shopId", "$$shopId"]}; el prefijo $$ hace referencia a la variable definida por let. Combina esa comparación con {shopPosId: {$exists: false}} dentro de $and para exigir que el documento unido de collection1 no tenga el campo shopPosId.

La primera consulta correspondiente es db.collection2.aggregate([{$lookup: {from: "collection1", let: {shopId: "$shopId"}, pipeline: [{$match: {$and: [{$expr: {$eq: ["$shopId", "$$shopId"]}}, {shopPosId: {$exists: false}}]}}], as: "shopDescription"}}]). Conserva las cuatro filas de entrada de collection2. Las tiendas 001 y 003 no tienen ningún documento de collection1 que cumpla las condiciones, así que sus arreglos shopDescription quedan vacíos; las tiendas 002 y 004 sí coinciden porque sus documentos unidos carecen de shopPosId.

Para devolver solo las filas con coincidencias, añade una etapa $match que compruebe que el arreglo unido contiene al menos un elemento: {$match: {shopDescription: {$ne: []}}}. La consulta completa con esa etapa adicional es db.collection2.aggregate([{$lookup: {from: "collection1", let: {shopId: "$shopId"}, pipeline: [{$match: {$and: [{$expr: {$eq: ["$shopId", "$$shopId"]}}, {shopPosId: {$exists: false}}]}}], as: "shopDescription"}}, {$match: {shopDescription: {$ne: []}}}]). Con los datos de muestra, devuelve las filas de origen de las tiendas 002 y 004. La primera etapa, $lookup, sigue siendo una unión externa izquierda; la segunda etapa elimina las filas cuyo arreglo unido está vacío.