在mongodb中使用索引搜索值

时间:2018-11-14 18:37:12

标签: mongodb multidimensional-array mongodb-query

我是Mongodb的新手,希望在mongo集合中实现对字段的搜索。

我的测试集合具有以下结构:-

{
  'key': <unique key>,
  'val_arr': [
               ['laptop', 'macbook pro', '16gb', 'i9', 'spacegrey'],
               ['cellphone', 'iPhone', '4gb', 't2', 'rose gold'],
               ['laptop', 'macbook air', '8gb', 'i5', 'black'],
               ['router', 'huawei', '10x10', 'white'],
               ['laptop', 'macbook', '8gb', 'i5', 'silve'],
}

我希望根据索引号和索引值找到它们,即 找到其中val_arr中的第一个元素为laptop且第三个元素的值为8gb的条目。

我尝试查看mongodb中的复合索引,但是它们的索引限制为32个键。对此方向的任何帮助将不胜感激。

1 个答案:

答案 0 :(得分:3)

有一个limit on indexes here,但实际上并不重要。就您而言,您实际上说的是'key': <unique key>。因此,如果这确实是“独特的”,那么它是集合中唯一需要需要建立索引的东西,只要您将"key"包含在所进行的每个查询中即可,确定您选择文档。

在文档“内部”的数组上的索引并不重要,除非您实际上打算直接在文档中搜索那些元素。可能是这种情况,但这实际上与按编号的索引位置匹配您的值没有关系:

db.collection.find(
  {
    "val_arr": {
      "$elemMatch": { "0": "laptop", "2": "8gb" }
    }
  },
  {  "val_arr.$": 1 }
)

哪个会返回:

{
    "val_arr" : [
        [
            "laptop",
            "macbook air",
            "8gb",
            "i5",
            "black"
        ]
    ]
}

$elemMatch允许您在同一数组元素上表达“多个条件”。这是标准点表示形式所需要的,因为否则条件只是寻找与索引值匹配的“ any” 数组成员。例如:

db.collection.find({ "val_arr.0": "laptop", "val_arr.2": "4gb" })

实际上匹配给定文档,即使单个“行”上不存在“组合”,但是两个值实际上作为一个整体存在于数组中。但是只是在不同的成员中。将这些相同的值与$elemMatch一起使用可确保该对在同一元素上匹配。

请注意上面示例中的{ "val_arr.$": 1 },它是“单个”匹配元素的投影。这是可选的,但这只是为了确定匹配项。

尽可能多地使用.find(),这是位置运算符的局限性,因为它只能识别一个匹配元素。对于“多个匹配项”,此方法是将aggregate()$filter结合使用:

db.collection.aggregate([
  { "$match": {
    "val_arr": {
      "$elemMatch": { "0": "laptop", "2": "8gb" }
    }
  }},
  { "$addFields": {
    "val_arr": {
      "$filter": {
        "input": "$val_arr",
        "cond": {
          "$and": [
            { "$eq": [ { "$arrayElemAt": [ "$$this", 0 ] }, "laptop" ] },
            { "$eq": [ { "$arrayElemAt": [ "$$this", 2 ] }, "8gb" ] }
          ]
        }
      }
    }
  }}
])

哪个返回:

{
        "key" : "k",
        "val_arr" : [
                [
                        "laptop",
                        "macbook air",
                        "8gb",
                        "i5",
                        "black"
                ],
                [
                        "laptop",
                        "macbook",
                        "8gb",
                        "i5",
                        "silve"
                ]
        ]
}

实际选择匹配文档的初始查询条件进入$match,并且与之前显示的查询条件完全相同。 $filter用于仅获取实际上符合其条件的元素。这些条件在逻辑表达式中对$arrayElemAt的用法与"0""2"的索引值如何应用于查询条件本身类似。

使用任何聚合表达式都会比标准查询引擎功能产生更多费用。因此,始终最好考虑在潜水和使用陈述之前是否真的需要。只要执行正常,常规查询表达式始终会更好。

更改结构

当然,虽然可以在数组的索引位置上进行匹配,但这些都无法真正创建能够用来加速查询的“索引”。

这里最好的方法是实际使用有意义的属性名而不是普通数组:

{
  'key': "k",
  'val_arr': [
    { 
      'type': 'laptop',
      'name': 'macbook pro',
      'memory': '16gb',
      'processor': 'i9',
      'color': 'spacegrey'
    },
    {
      'type': 'cellphone',
      'name': 'iPhone',
      'memory': '4gb',
      'processor': 't2',
      'color': 'rose gold'
    },
    {
      'type': 'laptop',
      'name': 'macbook air',
      'memory': '8gb',
      'processor': 'i5',
      'color': 'black'
    },
    { 
      'type':'router',
      'name': 'huawei',
      'size': '10x10',
      'color': 'white'
    },
    { 
      'type': 'laptop',
      'name': 'macbook',
      'memory': '8gb',
      'processor': 'i5',
      'color': 'silve'
    }
  ]
}

这确实允许您“在合理范围内”将数组中属性名称的路径作为复合索引的一部分包括在内。例如:

db.collection.createIndex({ "val_arr.type": 1, "val_arr.memory": 1 })

然后,实际发出的查询在代码中看起来比02的隐含值更具描述性:

db.collection.aggregate([
  { "$match": {
    "val_arr": {
      "$elemMatch": { "type": "laptop", "memory": "8gb" }
    }
  }},
  { "$addFields": {
    "val_arr": {
      "$filter": {
        "input": "$val_arr",
        "cond": {
          "$and": [
            { "$eq": [ "$$this.type", "laptop" ] },
            { "$eq": [ "$$this.memory", "8gb" ] }
          ]
        }
      }
    }
  }}
])

预期结果,更有意义:

{
        "key" : "k",
        "val_arr" : [
                {
                        "type" : "laptop",
                        "name" : "macbook air",
                        "memory" : "8gb",
                        "processor" : "i5",
                        "color" : "black"
                },
                {
                        "type" : "laptop",
                        "name" : "macbook",
                        "memory" : "8gb",
                        "processor" : "i5",
                        "color" : "silve"
                }
        ]
}

大多数人得出类似您所遇到的结构的常见原因通常是因为他们认为自己正在节省空间。这是not simply not true,在对存储引擎进行最现代的优化之后,MongoDB使用它与预期的任何小收益基本上无关。

因此,为了“清晰”起见,并且为了真正支持对“数组”中的数据进行索引,您确实应该更改结构并在此处使用命名属性。

同样,如果您对数据的整个使用模式都没有在查询中使用文档的key属性,那么最好将这些条目存储为单独的文档,而不是放在其中一个数组。这也使获得结果的效率更高。

因此,要想打破所有这些选择,实际上是:

  • 您实际上始终key作为查询的一部分,因此,除该属性外的其他任何地方的索引都没有关系。
  • 您更改为对数组成员上的值使用命名属性,从而使您可以对这些属性建立索引,而无需点击"Multikey limitations"
  • 您决定始终从不使用key访问此数据,因此您只需将所有数组数据作为具有适当命名属性的单独文档写入集合中即可。

选择最适合您需求的解决方案本质上是一种解决方案,可让您有效处理拥有的数据。

  

NB 与本主题无关(也许有关于存储大小的注释除外),但是通常建议使用具有固有数值的东西,例如{{1} }或memory类型的数据实际上表示为数字而不是“字符串”。

     

简单的理由是,尽管您可以平等地查询"8gb",但这对诸如“ 4到12 GB之间的范围”无济于事。

     

因此,通常使用"8gb"甚至8之类的数值会更有意义。请注意,数字值实际上将对存储产生影响,因为它们通常会比字符串占用更少的空间。考虑到忽略属性名称可能一直在尝试减少存储空间,但什么也没做,它确实显示了可以减小存储空间的实际区域。