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

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

Múltiples JOINS en una sola consulta

Múltiples JOINS normalmente se asocian con múltiples colecciones, pero debe tener una comprensión básica de cómo funciona INNER JOIN (vea mis publicaciones anteriores sobre este tema). Además de nuestras dos colecciones que teníamos antes; unidades y estudiantes, agreguemos una tercera colección y etiquetémosla como deportes. Complete la colección de deportes con los siguientes datos:

{
    "_id" : 1,"tournamentsPlayed" : 6,
    "gamesParticipated" : [{"hockey" : "midfielder","football" : "stricker","handball" : "goalkeeper"}],
    "sportPlaces" : ["Stafford Bridge","South Africa", "Rio Brazil"]
}
{
    "_id" : 2,"tournamentsPlayed" : 3,
    "gamesParticipated" : [{"hockey" : "goalkeeper","football" : "stricker", "handball" : "midfielder"}],
    "sportPlaces" : ["Ukraine","India", "Argentina"]
}
{
    "_id" : 3,"tournamentsPlayed" : 10,
    "gamesParticipated" : [{"hockey" : "stricker","football" : "goalkeeper","tabletennis" : "doublePlayer"}],
    "sportPlaces" : ["China","Korea","France"]
}

Nos gustaría, por ejemplo, devolver todos los datos de un estudiante con un valor de campo _id igual a 1. Normalmente, escribiríamos una consulta para obtener el valor del campo _id de la colección de estudiantes, luego usaríamos el valor devuelto para consultar datos en las otras dos colecciones. En consecuencia, esta no será la mejor opción, especialmente si se trata de un gran conjunto de documentos. Un mejor enfoque sería utilizar la función SQL del programa Studio3T. Podemos consultar nuestro MongoDB con el concepto SQL normal y luego intentar ajustar el código de shell de Mongo resultante para que se ajuste a nuestra especificación. Por ejemplo, obtengamos todos los datos con _id igual a 1 de todas las colecciones:

SELECT  *
  FROM students
    INNER JOIN units
      ON students._id = units._id
    INNER JOIN sports
      ON students._id = sports._id
  WHERE students._id = 1;

El documento resultante será:

{ 
    "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"}
    }, 
    "sports" : {
        "_id" : NumberInt(1),"tournamentsPlayed" : NumberInt(6), 
        "gamesParticipated" : [{"hockey" : "midfielder", "football" : "striker","handball" : "goalkeeper"}], 
        "sportPlaces" : ["Stafford Bridge","South Africa","Rio Brazil"]
    }
}

Desde la pestaña Código de consulta, el código MongoDB correspondiente será:

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}}, 
        { "$lookup" : {"localField" : "students._id","from" : "sports", "foreignField" : "_id","as" : "sports"}}, 
        { "$unwind" : {"path" : "$sports", "preserveNullAndEmptyArrays" : false}}, 
        { "$match" : {"students._id" : NumberLong(1)}}
    ]
);

Mirando el documento devuelto, personalmente no estoy muy contento con la estructura de datos, especialmente con los documentos incrustados. Como puede ver, se devuelven campos _id y para las unidades es posible que no necesitemos que el campo de calificaciones se incruste dentro de las unidades.

Nos gustaría tener un campo de unidades con unidades incrustadas y ningún otro campo. Esto nos lleva a la parte de melodía gruesa. Al igual que en las publicaciones anteriores, copie el código usando el ícono de copiar provisto y vaya al panel de agregación, pegue el contenido usando el ícono de pegar.

Lo primero es lo primero, el operador $match debería ser la primera etapa, así que muévalo a la primera posición y tenga algo como esto:

Haga clic en la pestaña de la primera etapa y modifique la consulta a:

{
    "_id" : NumberLong(1)
}

Luego, debemos modificar aún más la consulta para eliminar muchas etapas de incrustación de nuestros datos. Para hacerlo, agregamos nuevos campos para capturar datos de los campos que queremos eliminar, es decir:

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

Como puede ver, en el proceso de ajuste fino, hemos introducido nuevas unidades de campo que sobrescribirán el contenido de la canalización de agregación anterior con calificaciones como un campo incrustado. Además, hemos creado un campo _id para indicar que los datos estaban relacionados con cualquier documento de las colecciones con el mismo valor. La última etapa de $project es eliminar el campo _id en el documento deportivo de modo que podamos tener una presentación ordenada de los datos como se muestra a continuación.

{  "_id" : NumberInt(1), 
    "students" : {"name" : "James Washington", "age" : 15.0,  "grade" : "A", "score" : 10.5}, 
    "units" : {"Maths" : "A","English" : "A", "Science" : "A","History" : "B"}, 
    "sports" : {
        "tournamentsPlayed" : NumberInt(6), 
        "gamesParticipated" : [{"hockey" : "midfielder","football" : "striker","handball" : "goalkeeper"}],  
        "sportPlaces" : ["Stafford Bridge", "South Africa", "Rio Brazil"]
        }
}

También podemos restringir qué campos deben devolverse desde el punto de vista de SQL. Por ejemplo, podemos devolver el nombre del estudiante, las unidades que este estudiante está haciendo y la cantidad de torneos jugados usando múltiples JOINS con el siguiente código:

SELECT  students.name, units.grades, sports.tournamentsPlayed
  FROM students
    INNER JOIN units
      ON students._id = units._id
    INNER JOIN sports
      ON students._id = sports._id
  WHERE students._id = 1;

Esto no nos da el resultado más adecuado. Entonces, como de costumbre, cópielo y péguelo en el panel de agregación. Realizamos ajustes con el código siguiente para obtener el resultado adecuado.

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

Este resultado de agregación del concepto SQL JOIN nos brinda una estructura de datos ordenada y presentable que se muestra a continuación.

{ 
    "name" : "James Washington", 
    "grades" : {"Maths" : "A", "English" : "A", "Science" : "A", "History" : "B"}, 
    "tournamentsPlayed" : NumberInt(6)
}

Bastante simple, ¿verdad? Los datos son bastante presentables como si estuvieran almacenados en una sola colección como un solo documento.

UNIÓN EXTERNA IZQUIERDA

La UNIÓN EXTERNA IZQUIERDA se usa normalmente para mostrar documentos que no se ajustan a la relación más retratada. El conjunto resultante de una combinación LEFT OUTER contiene todas las filas de ambas colecciones que cumplen los criterios de la cláusula WHERE, igual que un conjunto de resultados de INNER JOIN. Además, todos los documentos de la colección de la izquierda que no tengan documentos coincidentes en la colección de la derecha también se incluirán en el conjunto de resultados. Los campos que se seleccionan de la tabla del lado derecho devolverán valores NULL. Sin embargo, no se devolverán los documentos de la colección de la derecha que no coincidan con los criterios de la colección de la izquierda.

Echa un vistazo a estas dos colecciones:

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" : 4,"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"}

En la colección de estudiantes no tenemos el valor del campo _id establecido en 3, pero sí en la colección de unidades. Del mismo modo, no hay un valor de campo _id 4 en la colección de unidades. Si usamos la colección de estudiantes como nuestra opción izquierda en el enfoque JOIN con la siguiente consulta:

SELECT *
  FROM students
    LEFT OUTER JOIN units
      ON students._id = units._id

Con este código obtendremos el siguiente resultado:

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

El segundo documento no tiene el campo de unidades porque no había ningún documento coincidente en la colección de unidades. Para esta consulta SQL, el Código Mongo correspondiente será

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

Por supuesto, hemos aprendido sobre el ajuste fino, por lo que puede seguir adelante y reestructurar la canalización de agregación para lograr el resultado final que desea. SQL es una herramienta muy poderosa en lo que se refiere a la gestión de bases de datos. Es un tema amplio en sí mismo, también puede intentar usar las cláusulas IN y GROUP BY para obtener el código correspondiente para MongoDB y ver cómo funciona.

Conclusión

Acostumbrarse a una nueva tecnología (de base de datos) además de a la que está acostumbrado a trabajar puede llevar mucho tiempo. Las bases de datos relacionales siguen siendo más comunes que las no relacionales. Sin embargo, con la introducción de MongoDB, las cosas han cambiado y a la gente le gustaría aprenderlo lo más rápido posible debido a su potente rendimiento asociado.

Aprender MongoDB desde cero puede ser un poco tedioso, pero podemos usar el conocimiento de SQL para manipular datos en MongoDB, obtener el código relativo de MongoDB y ajustarlo para obtener los resultados más apropiados. Una de las herramientas disponibles para mejorar esto es Studio 3T. Ofrece dos funciones importantes que facilitan la operación de datos complejos, a saber:la función de consulta SQL y el editor de agregación. Las consultas de ajuste fino no solo garantizarán que obtenga el mejor resultado, sino que también mejorarán el rendimiento en términos de ahorro de tiempo.