MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Filtrer les résultats par la dernière valeur du champ d'entrée de tableau

Le kilométrage peut varier sur ce point et il se peut bien que "actuellement" le processus que vous suivez soit au moins "le plus adapté". Mais nous pouvons probablement faire plus efficace.

Ce que vous pourriez faire maintenant

À condition que vos tableaux soient déjà "triés" via l'utilisation du $sort modificateur avec $push , alors vous pouvez probablement faire ceci :

db.somedb.find(
  { 
    "partn.is_partner": true,
    "$where": function() {
      return this.partn.slice(-1)[0].is_partner == true;
    }
  },
  { "partn": { "$slice": -1 } }
)

Donc tant que partn,is_partner est "indexé", cela reste assez efficace car cette condition de requête initiale peut être satisfaite à l'aide d'un index. La partie qui ne peut pas est $where clause ici qui utilise l'évaluation JavaScript.

Mais qu'est-ce que cette deuxième partie dans le $where fait simplement "découper" le dernier élément du tableau et tester sa valeur de is_partner propriété pour voir si c'est vrai. Ce n'est que si cette condition est également remplie que le document est renvoyé.

Il y a aussi le $slice opérateur de projection. Cela fait la même chose en renvoyant le dernier élément du tableau. Les fausses correspondances sont déjà filtrées, donc cela ne montre que le dernier élément où vrai.

Combiné avec l'index comme mentionné, cela devrait être assez rapide étant donné que les documents ont déjà été sélectionnés et que la condition JavaScript filtre simplement le reste. Notez que sans un autre champ avec une condition de requête standard à faire correspondre, un $where La clause ne peut pas utiliser d'index. Essayez donc toujours d'utiliser "avec parcimonie" avec d'autres conditions de requête en place.

Ce que vous pouvez faire à l'avenir

Next Up, bien qu'il ne soit pas disponible au moment de la rédaction, mais certainement dans un proche avenir sera le $slice opérateur pour le cadre d'agrégation. Ceci est actuellement dans la branche de développement, mais voici un aperçu de son fonctionnement :

db.somedb.aggregate([
  { "$match": { "partn.is_partner": true } },
  { "$redact": {
    "$cond": {
      "if": { 
        "$anyElementTrue": {
          "$map": {
            "input": { "$slice": ["$partn",-1] },
            "as": "el",
            "in": "$$el.is_partner"
          }
        }
      },
      "then": "$$KEEP",
      "else": "$$PRUNE"
    }
  }},
  { "$project": {
      "partn": { "$slice": [ "$partn",-1 ] }
  }}
])

Combiner ce $slice dans un $redact Cette étape permet de filtrer les documents avec une condition logique, testant le document. Dans ce cas, le $slice produit un seul tableau d'éléments qui est envoyé à $map afin d'extraire uniquement le seul is_partner value (toujours sous forme de tableau). Comme il s'agit au mieux d'un tableau à un seul élément, l'autre test est $anyElementTrue ce qui en fait un résultat booléen singulier, adapté à $cond .

Le $redact ici décide de ce résultat s'il faut $$KEEP ou $$PRUNE le document à partir des résultats. Plus tard, nous utilisons $slice à nouveau dans le projet pour ne renvoyer que le dernier élément du tableau après le filtrage.

Cela correspond à peu près exactement à ce que fait la version JavaScript, à l'exception du fait qu'elle utilise tous les opérateurs codés natifs, et devrait donc être un peu plus rapide que l'alternative JavaScript.

Les deux formulaires renvoient votre premier document comme prévu :

{
    "_id" : 0,
    "partn" : [
            {
                    "date" : ISODate("2015-07-28T00:59:14.963Z"),
                    "is_partner" : true
            },
            {
                    "date" : ISODate("2015-07-28T01:00:32.771Z"),
                    "is_partner" : false
            },
            {
                    "date" : ISODate("2015-07-28T01:15:29.916Z"),
                    "is_partner" : true
            },
            {
                    "date" : ISODate("2015-08-05T13:48:07.035Z"),
                    "is_partner" : false
            },
            {
                    "date" : ISODate("2015-08-05T13:50:56.482Z"),
                    "is_partner" : true
            }
    ]
}

Le gros problème ici avec les deux est que votre tableau doit déjà être trié, donc la dernière date est la première. Sans cela, vous avez besoin du cadre d'agrégation pour $sort le tableau, comme vous le faites maintenant.

Pas vraiment efficace, c'est pourquoi vous devez "pré-trier" votre tableau et maintenir l'ordre à chaque mise à jour.

Comme astuce pratique, cela réorganisera en fait tous les éléments du tableau dans tous les documents de collection en une simple instruction :

db.somedb.update(
    {},
    { "$push": { 
        "partn": { "$each": [], "$sort": { "date": 1 } }
    }},
    { "multi": true }
)

Ainsi, même si vous ne "poussez" pas un nouvel élément dans un tableau et ne faites que mettre à jour une propriété, vous pouvez toujours appliquer cette construction de base pour que le tableau reste ordonné comme vous le souhaitez.

Cela vaut la peine d'être considéré car cela devrait rendre les choses beaucoup plus rapides.