sql >> Base de Datos >  >> NoSQL >> MongoDB

Cómo unirse a dos colecciones adicionales con condiciones

Lo que te falta aquí es que $lookup produce una "matriz" en el campo de salida especificado por as en sus argumentos. Este es el concepto general de las "relaciones" de MongoDB, en el sentido de que una "relación" entre documentos se representa como una "subpropiedad" que está "dentro" del documento mismo, siendo singular o una "matriz" para muchos.

Dado que MongoDB es "sin esquema", la presunción general de $lookup es que quiere decir "muchos" y el resultado es, por lo tanto, "siempre" una matriz. Entonces, si busca el "mismo resultado que en SQL", entonces necesita $unwind esa matriz después de $lookup . Si es "uno" o "muchos" no tiene importancia, ya que sigue siendo "siempre" una matriz:

db.getCollection.('tb1').aggregate([
  // Filter conditions from the source collection
  { "$match": { "status": { "$ne": "closed" } }},

  // Do the first join
  { "$lookup": {
    "from": "tb2",
    "localField": "id",
    "foreignField": "profileId",
    "as": "tb2"
  }},

  // $unwind the array to denormalize
  { "$unwind": "$tb2" },

  // Then match on the condtion for tb2
  { "$match": { "tb2.profile_type": "agent" } },

  // join the second additional collection
  { "$lookup": {
    "from": "tb3",
    "localField": "tb2.id",
    "foreignField": "id",
    "as": "tb3"
  }},

  // $unwind again to de-normalize
  { "$unwind": "$tb3" },

  // Now filter the condition on tb3
  { "$match": { "tb3.status": 0 } },

  // Project only wanted fields. In this case, exclude "tb2"
  { "$project": { "tb2": 0 } }
])

Aquí debe anotar las otras cosas que faltan en la traducción:

La secuencia es "importante"

Las canalizaciones de agregación son más "sucintamente expresivas" que SQL. De hecho, se consideran mejor como "una secuencia de pasos" aplicado a la fuente de datos para cotejar y transformar los datos. El mejor análogo a esto son las instrucciones de línea de comando "entubadas", como:

ps -ef  | grep mongod | grep -v grep | awk '{ print $1 }'

Donde el "tubo" | se puede considerar como una "etapa de canalización" en una "canalización" de agregación de MongoDB.

Como tal, queremos $match para filtrar cosas de la colección "fuente" como nuestra primera operación. Y esto es generalmente una buena práctica, ya que elimina cualquier documento que no cumpliera con las condiciones requeridas de las condiciones adicionales. Al igual que lo que está sucediendo en nuestro ejemplo de "canalización de línea de comando", donde tomamos "entrada" y luego "tubería" a un grep para "eliminar" o "filtrar".

Los caminos importan

Donde lo siguiente que debe hacer aquí es "unirse" a través de $lookup . El resultado es una "matriz" de los elementos del "from" Argumento de colección coincidente con los campos proporcionados para generar en el "as" "ruta de campo" como una "matriz".

El nombre elegido aquí es importante, ya que ahora el "documento" de la colección de origen considera que todos los elementos de la "unión" existen ahora en esa ruta determinada. Para hacerlo más fácil, utilizo el mismo nombre de "colección" que "unirse" para la nueva "ruta".

Entonces, comenzando desde la primera "unión", la salida es "tb2" y que contendrá todos los resultados de esa colección. También hay algo importante a tener en cuenta con la siguiente secuencia de $unwind y luego $match , en cuanto a cómo MongoDB realmente procesa la consulta.

Ciertas Secuencias "realmente" importan

Dado que "parece" que hay "tres" etapas de canalización, siendo $lookup entonces $unwind y luego $match . Pero, de hecho, MongoDB realmente hace algo más, lo que se demuestra en el resultado de { "explain": true } añadido a .aggregate() comando:

    {
        "$lookup" : {
            "from" : "tb2",
            "as" : "tb2",
            "localField" : "id",
            "foreignField" : "profileId",
            "unwinding" : {
                "preserveNullAndEmptyArrays" : false
            },
            "matching" : {
                "profile_type" : {
                    "$eq" : "agent"
                }
            }
        }
    }, 
    {
        "$lookup" : {
            "from" : "tb3",
            "as" : "tb3",
            "localField" : "tb2.id",
            "foreignField" : "id",
            "unwinding" : {
                "preserveNullAndEmptyArrays" : false
            },
            "matching" : {
                "status" : {
                    "$eq" : 0.0
                }
            }
        }
    }, 

Entonces, aparte del primer punto de "secuencia" que se aplica donde necesita colocar el $match declaraciones donde se necesitan y hacen el "mayor bien", esto en realidad se vuelve "realmente importante" con el concepto de "uniones". Lo que hay que tener en cuenta aquí es que nuestras secuencias de $lookup entonces $unwind y luego $match , en realidad son procesados ​​por MongoDB como solo $lookup etapas, con las otras operaciones "agrupadas" en una etapa de canalización para cada una.

Esta es una distinción importante con otras formas de "filtrar" los resultados obtenidos por $lookup . Dado que en este caso, las condiciones reales de "consulta" en la "unión" de $match se realizan en la colección para unir "antes" de que los resultados se devuelvan al padre.

Esto en combinación con $unwind (que se traduce como unwinding ) como se muestra arriba, es cómo MongoDB realmente maneja la posibilidad de que la "unión" pueda resultar en la producción de una matriz de contenido en el documento de origen que hace que exceda el límite de BSON de 16 MB. Esto solo sucedería en los casos en que el resultado al que se une es muy grande, pero la misma ventaja está en el lugar donde se aplica el "filtro", estando en la colección de destino "antes" de que se devuelvan los resultados.

Es ese tipo de manejo el que mejor se "correlaciona" con el mismo comportamiento que SQL JOIN. Por lo tanto, también es la forma más efectiva de obtener resultados de un $lookup donde hay otras condiciones para aplicar a JOIN además de simplemente los valores de clave "locales" o "extranjeras".

También tenga en cuenta que el otro cambio de comportamiento proviene de lo que es esencialmente un LEFT JOIN realizado por $lookup donde el documento "origen" siempre se conservaría independientemente de la presencia de un documento coincidente en la colección "objetivo". En su lugar, $unwind se suma a esto al "descartar" cualquier resultado de la "fuente" que no tenga nada que coincida con el "objetivo" por las condiciones adicionales en $match .

De hecho, incluso se descartan de antemano debido al preserveNullAndEmptyArrays: false implícito que está incluido y descartaría cualquier cosa donde las claves "locales" y "foráneas" ni siquiera coincidieran entre las dos colecciones. Esto es algo bueno para este tipo particular de consulta, ya que la "unión" está destinada a la "igualdad" en esos valores.

Concluir

Como se señaló anteriormente, MongoDB generalmente trata las "relaciones" de manera muy diferente a cómo usaría una "Base de datos relacional" o RDBMS. El concepto general de "relaciones" es, de hecho, "incrustar" los datos, ya sea como una sola propiedad o como una matriz.

En realidad, puede desear tal salida, que también es parte de la razón por la que sin $unwind secuencia aquí la salida de $lookup es en realidad una "matriz". Sin embargo, usando $unwind en este contexto es en realidad lo más efectivo que se puede hacer, además de dar una garantía de que los datos "unidos" en realidad no causan que se exceda el límite de BSON antes mencionado como resultado de esa "unión".

Si realmente desea matrices de salida, lo mejor que puede hacer aquí sería usar el $group etapa de canalización, y posiblemente como etapas múltiples para "normalizar" y "deshacer los resultados" de $unwind

  { "$group": {
    "_id": "$_id",
    "tb1_field": { "$first": "$tb1_field" },
    "tb1_another": { "$first": "$tb1_another" },
    "tb3": { "$push": "$tb3" }    
  }}

Donde, de hecho, para este caso enumeraría todos los campos que necesita de "tb1" por sus nombres de propiedad usando $first para mantener solo la "primera" ocurrencia (esencialmente repetida por los resultados de "tb2" y "tb3" desenrollado) y luego $push el "detalle" de "tb3" en una "matriz" para representar la relación con "tb1" .

Pero la forma general de la canalización de agregación tal como se proporciona es la representación exacta de cómo se obtendrían los resultados del SQL original, que es una salida "desnormalizada" como resultado de la "unión". Depende de usted si desea "normalizar" los resultados nuevamente después de esto.