MongoDB使用或查询超集中的子集

时间:2015-01-11 21:40:50

标签: mongodb mongodb-query aggregation-framework

我有一个文档“item”,结构如下:

{
    "name": String,
    "parts": [
        {
        "alternate_names": Array,
        "size": String,
        "type": String
        }
    ]
}

E.g。

{
    "name": Plane,
    "parts": [
        {
        "alternate_names": ['wing', 'fin'],
        "size": 'Large',
        "type": 'Aero'
        },
        {
        "alternate_names": ['engine', 'motor'],
        "size": 'Medium',
        "type": 'Power'
        },
    ]
}

对parts.alternate_names的查询:[“wing”,“motor”]应返回此文档。 对parts.alternate_names:[“wing”]的查询不应该。

“alternate_names”是特定部分的可能名称列表。

每个项目都有多个部分。我想创建一个查询,指定我拥有的部件的名称,并获取我可以使用这些部件制作的所有项目。为了能够制作该项目,我需要拥有所有部分,但每个部分的“alternate_names”列表中只有一个名称。

我设法得到的最接近的是查询:

{"parts.alternate_names": {
    "$not": {
        "$elemMatch": {
            "$nin": ["part1", "part2", "part3", "part4"]
            }
        }
    }
}

但只有在指定了每个部分的所有alternate_names时才会匹配,而不只是一个。

这是执行此操作的唯一方法:Check if every element in array matches condition

2 个答案:

答案 0 :(得分:2)

原始问题解决方案


从您描述的文档结构中,您最终会得到如下数据:

{
    "parts" : [
        {
           "alt_names" : [
                    "sizeo",
                    "bill"
            ]
        },
        "size",
        "type"
    ]
}

无论实际参数是什么,但这只是在相应阵列上使用$all$in运算符的标准条件:

db.collection.find({ 
    "parts": { "$all": ["size","type"] },
    "parts.alt_names": { "$in": ["sizeo","sizeb"] } 
})

因此$all运算符确保条件中的所有指定元素都存在于外部数组中,$in运算符会查看特定字段。

是的,这是一个基本查询,是的,它可以使用相应字段值的索引。问题在于,由于索引两个"多键"的复杂程度,这不能成为一个复合指数。一个索引中的值。

听起来你想要外部数组值的索引,然后在结果减少之后让剩余的内部元素中的值进行剩余的强力过滤。


当前问题解决方案


使用这样的文件:

{
    "name" : "Plane",
    "parts" : [
            {
                    "alternate_names" : [
                            "wing",
                            "fin"
                    ],
                    "size" : "Large",
                    "type" : "Aero"
            },
            {
                    "alternate_names" : [
                            "engine",
                            "motor"
                    ],
                    "size" : "Medium",
                    "type" : "Power"
            }
    ],
}

需要对内部数组进行元素匹配测试,但这里重要的条件是它们所属的外部数组需要包含文档中每个元素的正匹配。

$all 查询参数将允许选择存在所有必需数组成员的文档。这会缩小范围,但需要的是对所有元素中的匹配进行逻辑评估。这就是聚合框架的 $allElementsTrue 集合运算符可以做的事情。

db.collection.aggregate([
  { "$match": { 
    "parts.alternate_names": { 
      "$all": [ "wing","motor" ],
    }
  }},
  { "$project": {
      "name": 1,
      "parts": 1,
      "matched": {
        "$allElementsTrue": [
          { "$map": {
            "input": "$parts",
            "as": "p",
            "in": { "$anyElementTrue": { "$map": {
              "input": "$$p.alternate_names",
              "as": "a",
              "in": {
                "$or": [
                   { "$eq": [ "$$a", "wing" ] },
                   { "$eq": [ "$$a", "motor" ] }
                ]
              }                   
            }}}
          }}
        ]
      }
  }},
  { "$match": { "matched": true } }
])

$map 运算符允许检查每个数组元素并对其进行评估,将结果返回为等长的数组,但将每个成员替换为结果评价。

在外部数组的迭代中,需要有类似的$map操作来测试这些元素是否符合您要匹配的单词列表。因此,查询列表中的相同条件将作为数组元素添加到 $or 操作中。这样就可以在任何数组元素上查找任何一个术语。

内部搜索结果发送到 $anyElementTrue 。这很好,因为对于" alternate_names"中的任何匹配都是如此。在它的地方回归正面。这会将外部数组中此字段的逻辑评估缩小为每个外部数组成员的单个true|false结果。

这有效地改变了"名称"根据从内部数组的求值返回的内容,将数组放入true|false条件列表中。从总体目标来看,所有这些条件都是true,因此 $allElementsTrue 可以评估该数组,以确定所有元素确实是true

答案 1 :(得分:1)

您需要进行更严格的匹配,而不仅仅是使用$nin

您可以按如下方式汇总结果:

  • Match所有包含至少一部分的项目 寻找。
  • Project每个项目中的部分数量,稍后将使用。
  • Unwind parts数组。
  • Project一个临时字段,用于保存完整的别名集 对于每个部分。
  • 现在unwind每个部分的别名。
  • Match那些具有我们正在寻找的别名的文档。
  • Group基于商品ID及其完整集合的那些文档 别名,这样做只为每个别名选择一个别名 匹配部分。
  • Group所有文件按项目ID,并累积别名 该项目每个部分的名称。
  • 现在我们对那些需要由子集构建的项目感兴趣 我们正在寻找的零件。所以project一个字段来检查是否有 项目可以选择。只有所有部件都可以选择一个项目 是我们搜索数组的一个子集。
  • Match有效的项目。

代码:

var c = ["p1","b1"];

db.collection.aggregate([
{$match:{"parts.alternate_names":{$in:c}}},
{$project:{"name":1,"parts":1,"partSize":{$size:"$parts"}}},
{$unwind:"$parts"},
{$project:{"name":1,"parts":1,"temp":"$parts.alternate_names","partSize":1}},
{$unwind:"$parts.alternate_names"},
{$match:{"parts.alternate_names":{$in:c}}},
{$group:{"_id":{"names":"$temp","id":"$_id"},
         "size":{$first:"$partSize"},
         "name":{$first:"$name"},
         "alternate_names":{$first:"$parts.alternate_names"}}},
{$group:{"_id":"$_id.id",
         "size":{$first:"$size"},
         "name":{$first:"$name"},
         "parts":{$push:{"alternate_names":"$alternate_names"}}}},
{$project:{"name":1,
           "parts":1,
           "valid":{$cond:[{$eq:["$size",{$size:"$parts"}]},true,false]}}},
{$match:{"valid":true}},
{$project:{"name":1,"parts":1}}
])

示例数据:

{
        "_id" : ObjectId("54b2f28adc64efa8ac8a07da"),
        "name" : "wing",
        "parts" : [
                {
                        "alternate_names" : [
                                "p1",
                                "p2",
                                "p3"
                        ]
                },
                {
                        "alternate_names" : [
                                "b1",
                                "b2",
                                "b3"
                        ]
                }
        ]
}

样本o / p:

{
        "_id" : ObjectId("54b2f28adc64efa8ac8a07da"),
        "name" : "wing",
        "parts" : [
                {
                        "alternate_names" : "b1"
                },
                {
                        "alternate_names" : "p1"
                }
        ]
}