MongoDB:使用嵌入式文档中的“或”运算符对查询进行排序和限制

时间:2014-07-02 13:03:51

标签: mongodb mongodb-query aggregation-framework

具有以下数据结构:

"feature": {
  "site": {
    "subjects": [ 
      {                   
        "subject_id" : 1,
        "time" : ISODate("2014-06-28T06:38:29.751Z")
      }
    ],
  },
  "mobile": {            
    "subjects" : [ 
      {                    
        "subject_id" : 1,
        "time" : ISODate("2014-06-28T16:14:29.758Z")
      }, 
      {                    
        "subject_id" : 2,
        "time" : ISODate("2014-06-24T23:44:29.759Z")
      }
    ]
  }
}

我希望进行查询以获取嵌入了ID为1的主题的所有功能,无论是在移动设备中还是#39;或者'网站'。有了这个查询:

db.features.find( { $or: [ { site.subjects.subject_id: 1 }, { mobile.subjects.subject_id:1 } ] } )

如何按(移动或网站).subjects.time?

对这样的查询进行排序

1 个答案:

答案 0 :(得分:3)

您的"排序"的一般情况问题是需要有一个特定的"要排序的字段值。通过在文档中包含该字段作为创建或更新,可以获得最佳性能。你不能有条件地排序"单独使用查找。

如果你需要动态地做""然后你正在寻找"项目"在这种情况下,符合您条件的东西,为此您需要聚合框架。

这里存在一些陷阱,因为在对文档进行操作时,操作过程并不像通用查询逻辑那样宽容。大多数情况下,在处理数组时,您需要确保在使用数组时没有空内容。基于样本数据的一些额外样本提供了解决问题的指南:

{
    "_id" : ObjectId("53b49853c1a7b867c4541482"),
    "site" : {
        "subjects" : [
            {
                "subject_id" : 1,
                "time" : ISODate("2014-06-28T06:38:29.751Z")
            }
        ]
    },
    "mobile" : {
        "subjects" : [
            {
                "subject_id" : 1,
                "time" : ISODate("2014-06-28T16:14:29.758Z")
            },
            {
                "subject_id" : 2,
                "time" : ISODate("2014-06-24T23:44:29.759Z")
            }
        ]
    }
}
{
    "_id" : ObjectId("53b4ccb6fbc9071ff8fc2d5b"),
    "mobile" : {
        "subjects" : [
            {
                "subject_id" : 1,
                "time" : ISODate("2014-06-28T16:14:29.758Z")
            },
            {
                "subject_id" : 2,
                "time" : ISODate("2014-06-24T23:44:29.759Z")
            }
        ]
    }
}
{
    "_id" : ObjectId("53b4cf58c4e3a228da24c225"),
    "site" : {
        "subjects" : [
            {
                "subject_id" : 2,
                "time" : ISODate("2014-06-28T06:38:29.751Z")
            }
        ]
    },
    "mobile" : {
        "subjects" : [
            {
                "subject_id" : 1,
                "time" : ISODate("2014-06-28T16:14:29.758Z")
            },
            {
                "subject_id" : 2,
                "time" : ISODate("2014-06-24T23:44:29.759Z")
            }
        ]
    }
}
{
    "_id" : ObjectId("53b4d03bc4e3a228da24c227"),
    "site" : {
        "subjects" : [
            {
                "subject_id" : 1,
                "time" : ISODate("2014-06-28T18:38:29.751Z")
            }
        ]
    },
    "mobile" : {
        "subjects" : [
            {
                "subject_id" : 1,
                "time" : ISODate("2014-06-28T04:14:29.758Z")
            },
            {
                "subject_id" : 2,
                "time" : ISODate("2014-06-24T23:44:29.759Z")
            }
        ]
    }
}

第一份文件是您的基本样本,但另一种文件在某些​​方面有所不同,用于特定目的,以展示一些可能的问题,当然不一定表示您自己的数据。

第二份文件故意省略"网站"关键和第三,虽然"网站"存在," subject_id"不符合考虑的条件。是的,这是文件选择的$or条件,但我们在这里进一步考虑这些"子文档"符合标准的要素。这意味着" date"排序甚至过滤"内容不会考虑那些没有所需"subject_id": 1的项目。

首先看一下创建一个可以根据条件排序的值:

db.features.aggregate([
    { "$match": {
         "$or": [ 
             { "site.subjects.subject_id": 1 }, 
             { "mobile.subjects.subject_id": 1 }
         ]
    }},
    { "$project": {
        "site": 1,
        "mobile": 1,
        "scopy": { "$ifNull": ["$site.subjects", { "$const": [false] }] },
        "mcopy": { "$ifNull": ["$mobile.subjects", { "$const": [false] }] }
    }},
    { "$unwind": "$scopy" },
    { "$project": {
        "site": 1,
        "mobile": 1,
        "scopy": {
            "$cond": [
                { "$eq": [ "$scopy.subject_id", 1 ] },
                "$scopy.time",
                false
            ]
        },
        "mcopy": 1
    }},
    { "$sort": { "_id": 1, "scopy": -1 } },
    { "$group": {
        "_id": "$_id",
        "site": { "$first": "$site" },
        "mobile": { "$first": "$mobile" },
        "mcopy": { "$first": "$mcopy" },
        "scopy": { "$first": "$scopy" }
    }},
    { "$unwind": "$mcopy" },
    { "$project": {
        "site": 1,
        "mobile": 1,
        "scopy": 1,
        "mcopy": {
            "$cond": [
                { "$eq": [ "$mcopy.subject_id", 1 ] },
                "$mcopy.time",
                false
            ]
        }
    }},
    { "$sort": { "_id": 1, "mcopy": -1 } },
    { "$group": {
        "_id": "$_id",
        "site": { "$first": "$site" },
        "mobile": { "$first": "$mobile" },
        "mcopy": { "$first": "$mcopy" },
        "scopy": { "$first": "$scopy" }
    }},
    { "$project": {
        "site": { 
            "$ifNull": [ 
                "$site", 
                { "$const": { "subjects": [] } }
            ]
        },
        "mobile": {
            "$ifNull": [
                "$mobile",
                { "$const": { "subjects": [] } }
            ]
        },
        "best": { 
            "$cond": [
                { "$gt": [ "$mcopy", "$scopy" ] },
                "$mcopy",
                "$scopy"
            ]
        }
    }},
    { "$sort": { "best": -1 } },
    { "$project": {
        "site": 1,
        "mobile": 1
    }}
])

那应该按照" time"的顺序排序文件。价值来自"网站"哪一个具有最新价值。样本中的最后一个文档应首先出现。

现在,如果你真的要求"限制"就像你的标题一样,我认为这意味着"过滤"实际的#34;匹配"结果,然后你做的事情有点不同:

db.features.aggregate([
    { "$match": {
         "$or": [ 
             { "site.subjects.subject_id": 1 }, 
             { "mobile.subjects.subject_id": 1 }
         ]
    }},
    { "$project": {
        "wsite": { "$ifNull": ["$site.subjects", { "$const": [false] }] },
        "wmobile": { "$ifNull": ["$mobile.subjects", { "$const": [false] }] }
    }},
    { "$unwind": "$wsite" },
    { "$project": {
        "wsite": {
            "$cond": [
                { "$eq": [ "$wsite.subject_id", 1 ] },
                "$wsite",
                false
            ]
        },
        "wmobile": 1
    }},
    { "$group": {
        "_id": "$_id",
        "wsite": { "$addToSet": "$wsite" },
        "wmobile": { "$first": "$wmobile" },
        "msite": { "$max": "$wsite.time" },
        "csite": { "$sum": 1 }
    }},
    { "$unwind": "$wsite" },
    { "$match": {
        "$or": [ 
            { "wsite": { "$ne": false } },
            { "csite": 1 }
        ]
    }},
    { "$group": {
        "_id": "$_id",
        "wsite": { "$push": "$wsite" },
        "wmobile": { "$first": "$wmobile" },
        "msite": { "$first": "$msite" }
    }},
    { "$unwind": "$wmobile" },
    { "$project": {
        "wsite": 1,
        "wmobile": {
            "$cond": [
                { "$eq": [ "$wmobile.subject_id", 1 ] },
                "$wmobile",
                false
            ]
        },
        "msite": 1,
    }},
    { "$group": {
        "_id": "$_id",
        "wsite": { "$first": "$wsite" },
        "wmobile": { "$addToSet": "$wmobile" },
        "msite": { "$first": "$msite" },
        "mmobile": { "$max": "$wmobile.time" },
        "cmobile": { "$sum": 1 }
    }},
    { "$unwind": "$wmobile" },
    { "$match": { 
        "$or": [
            { "wmobile": { "$ne": false } },
            { "cmobile": 1 }
        ]
    }},
    { "$group": {
        "_id": "$_id",
        "wsite": { "$first": "$wsite" },
        "wmobile": { "$push": "$wmobile" },
        "msite": { "$first": "$msite" },
        "mmobile": { "$first": "$mmobile" }
    }},
    { "$project": {
        "site": {
            "subjects": {
                "$cond": [
                    { "$eq": [ "$wsite", { "$const": [false] } ] },
                    { "$const": [] },
                    "$wsite"
                ]
            }
        },
        "mobile": {
            "subjects": {
                "$cond": [
                    { "$eq": [ "$wmobile", { "$const": [false] } ] },
                    { "$const": [] },
                    "$wmobile"
                ]
            }
        },
        "best": { 
            "$cond": [
                { "$gt": [ "$mmobile", "$msite" ] },
                "$mmobile",
                "$msite"
            ]
        }
    }},
    { "$sort": { "best": -1 } },
    { "$project": {
        "site": 1,
        "mobile": 1
    }}
])

使用MongoDB 2.6的功能更加清晰,其中大多数阵列过滤可以在一个阶段内完成:

db.features.aggregate([
    { "$match": {
         "$or": [ 
             { "site.subjects.subject_id": 1 }, 
             { "mobile.subjects.subject_id": 1 }
         ]
    }},
    { "$project": {
        "wsite": {
            "$let": {
                "vars": {            
                    "list": { "$setDifference": [
                        {
                            "$map": {
                                 "input": {
                                     "$ifNull": [ 
                                         "$site.subjects",
                                         { "$literal": [false] }
                                     ]
                                 },
                                 "as": "el",
                                 "in": {
                                     "$cond": [
                                         { "$eq": [ "$$el.subject_id", 1 ] },
                                         "$$el",
                                         false
                                     ]
                                 }
                             }
                        },
                        [false]
                    ]}
                },
                "in": {
                    "$cond": [
                        { "$eq": [{ "$size": "$$list" }, 0 ] },
                        { "$literal": [false] },
                        "$$list"
                    ]
                }
            }    
        },
        "wmobile": {
            "$let": {
                "vars": {            
                    "list": { "$setDifference": [
                        {
                            "$map": {
                                 "input": {
                                     "$ifNull": [ 
                                         "$mobile.subjects",
                                         { "$literal": [false] }
                                     ]
                                 },
                                 "as": "el",
                                 "in": {
                                     "$cond": [
                                         { "$eq": [ "$$el.subject_id", 1 ] },
                                         "$$el",
                                         false
                                     ]
                                 }
                             }
                        },
                        [false]
                    ]}
                },
                "in": {
                    "$cond": [
                        { "$eq": [{ "$size": "$$list" }, 0 ] },
                        { "$literal": [false] },
                        "$$list"
                    ]
                }
            }    
        }
    }},
    { "$unwind": "$wsite" },
    { "$group": {
        "_id": "$_id",
        "wsite": { "$push": "$wsite" },
        "wmobile": { "$first": "$wmobile" },
        "fsite": { "$max": "$wsite.time" }
    }},
    { "$unwind": "$wmobile" },
    { "$group": {
        "_id": "$_id",
        "wsite": { "$first": "$wsite" },
        "wmobile": { "$push": "$wmobile" },
        "fsite": { "$first": "$fsite" },
        "fmobile": { "$max": "$wmobile.time" }
    }},
    { "$project": {
        "site": {
            "subjects": {
                "$cond": [
                    { "$allElementsTrue": "$wsite" },
                    "$wsite",
                    { "$literal": [] }
                ]
            }
        },
        "mobile": {
            "subjects": {
                "$cond": [
                    { "$allElementsTrue": "$wmobile" },
                    "$wmobile",
                    { "$literal": [] }
                ]
            }
        },
        "best": {
            "$cond": [
                { "$gt": [ "$fmobile", "$fsite" ] },
                "$fmobile",
                "$fsite"
            ]
        }
    }},
    { "$sort": { "best": -1 } },
    { "$project": {
        "site": 1,
        "mobile": 1
    }}
])

这些陈述中要考虑的主要内容属于数组处理。这里的各种操作需要"如果不存在实际数组,则输入数组将失败。更糟糕的情况是$unwind,如果出现完全"空"数组,将完全从管道中删除该文档,因为它认为没有任何内容可以"展开"。

主要"柜台"这是$ifNull。这基本上测试了"存在"一个字段,并返回它或替代结果,这是第二个参数。每个案例都使用它来返回一个包含单个元素[false]的数组,这意味着任何后续的$unwind不仅不会爆炸"由于缺少一个数组的字段,但也不认为当前文档是空的,因此将其删除。

    { "$project": {
        "site": 1,
        "mobile": 1,
        "scopy": { "$ifNull": ["$site.subjects", { "$const": [false] }] },
        "mcopy": { "$ifNull": ["$mobile.subjects", { "$const": [false] }] }
    }},

第一个样本保留原始字段,因为它只是返回它们"按原样#34;在弄清楚文件将如何分类之后。但与副本一样,或者以其他方式过滤"只有匹配的结果,这些将以某种方式被操纵到"过滤"并确定用于排序的日期。

在不更改现有阵列的情况下,第一个示例相对简单。在这里你想要做的基本上是"排序"文档中的数组,在您展开后一次一个,以获得最新的日期。

    { "$unwind": "$mcopy" },
    { "$project": {
        "site": 1,
        "mobile": 1,
        "scopy": 1,
        "mcopy": {
            "$cond": [
                { "$eq": [ "$mcopy.subject_id", 1 ] },
                "$mcopy.time",
                false
            ]
        }
    }},
    { "$sort": { "_id": 1, "mcopy": -1 } },

在此版本中进行的额外操作是确保将考虑的日期来自"子文档"符合标准。如果没有,那么日期将替换为false,它将被排序到列表的底部。

$group然后使用$first运算符在排序后选取最新的项目。现在为每个数组执行该过程会提供两个比较日期,以便您可以决定在最后对哪一个进行排序。

    { "$group": {
        "_id": "$_id",
        "site": { "$first": "$site" },
        "mobile": { "$first": "$mobile" },
        "mcopy": { "$first": "$mcopy" },
        "scopy": { "$first": "$scopy" }
    }},

在"过滤"方法,不仅是进行比较,看看" date"被认为是符合标准,但事实上整个"子文件"如果元素不匹配,则会考虑并删除它。

这里要注意不要"销毁"如果该数组中的任何内容都不匹配,则文档完全不会留下空数组或以其他方式删除文档。这解释了使用$unwind然后使用$project进行比较和"尺寸"接下来的匹配结果。

这些是$group使用$addToSet运算符的放置,因为您可以合理地假设结果是unqiue,并且在这种情况下也使用$max找到最大的&#34} ;日期"值。这也会将任何false值压缩为单个条目。

    { "$group": {
        "_id": "$_id",
        "wsite": { "$addToSet": "$wsite" },
        "wmobile": { "$first": "$wmobile" },
        "msite": { "$max": "$wsite.time" },
        "csite": { "$sum": 1 }
    }},

只有这样,您才能$unwind再次安全地使用$match过滤掉false的所有内容。如果在该数组中实际上只有false的单个值,则此处需要注意不要删除该文档。决赛"分组"现在应该有过滤结果或每个数组下只有false的单个值。

    { "$unwind": "$wsite" },
    { "$match": {
        "$or": [ 
            { "wsite": { "$ne": false } },
            { "csite": 1 }
        ]
    }},
    { "$group": {
        "_id": "$_id",
        "wsite": { "$push": "$wsite" },
        "wmobile": { "$first": "$wmobile" },
        "msite": { "$first": "$msite" }
    }},

在最终列表中,我们正在利用可以使用MongoDB 2.6中实现的功能完成的新功能。

上一个列表中的各种管道阶段是"组合"因为新的$map运算符允许一些数组处理而不使用$unwind。基本上对匹配条件进行相同的评估,并且返回的false值被"过滤掉"将$setDifference与仅包含[false]的数组进行比较。

任何"空"然后使用$size运算符测试不包含匹配的数组,其中empty将返回0的大小。这里的条件只是用以前的单[false]替换那些空数组。

最后一部分的原因是您仍然需要$unwind才能获得最大或$max"日期"每个数组的值。

    { "$unwind": "$wsite" },
    { "$group": {
        "_id": "$_id",
        "wsite": { "$push": "$wsite" },
        "wmobile": { "$first": "$wmobile" },
        "fsite": { "$max": "$wsite.time" }
    }},

从这里开始编码的不同方法大多相似。现在你要从每个数组中比较日期,你只需要确定哪一个是最新的或其他逻辑比较:

        "best": {
            "$cond": [
                { "$gt": [ "$fmobile", "$fsite" ] },
                "$fmobile",
                "$fsite"
            ]
        }
    }},
    { "$sort": { "best": -1 } },
    { "$project": {
        "site": 1,
        "mobile": 1
    }}

结果日期值用于 $sort 最终结果,然后传递给 $project 以删除我们的预计字段日期比较。

在任何一种情况下,通过与样本文件进行比较的结果顺序是"第四","第一","第二"和"第三"。 "第四"文档包含首选"网站上的最新日期"这是最好的结果。 "第一"样本具有将被选择的下一个最大日期。

" Second"和"第三"实际上选择相同的日期值,即使两者都没有可能的匹配条目#34; site"领域。这里订单的唯一原因实际上只是文档_id值,这是文档进入管道的方式。

没有"过滤"输出实际上是数组:

{
    "_id" : ObjectId("53b4d03bc4e3a228da24c227"),
    "site" : {
        "subjects" : [
            {
                "subject_id" : 1,
                "time" : ISODate("2014-06-28T18:38:29.751Z")
            }
        ]
    },
    "mobile" : {
        "subjects" : [
            {
                "subject_id" : 1,
                "time" : ISODate("2014-06-28T04:14:29.758Z")
            },
            {
                "subject_id" : 2,
                "time" : ISODate("2014-06-24T23:44:29.759Z")
            }
        ]
    }
}
{
    "_id" : ObjectId("53b4cf58c4e3a228da24c225"),
    "site" : {
        "subjects" : [
            {
                "subject_id" : 2,
                "time" : ISODate("2014-06-28T06:38:29.751Z")
            }
        ]
    },
    "mobile" : {
        "subjects" : [
            {
                "subject_id" : 1,
                "time" : ISODate("2014-06-28T16:14:29.758Z")
            },
            {
                "subject_id" : 2,
                "time" : ISODate("2014-06-24T23:44:29.759Z")
            }
        ]
    }
}
{
    "_id" : ObjectId("53b4ccb6fbc9071ff8fc2d5b"),
    "site" : {
        "subjects" : [ ]
    },
    "mobile" : {
        "subjects" : [
            {
                "subject_id" : 1,
                "time" : ISODate("2014-06-28T16:14:29.758Z")
            },
            {
                "subject_id" : 2,
                "time" : ISODate("2014-06-24T23:44:29.759Z")
            }
        ]
    }
}
{
    "_id" : ObjectId("53b49853c1a7b867c4541482"),
    "site" : {
        "subjects" : [
            {
                "subject_id" : 1,
                "time" : ISODate("2014-06-28T06:38:29.751Z")
            }
        ]
    },
    "mobile" : {
        "subjects" : [
            {
                "subject_id" : 1,
                "time" : ISODate("2014-06-28T16:14:29.758Z")
            },
            {
                "subject_id" : 2,
                "time" : ISODate("2014-06-24T23:44:29.759Z")
            }
        ]
    }
}

并通过过滤:

{
    "_id" : ObjectId("53b4d03bc4e3a228da24c227"),
    "site" : {
        "subjects" : [
             {
                 "subject_id" : 1,
                 "time" : ISODate("2014-06-28T18:38:29.751Z")
             }
        ]
    },
    "mobile" : {
        "subjects" : [
             {
                 "subject_id" : 1,
                 "time" : ISODate("2014-06-28T04:14:29.758Z")
             }
        ]
    }
}
{
    "_id" : ObjectId("53b49853c1a7b867c4541482"),
    "site" : {
        "subjects" : [
             {
                 "subject_id" : 1,
                 "time" : ISODate("2014-06-28T06:38:29.751Z")
             }
        ]
    },
    "mobile" : {
        "subjects" : [
             {
                 "subject_id" : 1,
                 "time" : ISODate("2014-06-28T16:14:29.758Z")
             }
        ]
    }
}
{
    "_id" : ObjectId("53b4ccb6fbc9071ff8fc2d5b"),
    "site" : {
        "subjects" : [ ]
    },
    "mobile" : {
        "subjects" : [
             {
                 "subject_id" : 1,
                 "time" : ISODate("2014-06-28T16:14:29.758Z")
             }
        ]
    }
}
{
    "_id" : ObjectId("53b4cf58c4e3a228da24c225"),
    "site" : {
        "subjects" : [ ]
    },
    "mobile" : {
        "subjects" : [
             {
                 "subject_id" : 1,
                 "time" : ISODate("2014-06-28T16:14:29.758Z")
             }
        ]
    }
}

这里的主要案例是,虽然可以"项目"像这样的字段用于比较通常最好将它保存在您的文档中,因为您可以快速排序,而无需为每个文档构建第一个文档。

如果确实需要"过滤"数组会产生符合条件的数据,那么你确实会这样做,因为位置$运算符可用的投影不支持与" 2"阵列。

无论如何,至少这可以作为更高级的文档使用的样本"重塑"使用聚合框架并显示其中的可能性。但是,与所有复杂的操作一样,这确实需要付出代价,因此在性能方面,您应该围绕这一点设计数据。