I will query one value of "e" for various values of "r" and sort by "s". Rather than creating an Equality, Sort, Range index (ESR), I believe the Range condition is much more selective and crucial than the sort, so I will create an index with the order of Equality, Range, Sort (ERS):
test> db.demoesr.find(
{ e:{ $eq: 42 }, r:{ $in:[
201, 200, 199, 198, 197, 196, 195, 194, 193, 192, 191, 190, 189, 188, 187, 186, 185, 184, 183, 182, 181, 180, 179, 178, 177, 176, 175, 174, 173, 172, 171, 170, 169, 168, 167, 166, 165, 164, 163, 162, 161, 160, 159, 158, 157, 156, 155, 154, 153, 152, 151, 150, 149, 148, 147, 146, 145, 144, 143, 142, 141, 140, 139, 138, 137, 136, 135, 134, 133, 132, 131, 130, 129, 128, 127, 126, 125, 124, 123, 122, 121, 120, 119, 118, 117, 116, 115, 114, 113, 112, 111, 110, 109, 108, 107, 106, 105, 104, 103, 102, 101, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
] } } ,
{ e:1,s:1,r:1,"_id":0 }
).sort({ s: 1 }).explain("executionStats").executionStats
;
{
executionSuccess: true,
nReturned: 4,
totalKeysExamined: 5,
totalDocsExamined: 0,
executionStages: {
stage: 'SORT',
nReturned: 4,
sortPattern: { s: 1 },
inputStage: {
stage: 'PROJECTION_COVERED',
nReturned: 4,
transformBy: { e: 1, s: 1, r: 1, _id: 0 },
inputStage: {
stage: 'IXSCAN',
nReturned: 4,
keyPattern: { e: 1, r: 1, s: 1 },
indexName: 'e_1_r_1_s_1',
isMultiKey: false,
multiKeyPaths: { e: [], r: [], s: [] },
direction: 'forward',
indexBounds: {
e: [ '[42, 42]' ],
r: [
'[1, 1]', '[2, 2]', '[3, 3]', '[4, 4]', '[5, 5]',
'[6, 6]', '[7, 7]', '[8, 8]', '[9, 9]', '[10, 10]',
'[11, 11]', '[12, 12]', '[13, 13]', '[14, 14]', '[15, 15]',
'[16, 16]', '[17, 17]', '[18, 18]', '[19, 19]', '[20, 20]',
'[21, 21]', '[22, 22]', '[23, 23]', '[24, 24]', '[25, 25]',
'[26, 26]', '[27, 27]', '[28, 28]', '[29, 29]', '[30, 30]',
'[31, 31]', '[32, 32]', '[33, 33]', '[34, 34]', '[35, 35]',
'[36, 36]', '[37, 37]', '[38, 38]', '[39, 39]', '[40, 40]',
'[41, 41]', '[42, 42]', '[43, 43]', '[44, 44]', '[45, 45]',
'[46, 46]', '[47, 47]', '[48, 48]', '[49, 49]', '[50, 50]',
'[51, 51]', '[52, 52]', '[53, 53]', '[54, 54]', '[55, 55]',
'[56, 56]', '[57, 57]', '[58, 58]', '[59, 59]', '[60, 60]',
'[61, 61]', '[62, 62]', '[63, 63]', '[64, 64]', '[65, 65]',
'[66, 66]', '[67, 67]', '[68, 68]', '[69, 69]', '[70, 70]',
'[71, 71]', '[72, 72]', '[73, 73]', '[74, 74]', '[75, 75]',
'[76, 76]', '[77, 77]', '[78, 78]', '[79, 79]', '[80, 80]',
'[81, 81]', '[82, 82]', '[83, 83]', '[84, 84]', '[85, 85]',
'[86, 86]', '[87, 87]', '[88, 88]', '[89, 89]', '[90, 90]',
'[91, 91]', '[92, 92]', '[93, 93]', '[94, 94]', '[95, 95]',
'[96, 96]', '[97, 97]', '[98, 98]', '[99, 99]', '[100, 100]',
... 101 more items
],
s: [ '[MinKey, MaxKey]' ]
},
keysExamined: 5,
seeks: 1
}
}
}
}
This is the best way to filter the documents for retrieval, utilizing multiple ranges with a skip scan as discussed in the previous post. However, because it doesn't adhere to the ESR rule, the index failed to return the entries in the expected order, necessitating an additional SORT operation.
I've rarely seen such intelligent optimization in a database before. Oracle Database can convert a list into a concatenation (UNION ALL) using a technique known as OR Expansion, yet it lacks a merge sort feature on top of it. On the other hand, PostgreSQL can perform a merge sort on concatenated UNION but does not support the OR Expansion transformation to get it transparently. YugabyteDB employs a similar method for batching the nested loop join by pushing down the join condition as an array.
The internal name of this feature in MongoDB query planner is Explode for Sort, as a combination of Index Scan Explosion and Merge Sort and is advantageous with pagination when the limit is pushed down to each index scan.
It can be used by $or:{} or $in:[]. For example, the following query read one key from each index scan: