一个字段里混着数字、字符串、日期、null,排序谁在前?{$gt: 5} 会不会把字符串也查出来?这篇把 16 种类型放进同一个字段,用 sort、比较运算和 $cmp 逐个验证。
test> db.t.find({}, {_id: 0, name: 1}).sort({v: 1}).toArray().map(d => d.name)
[
'MinKey', 'null',
'(字段缺失)', 'number -1 (long)',
'number 5', 'number 5.5 (double)',
'string "a"', 'string "b"',
'object {a:1}', 'BinData',
'ObjectId', 'false',
'true', 'Date',
'Timestamp', 'regex',
'MaxKey'
]MongoDB 对不同类型有固定的排序:MinKey < null < 数字 < 字符串 < 对象 < 数组 < BinData < ObjectId < 布尔 < 日期 < Timestamp < 正则 < MaxKey。数字不区分 int、long、double、decimal,字段缺失与 null 并列。
test> db.t.find({v: {$gt: 5}}, {_id: 0, name: 1}).toArray().map(d => d.name)
[ 'number 5.5 (double)' ]
test> db.t.find({v: {$lt: "b"}}, {_id: 0, name: 1}).toArray().map(d => d.name)
[ 'string "a"' ]
test> db.t.countDocuments({v: {$gte: MinKey()}}) + " / " + db.t.countDocuments()
17 / 17
test> db.t.countDocuments({v: {$lte: MaxKey()}}) + " / " + db.t.countDocuments()
17 / 17$gt、$lt 只在同一类型括号内比较,所以 {v: {$gt: 5}} 不会返回字符串或日期,即使它们排在数字后面。MinKey 和 MaxKey 是例外。
test> db.t.find({v: null}, {_id: 0, name: 1}).toArray().map(d => d.name)
[ 'null', '(字段缺失)' ]
test> db.t.find({v: {$exists: false}}, {_id: 0, name: 1}).toArray().map(d => d.name)
[ '(字段缺失)' ]
test> db.t.find({v: {$type: "null"}}, {_id: 0, name: 1}).toArray().map(d => d.name)
[ 'null' ]
test> db.t.find({v: {$in: [null]}}, {_id: 0, name: 1}).sort({_id: 1}).toArray().map(d => d.name)
[ 'null', '(字段缺失)' ]{v: null} 匹配 null 和缺失两种;要区分用 $exists 或 $type。
test> db.a.find().sort({v: 1}).toArray().map(d => JSON.stringify(d.v))
[ '[9,0]', '[5,1]', '2', '[3]' ]
test> db.a.find().sort({v: -1}).toArray().map(d => JSON.stringify(d.v))
[ '[9,0]', '[5,1]', '[3]', '2' ]升序排序时数组用最小的元素参与比较,降序用最大的元素,所以 [9,0] 升序排最前、降序也排最前。
test> db.o.find().sort({v: 1}).toArray().map(d => JSON.stringify(d.v))
[
'{}',
'{"a":1}',
'{"a":1,"b":1}',
'{"a":2}',
'{"b":1}',
'{"b":1,"a":1}'
]子文档逐个字段比较类型、字段名、值,字段顺序不同的两个子文档不相等,也排在不同的位置。
test> db.t.aggregate([{$limit: 1}, {$project: {_id: 0, number_vs_string: {$cmp: [1, "a"]}, string_vs_object: {$cmp: ["a", {}]}, object_vs_array: {$cmp: [{}, []]}, array_vs_bool: {$cmp: [[], true]}, null_vs_number: {$cmp: [null, 0]}, int_vs_double: {$cmp: [NumberInt(1), 1.5]}, long_vs_double: {$cmp: [NumberLong("2"), 1.5]}}}])
[
{
number_vs_string: -1,
string_vs_object: -1,
object_vs_array: -1,
array_vs_bool: -1,
null_vs_number: -1,
int_vs_double: -1,
long_vs_double: 1
}
]聚合里的 $cmp 使用同一套顺序,可以用来快速验证任意两个值谁大;数值类型不同按数值大小比较。