web-dev-qa-db-fra.com

Agrégation MongoDB - correspondance si valeur dans un tableau

J'ai une collection que je fais une agrégation et je l'ai essentiellement obtenu jusqu'à 

{array:[1,2,3], value: 1},
{array:[1,2,3], value: 4}

Comment effectuer une correspondance d'agrégation pour vérifier si la valeur est dans le tableau? J'ai essayé d'utiliser {$match: {"array: {$in: ["$value"]}}} mais il ne trouve rien. 

Je voudrais que la sortie (si vous utilisez l'exemple ci-dessus) soit:

{array:[1,2,3], value:1}
17
eml002

Une légère variation basée sur la réponse de @ chridam:

db.test.aggregate([
    { "$unwind": "$array" },
    { "$group": {
                  _id: { "_id": "$_id", "value": "$value" },
                  array: { $Push: "$array" },
                  mcount: { $sum: {$cond: [{$eq: ["$value","$array"]},1,0]}}
                }
    },
    { $match: {mcount: {$gt: 0}}},
    { "$project": { "value": "$_id.value", "array": 1, "_id": 0 }}
])

L'idée est de $unwind et $group remonter le tableau, en comptant dans mcount le nombre d'éléments correspondant à la valeur. Après cela, un simple $match sur mcount > 0 éliminera les documents indésirables.

7
Sylvain Leroux

Comme indiqué, $where est une bonne option lorsque vous n'avez pas besoin de poursuivre la logique dans le pipeline d'agrégation.

Mais si vous le faites ensuite, utilisez $redact , avec $map pour transformer la "valeur" en un tableau et utilisez $setIsSubSet pour comparer. C’est le moyen le plus rapide de le faire car il n’est pas nécessaire de dupliquer les documents avec $unwind :

db.collection.aggregate([
   { "$redact": {
       "$cond": {
           "if": { "$setIsSubset": [
                { "$map": {
                    "input": { "$literal": ["A"] },
                    "as": "a",
                    "in": "$value"
                }},
                "$array"
           ]},
           "then": "$$KEEP",
           "else": "$$Prune"
       }
   }}
])

L'opérateur de pipeline $redact permet de traiter une condition logique entre $cond et utilise les opérations spéciales $$KEEP pour "conserver" le document contenant la condition logique. true ou $$Prune pour "supprimer" le document où la condition était fausse.

Cela lui permet de fonctionner comme $project avec un $match ultérieur, mais dans une seule étape de pipeline qui est plus efficace.

Étant donné qu'il s'agit d'opérateurs natifs et non de JavaScript, il s'agit probablement du "moyen" le plus rapide d'effectuer votre correspondance. Donc, à condition que vous utilisiez une version de MongoDB 2.6 ou une version ultérieure, procédez comme suit pour comparer ces éléments dans votre document.

13
Blakes Seven

Un peu tard pour répondre mais cela présente une autre solution:

En utilisant addFields et en faisant une correspondance séparée, cela donne plus de flexibilité que la rédaction Vous pouvez exposer plusieurs champs, puis utiliser ensemble une autre logique de correspondance basée sur les résultats.

db.applications.aggregate([
    {$addFields: {"containsValueInArray": {$cond:[{$setIsSubset: [["valueToMatch"], "$arrayToMatchIn"]},true,false]}}},
    {$match: {"containsValueInArray":true}}
]);
5
Ralph

Une approche plus efficace impliquerait un seul pipeline utilisant l'opérateur $redact comme suit:

db.collection.aggregate([
    { 
        "$redact": {
            "$cond": [
                { 
                    "$setIsSubset": [ 
                        ["$value"],
                        "$array"  
                    ] 
                },
                "$$KEEP",
                "$$Prune"
            ]
        }
    }
])

Pour les versions antérieures de MongoDB qui ne prennent pas en charge $redact (versions <2.6), envisagez ce pipeline d'agrégation qui utilise l'opérateur $unwind:

db.collection.aggregate([
    { "$unwind": "$array" },
    {
        "$project": {
            "isInArray": {
                "$cond": [
                    { "$eq": [ "$array", "$value" ] },
                    1,
                    0
                ]
            },
            "value": 1,
            "array": 1
        }
    },
    { "$sort": { "isInArray": -1 } },
    {
        "$group": {
            "_id": {
                "_id": "$_id",
                "value": "$value"
            },
            "array": { "$Push": "$array" },
            "isInArray": { "$first": "$isInArray" }
        }
    },
    { "$match": { "isInArray": 1 } },
    { "$project": { "value": "$_id.value", "array": 1, "_id": 0 } }
])
5
chridam

Vous pouvez utiliser une expression d'agrégation dans une requête standard dans la version 3.6.

db.collection_name.find({"$expr": {"$in": ["$value", "$array"]}})

Utilisation de l'agrégation:

Vous pouvez utiliser $match + $expr dans la version actuelle de 3.6.

db.collection_name.aggregate({"$match": {"$expr": {"$in": ["$value", "$array"]}}})

Vous pouvez essayer l'expression $redact + $in dans la version 3.4.

db.collection_name.aggregate({
  "$redact": {
    "$cond": [
      {
        "$in": [
          "$value",
          "$array"
        ]
      },
      "$$KEEP",
      "$$Prune"
    ]
  }
})
3
Veeram

Vous pouvez utiliser $where si l'agrégation n'est pas required

db.collection.find({ $where: function(){ 
    return (this.array.indexOf(this.value) !== -1)}
})
2
styvane

Essayez la combinaison de $ eq et $ setIntersection

{$group :{
  _id: "$id",
  yourName :  { $sum:
  { $cond :[
       {$and : [
          {$eq:[{$setIntersection : ["$someArrayField", ["$value"]]  },["$value"]]}
         ]
      },1,0]
  }

} }

1
Richard Mao