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

Vinculación y creación de uniones MongoDB usando SQL:Parte 2

JOIN es una de las características distintivas clave entre las bases de datos SQL y NoSQL. En las bases de datos SQL, podemos realizar un JOIN entre dos tablas dentro de la misma o diferentes bases de datos. Sin embargo, este no es el caso de MongoDB, ya que permite operaciones JOIN entre dos colecciones en la misma base de datos.

La forma en que se presentan los datos en MongoDB hace que sea casi imposible relacionarlos de una colección a otra, excepto cuando se utilizan funciones básicas de consulta de secuencias de comandos. MongoDB desnormaliza los datos almacenando elementos relacionados en un documento separado o relaciona los datos en algún otro documento separado.

Uno podría relacionar estos datos usando referencias manuales como el campo _id de un documento que se guarda en otro documento como referencia. Sin embargo, es necesario realizar varias consultas para obtener algunos datos necesarios, lo que hace que el proceso sea un poco tedioso.

Por lo tanto, decidimos utilizar el concepto JOIN que facilita la relación de los datos. La operación JOIN en MongoDB se logra mediante el uso del operador $lookup, que se introdujo en la versión 3.2.

Operador $búsqueda

La idea principal detrás del concepto JOIN es obtener una correlación entre los datos de una colección y otra. La sintaxis básica del operador $búsqueda es:

{
   $lookup:
     {
       from: <collection to join>,
       localField: <field from the input documents>,
       foreignField: <field from the documents of the "from" collection>,
       as: <output array field>
     }
}

Con respecto al conocimiento de SQL, siempre sabemos que el resultado de una operación JOIN es una fila separada que vincula todos los campos de la tabla local y foránea. Para MongoDB, este es un caso diferente en el que los documentos de resultados se agregan como una matriz de documentos de colección local. Por ejemplo, tengamos dos colecciones; 'estudiantes' y 'unidades'

estudiantes

{"_id" : 1,"name" : "James Washington","age" : 15.0,"grade" : "A","score" : 10.5}
{"_id" : 2,"name" : "Clinton Ariango","age" : 14.0,"grade" : "B","score" : 7.5}
{"_id" : 3,"name" : "Mary Muthoni","age" : 16.0,"grade" : "A","score" : 11.5}

Unidades

{"_id" : 1,"Maths" : "A","English" : "A","Science" : "A","History" : "B"}
{"_id" : 2,"Maths" : "B","English" : "B","Science" : "A","History" : "B"}
{"_id" : 3,"Maths" : "A","English" : "A","Science" : "A","History" : "A"}

Podemos recuperar las unidades de los estudiantes con las calificaciones respectivas usando el operador de $búsqueda con el enfoque JOIN .i.e

db.getCollection('students').aggregate([{
$lookup:
    {
        from: "units",
        localField: "_id",
        foreignField : "_id",
        as: "studentUnits"
    }
}])

Lo que nos dará los siguientes resultados:

{"_id" : 1,"name" : "James Washington","age" : 15,"grade" : "A","score" : 10.5,
    "studentUnits" : [{"_id" : 1,"Maths" : "A","English" : "A","Science" : "A","History" : "B"}]}
{"_id" : 2,"name" : "Clinton Ariango","age" : 14,"grade" : "B","score" : 7.5,
    "studentUnits" : [{"_id" : 2,"Maths" : "B","English" : "B","Science" : "A","History" : "B"}]}
{"_id" : 3,"name" : "Mary Muthoni","age" : 16,"grade" : "A","score" : 11.5,
    "studentUnits" : [{"_id" : 3,"Maths" : "A","English" : "A","Science" : "A","History" : "A"}]}

Como se mencionó anteriormente, si hacemos un JOIN usando el concepto SQL, se nos devolverá con documentos separados en la plataforma Studio3T .i.e

SELECT *
  FROM students
    INNER JOIN units
      ON students._id = units._id

Es un equivalente de

db.getCollection("students").aggregate(
    [
        { 
            "$project" : {
                "_id" : NumberInt(0), 
                "students" : "$$ROOT"
            }
        }, 
        { 
            "$lookup" : {
                "localField" : "students._id", 
                "from" : "units", 
                "foreignField" : "_id", 
                "as" : "units"
            }
        }, 
        { 
            "$unwind" : {
                "path" : "$units", 
                "preserveNullAndEmptyArrays" : false
            }
        }
    ]
);

La consulta SQL anterior devolverá los resultados a continuación:

{ "students" : {"_id" : NumberInt(1),"name" : "James Washington","age" : 15.0,"grade" : "A","score" : 10.5}, 
    "units" : {"_id" : NumberInt(1),"Maths" : "A","English" : "A","Science" : "A","History" : "B"}}
{ "students" : {"_id" : NumberInt(2), "name" : "Clinton Ariango","age" : 14.0,"grade" : "B","score" : 7.5 }, 
    "units" : {"_id" : NumberInt(2),"Maths" : "B","English" : "B","Science" : "A","History" : "B"}}
{ "students" : {"_id" : NumberInt(3),"name" : "Mary Muthoni","age" : 16.0,"grade" : "A","score" : 11.5},
"units" : {"_id" : NumberInt(3),"Maths" : "A","English" : "A","Science" : "A","History" : "A"}}

La duración del rendimiento obviamente dependerá de la estructura de su consulta. Por ejemplo, si tiene muchos documentos en una colección sobre otra, debe hacer la agregación de la colección con menos documentos y luego buscar en la que tiene más documentos. De esta forma, una búsqueda del campo elegido de la colección de documentos menores es bastante óptima y toma menos tiempo que realizar búsquedas múltiples para un campo elegido en la colección con más documentos. Por lo tanto, es recomendable poner primero la colección más pequeña.

Para una base de datos relacional, el orden de las bases de datos no importa, ya que la mayoría de los intérpretes de SQL tienen optimizadores, que tienen acceso a información adicional para decidir cuál debe ser la primera.

En el caso de MongoDB, necesitaremos usar un índice para facilitar la operación JOIN. Todos sabemos que todos los documentos de MongoDB tienen una clave _id que para un DBM relacional puede considerarse como la clave principal. Un índice proporciona una mejor oportunidad de reducir la cantidad de datos a los que se debe acceder además de respaldar la operación cuando se usa en la clave externa $lookup.

En la canalización de agregación, para usar un índice, debemos asegurarnos de que $match se realice en la primera etapa para filtrar los documentos que no coincidan con los criterios. Por ejemplo, si queremos recuperar el resultado del estudiante con un valor de campo _id igual a 1:

select * 
from students 
  INNER JOIN units 
    ON students._id = units._id 
      WHERE students._id = 1;

El código MongoDB equivalente que obtendrá en este caso es:

db.getCollection("students").aggregate(
[{"$project" : { "_id" : NumberInt(0), "students" : "$$ROOT" }}, 
     {  "$lookup" : {"localField" : "students._id", "from" : "units",  "foreignField" : "_id",  "as" : "units"} }, 
     { "$unwind" : { "path" : "$units","preserveNullAndEmptyArrays" : false } }, 
      { "$match" : {"students._id" : NumberLong(1) }}
    ]);

El resultado devuelto por la consulta anterior será:

{"_id" : 1,"name" : "James Washington","age" : 15,"grade" : "A","score" : 10.5,
    "studentUnits" : [{"_id" : 1,"Maths" : "A","English" : "A","Science" : "A","History" : "B"}]}

Cuando no usamos la etapa $match o mejor dicho no en la primera etapa, si verificamos con la función de explicación, obtendremos la etapa COLLSCAN también incluida. Hacer un COLLSCAN para un gran conjunto de documentos generalmente llevará mucho tiempo. Por lo tanto, decidimos usar un campo de índice que en la función de explicación involucra solo la etapa IXSCAN. Este último tiene una ventaja ya que estamos revisando un índice en los documentos y no escaneando todos los documentos; no tardará en devolver los resultados. Puede tener una estructura de datos diferente como:

{    "_id" : NumberInt(1), 
    "grades" : {"Maths" : "A", "English" : "A",  "Science" : "A", "History" : "B"
    }
}

Es posible que deseemos devolver las calificaciones como entidades diferentes en una matriz en lugar de un campo de calificaciones incrustado completo.

Después de escribir la consulta SQL anterior, debemos modificar el código MongoDB resultante. Para hacerlo, haga clic en el icono de copia a la derecha como se muestra a continuación para copiar el código de agregación:

A continuación, vaya a la pestaña de agregación y, en el panel presentado, hay un icono para pegar, haga clic en él para pegar el código.

Haga clic en la fila $match y luego en la flecha hacia arriba verde para mover el escenario a la parte superior como el primer escenario. Sin embargo, primero deberá crear un índice en su colección como:

db.students.createIndex(
   { _id: 1 },
   { name: studentId }
)

Obtendrá el ejemplo de código a continuación:

db.getCollection("students").aggregate(
    [{ "$match" : {"_id" : 1.0}},
  { "$project" : {"_id" : NumberInt(0),"students" : "$$ROOT"}}, 
      { "$lookup" : {"localField" : "students._id","from" : "units","foreignField" : "_id","as" : "units"}}, 
      { "$unwind" : {"path" : "$units", "preserveNullAndEmptyArrays" : false}}
    ]
Varios nueves Conviértase en un administrador de bases de datos de MongoDB - Llevando MongoDB a la producción Obtenga información sobre lo que necesita saber para implementar, monitorear, administrar y escalar MongoDBDescargar gratis

Con este código obtendremos el siguiente resultado:

{ "students" : {"_id" : NumberInt(1), "name" : "James Washington","age" : 15.0,"grade" : "A", "score" : 10.5}, 
    "units" : {"_id" : NumberInt(1), "grades" : {"Maths" : "A", "English" : "A", "Science" : "A",  "History" : "B"}}}

Pero todo lo que necesitamos es tener las calificaciones como una entidad de documento separada en el documento devuelto y no como en el ejemplo anterior. Por lo tanto, agregaremos la etapa $addfields y, por lo tanto, el código que se muestra a continuación.

db.getCollection("students").aggregate(
    [{ "$match" : {"_id" : 1.0}},
  { "$project" : {"_id" : NumberInt(0),"students" : "$$ROOT"}}, 
      { "$lookup" : {"localField" : "students._id","from" : "units","foreignField" : "_id","as" : "units"}}, 
      { "$unwind" : {"path" : "$units", "preserveNullAndEmptyArrays" : false}},
      { "$addFields" : {"units" : "$units.grades"} }]

Los documentos resultantes serán entonces:

{
"students" : {"_id" : NumberInt(1), "name" : "James Washington", "grade" : "A","score" : 10.5}, 
     "units" : {"Maths" : "A", "English" : "A",  "Science" : "A", "History" : "B"}
}

Los datos devueltos son bastante claros, ya que hemos eliminado los documentos incrustados de la colección de unidades como un campo separado.

En nuestro próximo tutorial, analizaremos las consultas con varias combinaciones.