2017-10-11 3 views
5

J'essaie d'utiliser une requête d'agrégation mongodb pour joindre deux collections ($ lookup) et ensuite compter toutes les valeurs uniques dans le tableau joint. * Note: Je ne sais pas nécessairement quels champs (clés) sont dans le tableau metaDataMap. Et je ne veux pas compter ou inclure des champs qui pourraient exister ou non sur la carte. C'est pourquoi la requête d'agrégation ressemble à cela.Problème de taille et de vitesse du pipeline d'agrégation Mongodb

donc mes deux collections ressemblent à ceci: Events-

{ 
"_id" : "1", 
"name" : "event1", 
"objectsIds" : [ "1", "2", "3" ], 
} 

Objets

{ 
"_id" : "1", 
"name" : "object1", 
"metaDataMap" : { 
        "SOURCE" : ["ABC", "DEF"], 
        "DESTINATION" : ["XYZ", "PDQ"], 
        "TYPE" : [] 
       } 
}, 
{ 
"_id" : "2", 
"name" : "object2", 
"metaDataMap" : { 
        "SOURCE" : ["RST", "LNE"], 
        "TYPE" : ["text"] 
       } 
}, 
{ 
"_id" : "3", 
"name" : "object3", 
"metaDataMap" : { 
        "SOURCE" : ["NOP"], 
        "DESTINATION" : ["PHI", "NYC"], 
        "TYPE" : ["video"] 
       } 
} 

Mes résultats sont

{ 
_id:"SOURCE", count:5 
_id:"DESTINATION", count: 4 
_id:"TYPE", count: 2 
} 

Ce que j'est jusqu'à présent ceci:

db.events.aggregate([ 
{$match: {"_id" : id}} 

,{$lookup: {"from" : "objects", 
     "localField" : "objectsIds", 
     "foreignField" : "_id", 
     "as" : "objectResults"}} 

,{$unwind: "$objectResults"} //Line 1 
,{$project: {x: "$objectResults.metaDataMap"}} //Line 2 


,{$unwind: "$x"} 
,{$project: {"_id":0}} 

,{$project: {x: {$objectToArray: "$x"}}} 
,{$unwind: "$x"} 

,{$group: {_id: "$x.k", tmp: {$push: "$x.v"}}} 

,{$addFields: {tmp: {$reduce:{ 
input: "$tmp", 
initialValue:[], 
in:{$concatArrays: [ "$$value", "$$this"]} 
    }} 
}} 

,{$unwind: "$tmp"} 
,{$group: {_id: "$_id", uniqueVals: {$addToSet: "$tmp"}}} 

,{$addFields: {count: {"$size":"$uniqueVals"}}} 
,{$project: {_id: "$_id", count: "$count"}} 
]); 

Mon problème est ai-je été marqué ligne 1 & 2. Le travail ci-dessus, mais prend environ 50 secondes pour 25 000 valeurs dans les champs de tableau metaDataMap (objectsResults.metaDataMap). Ainsi, par exemple, avoir 25 000 valeurs dans le tableau de l'objet 1 metaDataMap SOURCE. C'est moyen de ralentir. Mon autre moyen plus rapide de le faire est de remplacer la ligne 1 & 2 avec:

,{$project: {x: "$objectResults.metaDataMap"}} //Line 1 
,{$unwind: "$x"} //Line 2 

C'est beaucoup plus rapide (moins de 3 secondes), mais ne peut être exécuté sur des ensembles de données qui ont ~ 10.000 articles ou moins. Quelque chose de plus élevé et je reçois une erreur disant "dépasse la taille maximale du document".

Aidez s'il vous plaît!

+0

Pourrait ajouter un peu plus de description autour de "25.000 éléments dans divers tableaux"? –

+1

Juste une pensée. Peut être que vous pouvez essayer de changer votre structure 'metaDataMap' en' 'metaDataMap": ["k": {"SOURCE", "v": ["ABC", "DEF"]} ... '' et insérer un ' $ map' stage après '$ lookup'. Quelque chose comme '{" $ project ": {" data ": {" $ map ": {" input ":" $ objectResults.metaDataMap "," as ":" resultom "," in ": {" $ map ": {"input": "$$ resultom", "comme": "resultim", "in": {"k": "$$ resultim.k", "v": {\t "$ size": \t "$ $ resultim.v "}}}}}}}}'. Je crois que de cette façon, vous pouvez obtenir la taille et le déroulement devrait être plus rapide. – Veeram

+0

Mais je ne vais pas avoir un compte distinct avec la taille. Vais-je? J'ai besoin de déduire les valeurs v. – Deckard

Répondre

0

Si vous êtes en mesure de modifier la conception de votre schéma sur la collection object d'inclure un champ parent_id, vous pouvez immédiatement retirer les 4 premières étapes de votre pipeline (la première $match, $lookup, $unwind et $project). Cela fera disparaître les préoccupations concernant Line 1 et Line 2.

Par exemple, un document dans la collection object ressemblerait à ceci:

{ 
    "_id": "1", 
    "name": "object1", 
    "metaDataMap": { 
    "SOURCE": [ 
     "ABC", 
     "DEF" 
    ], 
    "DESTINATION": [ 
     "XYZ", 
     "PDQ" 
    ], 
    "TYPE": [ ] 
    }, 
    "parent_id": "1" 
} 

Ainsi vous n'avez pas besoin coûteux $lookup et $unwind. Les 4 premières étapes peuvent être remplacées par:

{$match: {parent_id: id}} 

Sur la base de cette idée, je l'ai fait une optimisation plus poussée du pipeline, ce qui a entraîné:

db.objects.aggregate([ 
    {$match: {parent_id: id}} 
    ,{$project: {metaDataMap: {$filter: {input: {$objectToArray: '$metaDataMap'}, cond: {$ne: [[], '$$this.v']}}}}} 
    ,{$unwind: '$metaDataMap'} 
    ,{$unwind: '$metaDataMap.v'} 
    ,{$group: {_id: '$metaDataMap.k', val: {$addToSet: '$metaDataMap.v'}}} 
    ,{$project: {count: {$size: '$val'}}} 
]) 

Affichera:

{ "_id": "TYPE", "count": 2 } 
{ "_id": "DESTINATION", "count": 4 } 
{ "_id": "SOURCE", "count": 5 }