mongodb

mongodb - Consultas jerárquicas con Mongo usando $ graphLookup



(3)

Tengo una colección de empleados con medio millón de registros. Cada registro tendrá los siguientes detalles.

El documento mongo es el siguiente.

{ "_id": "234463456453643563456", "name": "Mike", "empId": "10", "managerId": "8", "projects" : [ "123", "456", "789"] }

  1. Cuando doy un vacío, debería devolver las jerarquías completas de ese administrador al nivel inferior junto con el siguiente filtro.

a. filtrar en la ubicación
si. filtrar en proyectos

El resultado debería ser como,

10 ->>> Manager // / / 8 6 ---->> 8 & 6 reporting to manager 10 // // / / / / 4 5 2 1 ---->> 4 & 5 reporting to manager 8 ...

¿Se apreciará alguna ayuda para obtener los resultados jerárquicos con el nivel?

No puedo obtener el resultado como se esperaba.

Data de muestra :-

db.getCollection("employees").insert({"_id":"10","empId": "10","name":"Employee10","managerId":"15" }); db.getCollection("employees").insert({"_id":"8","empId": "8","name":"Employee8","managerId":"10" }); db.getCollection("employees").insert({"_id":"6","empId": "6","name":"Employee6","managerId":"10" }); db.getCollection("employees").insert({"_id":"4","empId": "4","name":"Employee4","managerId":"8" }); db.getCollection("employees").insert({"_id":"5","empId": "5","name":"Employee5","managerId":"8" }); db.getCollection("employees").insert({"_id":"2","empId": "2","name":"Employee2","managerId":"6" }); db.getCollection("employees").insert({"_id":"1","empId": "1","name":"Employee1","managerId":"6" });

Consulta :-

db.getCollection(''employees'').aggregate([ { $match: { empId : "10" } }, { $graphLookup: { from: "employees", startWith: "$empId", connectFromField: "empId", connectToField: "managerId", as: "reportees", maxDepth: 4, depthField: "level" } }, { $project: { "empId":1, "managerId":1, "reportees.empId":1, "reportees.name":1, "reportees.managerId":1, "reportees.level":1 } } ]);

Resultado actual :-

{ "_id" : "10", "empId" : "10", "managerId" : "15", "reportees" : [ { "empId" : "1", "name" : "Employee1", "managerId" : "6", "level" : NumberLong(1) }, { "empId" : "4", "name" : "Employee4", "managerId" : "8", "level" : NumberLong(1) }, { "empId" : "2", "name" : "Employee2", "managerId" : "6", "level" : NumberLong(1) }, { "empId" : "5", "name" : "Employee5", "managerId" : "8", "level" : NumberLong(1) }, { "empId" : "6", "name" : "Employee6", "managerId" : "10", "level" : NumberLong(0) }, { "empId" : "8", "name" : "Employee8", "managerId" : "10", "level" : NumberLong(0) } ] }

Resultado Esperado :-

{ "_id" : "10", "empId" : "10", "managerId" : "15", "reportees" : [ { "empId" : "6", "name" : "Employee6", "managerId" : "10", "level" : NumberLong(0), "reportees" : [ { "empId" : "1", "name" : "Employee1", "managerId" : "6", "level" : NumberLong(1) }, { "empId" : "2", "name" : "Employee2", "managerId" : "6", "level" : NumberLong(1) } ] }, { "empId" : "8", "name" : "Employee8", "managerId" : "10", "level" : NumberLong(0), "reportees" : [ { "empId" : "5", "name" : "Employee5", "managerId" : "8", "level" : NumberLong(1) }, { "empId" : "4", "name" : "Employee4", "managerId" : "8", "level" : NumberLong(1) } ] } ] }

Preguntas:

  1. ¿Es posible obtener el resultado esperado con $ graphLookup?
  2. Además, ¿es posible obtener el recuento en el nivel superior y también para cada nivel secundario?
  3. ¿Cómo aplicar la proyección a todos los niveles?
  4. ¿Cómo aplicar el filtro encima de esto?

Creo que tener un campo de nivel podemos construir una estructura jerárquica a partir de una matriz usando $reduce . Para lograr eso, necesitamos obtener reportees ordenados por nivel descendente después de $graphLookup . Desafortunadamente, la única forma de hacerlo actualmente es usar $unwind + $sort + $group que hace que la agregación sea bastante larga.

Entonces podemos procesar esa matriz ordenada usando $reduce . En cada paso solo tenemos que agregar un empleado al conjunto de resultados, incluidos sus reportees del nivel anterior. Además, debemos detectar cuándo cambia el level durante nuestro procesamiento y reorganizar las matrices auxiliares en ese caso.

$addFields simplemente reemplaza el campo de reportees existente en este caso. $concatArrays nos permite agregar al empleado actual ( $$this ) al resultado. Usando $filter podemos obtener reportees del nivel inferior.

db.getCollection(''employees'').aggregate([ { $match: { empId : "10" } }, { $graphLookup: { from: "employees", startWith: "$empId", connectFromField: "empId", connectToField: "managerId", as: "reportees", maxDepth: 4, depthField: "level" } }, { $project: { "empId":1, "managerId":1, "reportees.empId":1, "reportees.name":1, "reportees.managerId":1, "reportees.level":1 } }, { $unwind: "$reportees" }, { $sort: { "reportees.level": -1 } }, { $group: { _id: "$_id", empId: { $first: "$empId" }, managerId: { $first: "$managerId" }, reportees: { $push: "$reportees" } } }, { $addFields: { reportees: { $reduce: { input: "$reportees", initialValue: { currentLevel: -1, currentLevelEmployees: [], previousLevelEmployees: [] }, in: { $let: { vars: { prev: { $cond: [ { $eq: [ "$$value.currentLevel", "$$this.level" ] }, "$$value.previousLevelEmployees", "$$value.currentLevelEmployees" ] }, current: { $cond: [ { $eq: [ "$$value.currentLevel", "$$this.level" ] }, "$$value.currentLevelEmployees", [] ] } }, in: { currentLevel: "$$this.level", previousLevelEmployees: "$$prev", currentLevelEmployees: { $concatArrays: [ "$$current", [ { $mergeObjects: [ "$$this", { reportees: { $filter: { input: "$$prev", as: "e", cond: { $eq: [ "$$e.managerId", "$$this.empId" ] } } } } ] } ] ] } } } } } } } }, { $addFields: { reportees: "$reportees.currentLevelEmployees" } } ]).pretty()

La solución anterior debería funcionar para múltiples niveles. Salidas:

{ "_id" : "10", "empId" : "10", "managerId" : "15", "reportees" : [ { "empId" : "6", "name" : "Employee6", "managerId" : "10", "level" : NumberLong(0), "reportees" : [ { "empId" : "1", "name" : "Employee1", "managerId" : "6", "level" : NumberLong(1), "reportees" : [ ] }, { "empId" : "2", "name" : "Employee2", "managerId" : "6", "level" : NumberLong(1), "reportees" : [ ] } ] }, { "empId" : "8", "name" : "Employee8", "managerId" : "10", "level" : NumberLong(0), "reportees" : [ { "empId" : "5", "name" : "Employee5", "managerId" : "8", "level" : NumberLong(1), "reportees" : [ ] }, { "empId" : "4", "name" : "Employee4", "managerId" : "8", "level" : NumberLong(1), "reportees" : [ ] } ] } ] }


La documentación oficial en $ graphLookup puede proporcionar ayuda más o menos.

$graphLookup

Solo un amable recordatorio.


Precisamente para eso sería $graphLookup (el bit transversal al menos). Para la parte de filtrado, simplemente puede usar $filter o $match dependiendo de cómo desea filtrar exactamente.

Eche un vistazo a los resultados de esta consulta:

db.employees.aggregate({ $graphLookup: { from: "employees", startWith: "$managerId", connectFromField: "managerId", connectToField: "empId", as: "managers", } })

ACTUALIZACIÓN 1 según su aclaración:

Para obtener la estructura jerárquica que le gustaría obtener, puede hacer lo siguiente. Sin embargo, no lo llamaría una solución bonita, ya que requiere que defina estáticamente el número de niveles que desea bajar y también que repita las secciones, pero hace el trabajo para su ejemplo. No estoy seguro, si / con qué facilidad esto se puede extender a más niveles, tampoco. Personalmente, creo que una solución de bucle del lado del cliente sería más adecuada para este tipo de trabajo:

db.employees.aggregate([ { $match: { empId : "10" } }, // level 0 { $graphLookup: { from: "employees", startWith: "$empId", connectFromField: "empId", connectToField: "managerId", as: "reportees", maxDepth: 0 } }, { $unwind: "$reportees" // flatten }, { $addFields: { "reportees.level": 0 // add level field } }, // level 1 { $graphLookup: { from: "employees", startWith: "$reportees.empId", connectFromField: "reportees.empId", connectToField: "managerId", as: "reportees.reportees", maxDepth: 0 } }, { $group: { // group previously flattened documents back together _id: "$_id", empId: { $first: "$empId" }, name: { $first: "$name" }, managerId: { $first: "$managerId" }, reportees: { $push: "$reportees" }, } }, { $addFields: { "reportees.reportees.level": 1 // add level field } } ])

ACTUALIZACIÓN 2:

La siguiente consulta lo lleva a donde desea estar desde el punto de vista de la estructura de salida (omití el campo de level , pero debería ser fácil de agregar). Sin embargo, no es particularmente bonito y, nuevamente, requiere que defina una profundidad organizacional máxima por adelantado.

db.employees.aggregate([ { $match: { empId : "10" } }, { $graphLookup: { // get the relevant documents out of our universe of employees from: "employees", startWith: "$empId", connectFromField: "empId", connectToField: "managerId", as: "reportees" } }, { $project: { // add the employee we are interested in into the array of employees we''re looking at _id: 0, reportees: { $concatArrays: [ "$reportees", [ { _id: "$_id", empId: "$empId", name: "$name", managerId: "$managerId" } ] ] } } }, { $project: { reportees: { $let: { vars: { managers: { $filter: { // remove employees with no reportess so keep managers only input: { $map: { input: "$reportees", as: "this", in: { $mergeObjects: [ "$$this", { reportees: { $filter: { // extract reportees from list of employees input: "$reportees", as: "that", cond: { $eq: [ "$$this._id", "$$that.managerId" ] } } } } ] } } }, as: "this", cond: { $ne: [ "$$this.reportees", [] ] } } } }, in: { $cond: [ // this is to break the processing once we have reached a top level manager { $eq: [ "$$managers", [] ] }, "$reportees", "$$managers" ] } } } } }, // second level: exactly identical to the previous stage // third level: exactly identical to the previous stage // basically, from here onwards you would need to repeat an exact copy of the previous stage to go one level deeper ]);