如何在Mongoid中将NULL值排至末尾并保留Mongoid::Criteria用于分页?
我完全懂你的困扰——既要把nil值压到排序结果的最后,又得保住Mongoid::Criteria对象来支持分页,总不能转成数组或者用聚合返回个没法分页的对象对吧?之前试的merge和聚合管道都没踩对路子,我给你俩靠谱的方案:
方案1:兼容性拉满的存在性排序法(适配所有MongoDB版本)
Mongoid支持组合排序条件,我们可以先按sort_index字段是否存在来排序,把有值的文档优先排前面,再按sort_index的实际值升序排列。这样既能把nil值甩到末尾,又全程返回Mongoid::Criteria,完美兼容分页。
代码示例:
Category.where(_type: "Category").order_by( { sort_index: { "$exists": true } => :desc }, { sort_index: :asc } )
原理很简单:{ sort_index: { "$exists": true } => :desc }会让所有包含sort_index字段(哪怕字段值是0或者负数,但不会是nil)的文档排在前面;紧接着的sort_index: :asc会把这些非nil的数值按从小到大排列,nil的文档自然就被挤到最后了。
方案2:更简洁的排序表达式法(MongoDB 4.2+)
如果你的MongoDB版本在4.2及以上,可以直接在排序逻辑里用$ifNull表达式,把nil值替换成一个极大的数(比如Float::INFINITY),这样升序排序时它们肯定排在最后。这种写法更直观,而且同样返回Mongoid::Criteria对象。
代码示例:
Category.where(_type: "Category").order_by( { "$expr": { "$ifNull": ["$sort_index", Float::INFINITY] } => :asc } )
这里$expr允许我们在排序中使用聚合表达式,$ifNull会把sort_index为nil的文档替换成无穷大,升序排序时无穷大的数值必然在最后,非nil的数值则按正常顺序排列,完全符合你的需求,分页也能直接用(比如page(2).per(5)这类方法都能正常工作)。
为啥之前的方法不行?
- 用
merge合并两个查询:merge是合并查询条件,不是合并结果集,所以会变成“既满足sort_index是nil又满足sort_index非nil”的矛盾条件,自然查不到数据。 - 聚合管道:返回的
Mongo::Collection::View::Aggregation是结果视图,不是Mongoid::Criteria,没法直接用Mongoid的分页方法。 partition:直接把结果转成数组,分页逻辑就没法利用MongoDB的游标优化了,数据量大的时候性能会崩。
内容的提问来源于stack exchange,提问作者Shrikanth Hathwar

