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

Requête Mongodb basée sur le nombre de champs dans un enregistrement

Ce n'est toujours pas une requête agréable à exécuter, mais il existe une manière légèrement plus moderne de le faire via $objectToArray et $redact

db.collection.aggregate([
  { "$redact": {
    "$cond": {
      "if": {
        "$eq": [
          { "$size": { "$objectToArray": "$value" } },
          3
        ]
      },
      "then": "$$KEEP",
      "else": "$$PRUNE"
    }
  }}
])

$objectToArray fondamentalement contraint l'objet dans une forme de tableau, un peu comme une combinaison de Object.keys() et .map() serait en JavaScript.

Ce n'est toujours pas une idée fantastique car cela nécessite d'analyser toute la collection, mais au moins les opérations du cadre d'agrégation utilisent le "code natif" par opposition à l'interprétation JavaScript comme c'est le cas en utilisant $where .

Il est donc généralement conseillé de modifier la structure des données et d'utiliser un tableau naturel ainsi que des propriétés de "taille" stockées dans la mesure du possible afin d'effectuer les opérations de requête les plus efficaces.

Oui, c'est possible de le faire mais pas de la plus belle des manières. La raison en est que vous utilisez essentiellement un $where requête d'opérateur qui utilise l'évaluation JavaScript pour faire correspondre le contenu. Ce n'est pas le moyen le plus efficace car cela ne peut jamais utiliser d'index et doit tester tous les documents :

db.collection.find({ "$where": "return Object.keys(this.value).length == 3" })

Cela recherche la condition correspondant à "trois" éléments, alors seuls deux de vos documents répertoriés seront renvoyés :

{ "_id" : "number1", "value" : { "a" : 1, "b" : 2, "f" : 5 } }
{ "_id" : "number2", "value" : { "e" : 2, "f" : 114, "h" : 12 } }

Ou pour "cinq" champs ou plus, vous pouvez faire à peu près la même chose :

db.numbers.find({ "$where": "return Object.keys(this.value).length >= 5" })

Ainsi, les arguments de cet opérateur sont en fait des instructions JavaScript qui sont évaluées sur le serveur pour renvoyer où true .

Un moyen plus efficace consiste à stocker le "compte" des éléments dans le document lui-même. De cette façon, vous pouvez "indexer" ce champ et les requêtes sont beaucoup plus efficaces car chaque document de la collection sélectionnée par d'autres conditions n'a pas besoin d'être scanné pour déterminer la longueur :

{_id:'number1', value:{'a':1, 'b':2, 'f':5} count: 3},
{_id:'number2', value:{'e':2, 'f':114, 'h':12}, count: 3},
{_id:'number3', value:{'i':2, 'j':22, 'z':12, 'za':111, 'zb':114}, count: 5}

Ensuite, pour obtenir les documents avec "cinq" éléments, vous n'avez besoin que de la simple requête :

db.collection.find({ "count": 5 })

C'est généralement la forme la plus optimale. Mais un autre point est que la structure générale "Objet" avec laquelle vous pourriez être satisfait de la pratique générale n'est pas quelque chose avec laquelle MongoDB "joue bien" en général. Le problème est la "traversée" des éléments dans l'objet, et de cette façon MongoDB est beaucoup plus heureux lorsque vous utilisez un "tableau". Et même sous cette forme :

{
    '_id': 'number1', 
    'values':[
        { 'key': 'a', 'value': 1 },
        { 'key': 'b', 'value': 2 }, 
        { 'key': 'f', 'value': 5 }
    ],
},
{
    '_id': 'number2', 
    'values':[
        { 'key': 'e', 'value': 2 }, 
        { 'key': 'f', 'value': 114 }, 
        { 'key': 'h', 'value': 12 }
    ],
},
{
    '_id':'number3', 
    'values': [
        { 'key': 'i', 'values': 2 }, 
        { 'key': 'j', 'values': 22 }, 
        { 'key': 'z'' 'values': :12 }, 
        { 'key': 'za', 'values': 111 },
        { 'key': 'zb', 'values': 114 }
    ]
}

Donc, si vous passez réellement à un format "tableau" comme celui-ci, vous pouvez faire un exact longueur d'un tableau avec une version du $size opérateur :

db.collection.find({ "values": { "$size": 5 } })

Cet opérateur peut travailler pour un exact valeur pour une longueur de tableau car c'est une disposition de base de ce qui peut être fait avec cet opérateur. Ce que vous ne pouvez pas faire est documenté dans un match "in-equality". Pour cela, vous avez besoin du "cadre d'agrégation" pour MongoDB, qui est une meilleure alternative aux opérations JavaScript et mapReduce :

db.collection.aggregate([
    // Project a size of the array
    { "$project": {
        "values": 1,
        "size": { "$size": "$values" }
    }},
    // Match on that size
    { "$match": { "size": { "$gte": 5 } } },
    // Project just the same fields 
    {{ "$project": {
        "values": 1
    }}
])

Ce sont donc les suppléants. Il existe une méthode "native" disponible pour l'agrégation et un type de tableau. Mais il est assez discutable que l'évaluation JavaScript est également "native" à MongoDB, mais n'est donc pas implémentée dans le code natif.