在 mongodb 中使用索引搜索值

Nis*_*rma 3 multidimensional-array mongodb 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'],
}
Run Code Online (Sandbox Code Playgroud)

我希望根据索引号和值找到它们,即找到其中任何一个val_arris中的第一个元素laptop和第三个元素的值 is的条目8gb

我尝试查看 mongodb 中的复合索引,但它们的索引键限制为 32 个。任何在这个方向上的帮助表示赞赏。

Nei*_*unn 5

这里的索引是有限制的,但这真的无关紧要。在你的情况下,你实际上说'key': <unique key>. 因此,如果这真的是“独一无二的”,那么它就是该系列中唯一需要的东西索引的内容,只要您实际将其包含"key"为您所做的每个查询的一部分,因为这将决定您选择一个文档。

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

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

哪个会返回:

{
    "val_arr" : [
        [
            "laptop",
            "macbook air",
            "8gb",
            "i5",
            "black"
        ]
    ]
}
Run Code Online (Sandbox Code Playgroud)

$elemMatch让你表达了同样的数组元素的“多条件”。这是标准点符号形式所必需的,因为否则条件只是寻找与索引处的值匹配的“任何”数组成员。例如:

db.collection.find({ "val_arr.0": "laptop", "val_arr.2": "4gb" })
 
Run Code Online (Sandbox Code Playgroud)

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

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

.find()尽可能多地使用它,这是位置运算符的一个限制,因为它只能识别一个匹配的元素。为“多个匹配”执行此操作的方法是使用aggregate()with $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" ] }
          ]
        }
      }
    }
  }}
])
Run Code Online (Sandbox Code Playgroud)

返回:

{
        "key" : "k",
        "val_arr" : [
                [
                        "laptop",
                        "macbook air",
                        "8gb",
                        "i5",
                        "black"
                ],
                [
                        "laptop",
                        "macbook",
                        "8gb",
                        "i5",
                        "silve"
                ]
        ]
}
Run Code Online (Sandbox Code Playgroud)

实际选择匹配文档的初始查询条件进入 并且$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'
    }
  ]
}
Run Code Online (Sandbox Code Playgroud)

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

db.collection.createIndex({ "val_arr.type": 1, "val_arr.memory": 1 })
Run Code Online (Sandbox Code Playgroud)

然后实际发出查询在代码中看起来比0and 的神秘值更具描述性2

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" ] }
          ]
        }
      }
    }
  }}
])
Run Code Online (Sandbox Code Playgroud)

预期的结果,更有意义:

{
        "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"
                }
        ]
}
Run Code Online (Sandbox Code Playgroud)

大多数人到达问题中的结构的常见原因通常是因为他们认为他们正在节省空间。这并非完全不正确,而且对于 MongoDB 使用的存储引擎的大多数现代优化,它基本上与可能预期的任何小收益无关。

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

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

因此,要分解所有这些,您的选择实际上是:

  • 实际上,您始终将包含key作为查询的一部分,因此除该属性之外的其他任何位置的索引都无关紧要。
  • 您更改为对数组成员上的值使用命名属性,允许您对这些属性进行索引,而无需点击“多键限制”
  • 您决定永远不会使用 访问此数据key,因此您只需将所有数组数据作为具有适当命名属性的集合中的单独文档编写。

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

注意与手头的主题无关(除了可能关于存储大小的注释),但通常建议将具有固有数值的事物(例如数据的memory"8gb"类型)实际上表示为数字而不是“字符串” .

简单的推理是,虽然您可以查询为"8gb"等式,但这对“4 到 12 GB 之间的范围”没有帮助。

因此,使用像8或 之类的数值通常更有意义8000。请注意,数值实际上会对存储产生影响,因为它们通常比字符串占用更少的空间。鉴于省略属性名称可能一直试图减少存储但没有做任何事情,确实显示了可以减少存储大小的实际区域。