至少匹配" N"数组的元素到条件列表

时间:2015-01-26 15:26:18

标签: javascript mongodb mongodb-query aggregation-framework

我有以下情况: 我的一个mongo集合包含以下格式的文档:

user: "test",
tracks: [{artist: "A", ...}, {artist: "B", ...}, ..., { artist: "N", ...}]

我想提取所有曲目,其艺术家都在给定的数组arr中。为此,我使用以下查询(工作正常)。

collection.find({ tracks: { $elemMatch: { artist: { $in: arr }}}})

但是,现在我想修改查询,以便它只返回集合中那些由至少让来自arr数组的3个不同艺术家所执行的轨道的文档。我怎样才能实现这一点(除了从数据库返回结果后过滤结果,这不是一个选项)?

1 个答案:

答案 0 :(得分:8)

你的问题对我有两种可能,但也许有一些解释可以让你开始。

首先,我需要向您解释您误解了$elemMatch的意图,并且在这种情况下被误用了。

$elemMatch的想法是创建一个"查询文档"这实际上应用于数组的元素。意图是你有多个条件"在数组中的文档上,以便在成员文档内离散地匹配它,而不是在外部文档的整个数组中。即:

{
   "data": [
       { "a": 1, "b": 3 },
       { "a": 2, "b": 2 }
   ]
}

以下查询将起作用,即使该数组中没有实际的单个元素匹配,但整个文档也是如此:

db.collection.find({ "data.a": 1, "data.b": 2 })

但要检查实际元素是否与这两个条件匹配,这是您使用$elemMatch的地方:

db.collection.find({ "data": { "a": 1, "b": 2 } })

因此该样本中没有匹配,并且它只匹配特定数组元素具有这两个元素的位置。


现在我们已解释$elemMatch,这是您的简化查询:

db.collection.find({ "tracks.artist": { "$in": arr } })

更简单,它的工作原理是通过单个字段查看所有数组成员,并返回文档中的任何元素至少包含其中一个可能结果的位置。

但不是你在问什么,等你的问题。如果您仔细阅读最后一条陈述,您应该意识到$in实际上是$or条件。它只是一个缩短的形式,用于询问"或"在文档中的相同元素。

考虑到这一点,您所要求的核心是"" 操作,其中所有"三"值包含在内。假设你只发送了三个"测试中的项目然后您可以使用$and的缩写形式的$all形式:

db.collection.find({ "tracks.artist": { "$all": arr } })

那只会返回那些元素在该数组成员中匹配的文件#34; all"在测试条件中指定的元素。这可能就是你想要的,但有一种情况当然你想要指定一个说清单,"四个或更多"艺术家要测试,只想要"三"或者其中一些较小的数字,在这种情况下,$all运算符太简洁了。

但是有一种合理的方法可以解决这个问题,只需要对基本查询不可用的运算符进行更多处理,但aggregation framework可以使用

var arr = ["A","B","C","D"];     // List for testing

db.collection.aggregate([
    // Match conditions for documents to narrow down
    { "$match": {
        "tracks.artist": { "$in": arr },
        "tracks.2": { "$exists": true }      // you would construct in code
    }},

    // Test the array conditions
    { "$project": {
        "user": 1,
        "tracks": 1,                         // any fields you want to keep
        "matched": {
            "$gte": [
                 { "$size": {
                     "$setIntersection": [
                         { "$map": {
                             "input": "$tracks",
                             "as": "t",
                             "in": { "$$t.artist" }
                         }},
                         arr
                     ]
                 }},
                 3
             ]
        }
    }},

    // Filter out anything that did not match
    { "$match": { "matched": true } }
])

第一阶段实现标准查询$match条件,以便将文档过滤到那些可能"可能"符合条件。这里的逻辑情况是像以前一样使用$in,它会找到那些文件,其中至少有一个元素出现在你的" test"数组存在于文档自己的数组中的至少一个成员字段中。

下一个条款是理想情况下应该在代码中构建的,因为它与"长度"相关。数组。这里的想法是你至少想要的地方"三"然后匹配您在文档中测试的数组必须至少有"三"元素是为了满足这一要求,所以用"两个"检索文件是没有意义的。或更少的数组元素,因为它们永远不会匹配"三"。

由于所有MongoDB查询基本上只是数据结构的表示,因此很容易构建。即,对于JavaScript:

var matchCount = 3;    // how many matches we want

var match1 = { "$match": { "tracks.artist": { "$in": arr } } };

match1["$match"]["tracks."+ (matchCount-1)] = { "$exits": true };

逻辑是"点符号"使用$exists测试表单是否存在指定索引处的元素(n-1),并且数组必须至少具有该长度。

其余的缩小理想情况下使用$setIntersection方法,以便返回实际数组和测试数组之间的匹配元素。由于文档中的数组与"测试数组的结构不匹配"它需要通过$map操作进行转换,该操作被设置为仅返回"艺术家"每个数组元素的字段。

作为"十字路口"在这两个数组中,最后测试了所得到的常见元素列表的$size,其中应用了测试以查看"至少三个"这些元素被发现是共同的。

最后你只是"过滤掉"任何使用$match条件都不正确的事情。


理想情况下,您使用MongoDB 2.6或更高版本才能使这些运算符可用。对于早期版本的2.2.x和2.4.x,它仍然可行,但只需要更多的工作和处理开销:

db.collection.aggregate([
    // Match conditions for documents to narrow down
    { "$match": {
        "tracks.artist": { "$in": arr },
        "tracks.2": { "$exists": true }      // you would construct in code
    }},

    // Unwind the document array
    { "$unwind": "$tracks" },

    // Filter the content
    { "$match": { "tracks.artist": { "$in": arr } }},

    // Group for distinct values
    { "$group": {
        "_id": { 
           "_id": "$_id",
           "artist": "$tracks.artist"
        }
    }},

    // Make arrays with length
    { "$group": {
        "_id": "$_id._id",
        "artist": { "$push": "$_id.artist" },
        "length": { "$sum": 1 }
    }},

    // Filter out the sizes
    { "$match": { "length": { "$gte": 3 } }}
])