2017-09-20 3 views
1

J'utilise la collection suivante qui représente les tournois sports> catégories>.Faire correspondre des éléments dans des tableaux incorporés à plusieurs niveaux

{ 
    "_id" : ObjectId("597846358bbbc4440895f2e8"), 
    "Name" : [ 
     { "k" : "en-US", "v" : "Soccer" }, 
     { "k" : "fr-FR", "v" : "Football" } 
    ], 
    "Categories" : [ 
     { 
      "Name" : [ 
       { "k" : "en-US", "v" : "France" }, 
       { "k" : "fr-FR", "v" : "France" } 
      ], 
      "Tournaments" : [ 
       { 
        "Name" : [ 
         { "k" : "en-US", "v" : "Ligue 1" }, 
         { "k" : "fr-FR", "v" : "Ligue 1" } 
        ], 
       }, 
       { 
        "Name" : [ 
         { "k" : "en-US", "v" : "Ligue 2" }, 
         { "k" : "fr-FR", "v" : "Ligue 2" } 
        ], 
       } 
      ] 
     }, 
     { 
      "Name" : [ 
       { "k" : "en-US", "v" : "England" }, 
       { "k" : "fr-FR", "v" : "Angleterre" } 
      ], 
      "Tournaments" : [ 
       { 
        "Name" : [ 
         { "k" : "en-US", "v" : "Premier League" }, 
         { "k" : "fr-FR", "v" : "Premier League" } 
        ], 
       }, 
       { 
        "Name" : [ 
         { "k" : "en-US", "v" : "Championship" }, 
         { "k" : "fr-FR", "v" : "Championnat" } 
        ], 
       } 
      ] 
     }, 
    ] 
} 

Je veux interroger la collection en utilisant le nom de la catégorie et le nom du tournoi. J'ai utilise avec succès « $ elemMatch » avec le code suivant:

db.getCollection('Sport').find({ 
    Categories: { 
     $elemMatch: { 
      Name: { 
       $elemMatch: { v: "France" } 
      }, 
      Tournaments: { 
       $elemMatch: { 
        Name: { 
         $elemMatch: { v: "Ligue 1" } 
        } 
       } 
      } 
     } 
    } }, 
    { "Categories.$": 1, Name: 1 }) 

Cependant, je ne peux pas recevoir que le tournoi correspondant dans l'objet de la catégorie. En utilisant la réponse à cette question: MongoDB Projection of Nested Arrays, j'ai construit une agrégation:

db.getCollection('Sport').aggregate([{ 
      "$match": { 
       "Categories": { 
        "$elemMatch": { 
         "Name": { 
          "$elemMatch": { 
           "v": "France" 
          } 
         }, 
         "Tournaments": { 
          "$elemMatch": { 
           "Name": { 
            "$elemMatch": { 
             "v": "Ligue 1" 
            } 
           } 
          } 
         } 
        } 
       } 
      } 
     }, { 
      "$addFields": { 
       "Categories": { 
        "$filter": { 
         "input": { 
          "$map": { 
           "input": "$Categories", 
           "as": "category", 
           "in": { 
            "Tournaments": { 
             "$filter": { 
              "input": "$$category.Tournaments", 
              "as": "tournament", 
              "cond": { 
               // stuck here 
              } 
             } 
            } 
           } 
          } 
         }, 
         "as": "category", 
         "cond": { 
          // stuck here 
         } 
        } 
       } 
      } 
     } 
    ]) 

J'ai essayé d'utiliser une condition, mais MongoDB ne reconnaît pas (Utilisation de la variable non définie:) $$ Cons et $ $ PRUNE ($redact) quand j'utilise $ anyElementTrue puis $ map sur la propriété "Name".

Ma question: Comment puis-je vérifier que la collection de noms contient ma chaîne?

Répondre

0

Je suis plus surpris que sur la réponse que vous référencez, je n'ai pas "fortement recommandé de ne pas imbriquer des tableaux" comme ceci. L'imbrication de cette façon est impossible à mettre à jour de manière atomique jusqu'à la prochaine version de MongoDB, et ils sont notoirement difficiles à interroger.

Pour ce cas particulier que vous feriez:

db.getCollection('Sport').aggregate([ 
    { "$match": { 
    "Categories": { 
     "$elemMatch": { 
     "Name.v": "France", 
     "Tournaments.Name.v": "Ligue 1"  
     } 
    }  
    }}, 
    { "$addFields": { 
    "Categories": { 
     "$filter": { 
     "input": { 
      "$map": { 
      "input": "$Categories", 
      "as": "c", 
      "in": { 
       "Name": { 
       "$filter": { 
        "input": "$$c.Name", 
        "as": "n", 
        "cond": { "$eq": [ "$$n.v", "France" ] } 
       }  
       }, 
       "Tournaments": { 
       "$filter": { 
        "input": { 
        "$map": { 
         "input": "$$c.Tournaments", 
         "as": "t", 
         "in": { 
         "Name": { 
          "$filter": { 
          "input": "$$t.Name", 
          "as": "n", 
          "cond": { 
           "$eq": [ "$$n.v", "Ligue 1" ] 
          } 
          } 
         }   
         } 
        } 
        }, 
        "as": "t", 
        "cond": { 
        "$ne": [{ "$size": "$$t.Name" }, 0] 
        } 
       } 
       } 
      } 
      } 
     }, 
     "as": "c", 
     "cond": { 
      "$and": [ 
      { "$ne": [{ "$size": "$$c.Name" },0] }, 
      { "$ne": [{ "$size": "$$c.Tournaments" },0] } 
      ] 
     } 
     } 
    } 
    }} 
]) 

qui renvoie le résultat:

/* 1 */ 
{ 
    "_id" : ObjectId("597846358bbbc4440895f2e8"), 
    "Name" : [ 
     { 
      "k" : "en-US", 
      "v" : "Soccer" 
     }, 
     { 
      "k" : "fr-FR", 
      "v" : "Football" 
     } 
    ], 
    "Categories" : [ 
     { 
      "Name" : [ 
       { 
        "k" : "en-US", 
        "v" : "France" 
       }, 
       { 
        "k" : "fr-FR", 
        "v" : "France" 
       } 
      ], 
      "Tournaments" : [ 
       { 
        "Name" : [ 
         { 
          "k" : "en-US", 
          "v" : "Ligue 1" 
         }, 
         { 
          "k" : "fr-FR", 
          "v" : "Ligue 1" 
         } 
        ] 
       } 
      ] 
     } 
    ] 
} 

Le tout est que chaque tableau a besoin d'un $filter et aux niveaux extérieurs que vous recherchez pour $size n'étant pas 0 à la suite des opérations "internes" $filter sur les tableaux contenus. Comme les tableaux "internes" peuvent changer de contenu, les tableaux "externes" ont besoin d'un $map pour renvoyer les éléments "modifiés".

Donc, en termes de structure "Categories" a besoin d'un $map car il a des éléments internes. Et le "interne" "Tournaments" a besoin d'un $map pour la même raison. Tous les tableaux jusqu'aux propriétés finales ont besoin de $filter, et chaque tableau d'emballage avec $map a un $filter avec une condition $size.

C'est le modèle de logique générale, et cela fonctionne en répétant ce modèle pour chaque niveau imbriqué. Comme indiqué cependant, c'est assez horrible. C'est pourquoi vous devriez vraiment éviter de "nicher" comme ça à tout prix. La complexité accrue dépasse à peu près toujours les gains perçus.

Il faut aussi noter que vous êtes allé un peu trop loin avec $elemMatch, Vous avez vraiment besoin au niveau du tableau "Categories" puisque c'est la seule chose qui a de multiples conditions à remplir pour son élément.

Les sous-éléments peuvent utiliser "Dot Notation" car ils ne sont que des conditions "singulières" dans leurs tableaux respectifs.Cela réduit quelque peu la syntaxe laconique et correspond exactement aux mêmes documents.

+0

Merci beaucoup. J'utilise un tableau imbriqué parce que dans ma conception, ces documents incorporés ne seront jamais interrogés sans leur sport parent. Nous avons essayé d'éviter la conception relationnelle basée sur une collection par type. Cependant, vous avez raison, j'ai seulement essayé de mettre à jour le tableau de niveau racine (avec succès) mais il semble impossible de mettre à jour le tableau imbriqué sans remplacer l'objet entier. – Clement