【问题标题】:Match at least "N" elements of an array to a list of conditions将数组的至少“N”个元素与条件列表匹配
【发布时间】:2015-01-26 15:26:18
【问题描述】:

我有以下情况: 我的 mongo 收藏之一具有以下格式的文档:

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

我想提取所有曲目,其艺术家在给定数组arr 中。为此,我正在使用以下查询(效果很好)。

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

但是,现在我想修改查询,使其仅返回集合中的那些文档,这些文档至少有来自arr 数组的 3 位不同的艺术家执行过曲目。我怎样才能做到这一点(除了在从数据库返回后过滤结果,这不是一个选项)?

【问题讨论】:

  • 没有办法通过简单的查找查询来做到这一点。您可以使用聚合来完成此操作,但这表明数据模型不合适,必须进行聚合以返回集合中文档的传真。是否能够重新设计数据以适应此查询?查询是否重要到足以证明这一点?如果我们要修补它,其他重要的查询是什么?整个数据模型是什么样的?如果您不想更改数据模型,那么我认为在获得结果后进行过滤是最好的选择。

标签: javascript mongodb mongodb-query aggregation-framework


【解决方案1】:

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

首先我需要向您解释一下,您误解了$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” 操作。假设您在测试中只发送“三个”项目,那么您可以使用$and 的形式,它是$all 的缩写形式:

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

这只会返回在该数组的成员中包含与测试条件中指定的“所有”元素匹配的元素的文档。这很可能是您想要的,但是在某些情况下,您当然要指定一个列表,例如“四个或更多”艺术家进行测试,并且只想要“三个”或更少的数字,在这种情况下$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,它将找到那些文档,其中您的“测试”数组中存在的至少一个元素存在于文档自己的数组中的至少一个成员字段中。

下一个子句最好是在代码中构建,因为它与数组的“长度”有关。这里的想法是您希望至少“三个”匹配,那么您在文档中测试的数组必须至少具有“三个”元素才能满足这一要求,因此检索具有“两个”或更少数组元素的文档没有意义因为它们永远无法匹配“三”。

由于所有 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 } }}
])

【讨论】:

    猜你喜欢
    • 2017-03-15
    • 2018-04-15
    • 2019-11-09
    • 2023-04-03
    • 1970-01-01
    • 1970-01-01
    • 2022-01-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多