【问题标题】:MongoDB partial match on objects对象上的 MongoDB 部分匹配
【发布时间】:2018-12-18 13:42:15
【问题描述】:

例如,我有一个这样的文档:

{
   name: "Test",
   params: {
     device: "windows",
     gender: "m",
     age: 28
   }
}

现在我得到以下参数输入:

{
   device: "windows",
   gender: "m"
}

在这种情况下,我想查找与输入对象部分匹配的所有文档。有没有简单的方法来解决这个问题?我试过 $elemMatch 但这似乎只适用于数组。

【问题讨论】:

    标签: mongodb mongodb-query


    【解决方案1】:

    如果我正确理解了您的问题,您有一个输入对象,其中可能包含 一些 主文档的 params 对象中的字段,按 any 顺序排列,用于示例:

    {
       device: "windows",
       gender: "m"
    }
    
    {
       gender: "m",
       device: "windows"
    }
    
    {
       device: "windows",
       age: 28
    }
    

    并且您只想匹配所有输入对象中的字段存在于主文档中:

    {
       device: "linux",    // NO MATCH
       gender: "m"
    }
    
    {
       gender: "m",        // MATCH
       device: "windows"
    }
    
    {
       device: "windows",  // NO MATCH
       age: 29
    }
    

    正确吗?


    选项 1

    您的 param 对象是否始终只包含这三个字段(devicegenderage)?

    如果是这样,您可以在根级别手动投影它们中的每一个,进行匹配,然后再次“取消投影”它们:

    db.my_collection.aggregate([
        {
            $project: {
                name: 1, 
                params: 1, 
                device: "$params.device",  // add these three for your match stage
                gender: "$params.gender", 
                age: "$params.age"
            }
        },
        {
            $match: input_params
        },
        {
            $project: {name: 1, params: 1}  // back to original
        }
    ]);
    

    但我假设您想对 params 对象内的任意数量的字段执行此操作。


    选项 2

    有办法操纵输入对象吗?如果是这样,您可以在所有字段前加上“params.”:

    let input_params = 
    {
       device: "windows",
       gender: "m"
    };
    
    let new_input_params =
    {
       "params.device": "windows",
       "params.gender": "m"
    };
    

    那么你的查询就是:

    db.my_collection.find(new_input_params);
    

    选项 3

    如果您无法修改输入,您可以使用 $replaceRoot 聚合运算符(从 3.4 版开始,归功于 this answer)将您的 params 展平为根文档。由于这会将根文档替换为嵌入的文档,因此您需要先提取您感兴趣的字段,至少是 _id 字段:

    db.my_collection.aggregate([
        {
            $addFields: {"params.id": "$_id"}  // save _id inside the params doc, 
                                               // as you will lose the original one
        },
        {
            $replaceRoot: {newRoot: "$params"}
        },
        {
            $match: input_params
        },
        ...
    ]);
    

    这将匹配文档并保留_id 字段,您可以使用该字段再次获取文档的其余部分,例如通过$lookup

        ...
        {
            $lookup: {
                from: "my_collection", 
                localField: "id", 
                foreignField: "_id", 
                as: "doc"
            }
        },
        ...
    

    这将使您的文档采用以下格式:

    { 
      "device" : "windows", 
      "gender" : "m", 
      "age" : 28, 
      "id" : ObjectId("XXX"), 
      "doc" : [ 
        {
          "_id" : ObjectId("XXX"), 
          "name" : "Test", 
          "params" : { 
            "device" : "windows", 
            "gender" : "m", 
            "age" : 28 
          }
        }
      ]
    }
    

    要完整循环并恢复原始文档格式,您可以:

        ...
        {
            $unwind: "$doc" // get rid of the [] around the "doc" object 
                            // ($lookup always results in array)
        },
        {
            $replaceRoot: {newRoot: "$doc"} // get your "doc" back to the root
        }
        ...
    

    在我写这篇文章时,我不敢相信单独使用 MongoDB 没有更清洁的方法,但我想不出任何方法。

    我希望这会有所帮助!

    【讨论】:

      【解决方案2】:

      您可以使用$or 运算符对来自 params 对象的字段进行部分比较。你可以这样使用:

      db.collection.find({
        $or: [
          {
            "params.device": "windows"
          },
          {
            "params.gender": "m"
          }
        ]
      })
      

      【讨论】:

        【解决方案3】:

        Using projection

        一个文档可能有很多字段,但并非所有这些字段在请求时都是必要且重要的。在这种情况下,您只能使用投影在示例中包含必填字段。

        默认情况下,MongoDB 中的查询返回匹配的所有字段 文件。限制 MongoDB 发送到的数据量 应用程序,您可以包含一个投影文档来指定或 限制要返回的字段。

        例如,您之前将以下文档添加到数据库中:

        > db.users.insertOne({"name": "Jennifer", "gender": "female", languages: ["english", "spanish"]})
        > db.users.insertOne({"name": "Alex", "gender": "male", languages: ["english", "french"]})
        > db.users.insertOne({"name": "Maria", "gender": "female", languages: ["english", "german"]})
        

        现在您要显示与字段gender 匹配并具有值female 的信息,因此您可以通过查询指定它:

        db.users.find({gender: "female"}, {name: 1})
        

        该操作返回以下文档:

        { "name" : "Jennifer", "gender" : "female" }
        { "name" : "Maria", "gender" : "female" }
        

        <value> 可以是以下任何一种:

        { field1: <value>, field2: <value> ... }
        
        • 1 或 true 以在退货单据中包含该字段。
        • 0 或 false 以排除该字段。
        • 使用投影运算符的表达式。

        此外,如果你不想具体化选择,但想显示所有文档,那么我们可以将第一个括号留空:

        db.users.find({}, {name: 1, _id: 0})
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-09-08
          • 1970-01-01
          • 2015-09-06
          • 1970-01-01
          • 2017-06-21
          • 2021-05-26
          • 1970-01-01
          相关资源
          最近更新 更多