@ahmad_al_sharbaji ,
The main problem in your approach with $searchMeta, that it is used to provide metadata (information about the data, like bucket boundaries or total number of documents, that fall under certain set of conditions), not the data itself.
However, I did not say it is not possible to get the data you want. But, you gonna need to play dirty
.
If you look chosely at the example of how $search meta is used with facets, you can see, that some data, can be extracted. It just needs to become good boundary names for the buckets. A good name, in your case should:
- represent document position in the result set, so it is clear whether is older or newer, comparing to the other ones.
- be unique enough, so can easily select one document among other ones.
I think a good candidate for that name can be createdAt
field (see my example dataset below), but stringified - `createdAtString.
I will demonstrate the idea with examples below.
Example dataset:
db.messages.insertMany([
{
_id: 'M1',
text: 'Lorem ipsum dolor sit amet',
channelId: 1,
createdAt: ISODate('2023-08-11T15:00:00.000Z'), // 15:00:00
createdAtString: '2023-08-11T15:00:00.000Z',
},
{
_id: 'M2',
channelId: 1,
text: 'Eiusmod tempor incididunt',
createdAt: ISODate('2023-08-11T15:30:00.000Z'), // 15:30:00
createdAtString: '2023-08-11T15:30:00.000Z',
},
{
_id: 'M3',
channelId: 1,
text: 'Ut labore et dolore magna aliqua',
createdAt: ISODate('2023-08-11T16:00:45.000Z'), // 16:00:45
createdAtString: '2023-08-11T16:00:45.000Z',
},
{
_id: 'M4',
channelId: 2,
text: 'Excepteur sint occaecat cupidatat',
createdAt: ISODate('2023-08-11T15:00:25.000Z'), // 15:00:25
createdAtString: '2023-08-11T15:00:25.000Z',
},
{
_id: 'M5',
channelId: 2,
text: 'Fugiat nulla pariatur',
createdAt: ISODate('2023-08-11T16:01:15.000Z'), // 16:01:15
createdAtString: '2023-08-11T16:01:15.000Z',
},
]);
Example aggregation pipeline that contains conditions close to your query:
db.messages.aggregate([
{
$searchMeta: {
index: 'messages-test-search',
count: {
type: 'total'
},
facet: {
operator: {
compound: {
must: [
{
range: {
path: 'createdAt',
gte: ISODate('2023-08-11T15:00:00.000Z'),
lte: ISODate('2023-08-11T16:00:00.000Z')
},
},
{
equals: {
path: 'channelId',
value: 1
}
}
// you can add more equality conditions in this array
]
},
},
facets: {
myFacet: {
type: 'string',
// field 'createdAtString' will be used as a bucket name
path: 'createdAtString',
}
}
},
}
},
]);
Output:
[
{
count: { total: Long("2") },
facet: {
myFacet: {
buckets: [
{ _id: '2023-08-11T15:00:00.000Z', count: Long("1") },
{ _id: '2023-08-11T15:30:00.000Z', count: Long("1") }
]
}
}
}
]
As you can see, now we have metadata (total documents selected) and actual data (docuement’s createdAt
) in form of bucket names. Then, will little efforts, we can get the result you want:
db.messages.aggregate([
{
$searchMeta: { /* unchanged */ }
},
{
$unwind: '$facet.myFacet.buckets'
},
{
$project: {
total: '$count.total',
createdAtBoundary: {
$toDate: '$facet.myFacet.buckets._id',
}
}
},
{
$group: {
_id: null,
total: {
$first: '$total'
},
largestCreatedAt: {
$max: '$createdAtBoundary'
}
}
}
]);
Final output:
[
{
_id: null,
total: Long("2"),
largestCreatedAt: ISODate("2023-08-11T15:30:00.000Z")
}
]
Atlas Search Index configuration object I used:
{
"mappings": {
"dynamic": false,
"fields": {
"channelId": {
"type": "number"
},
"createdAt": {
"type": "date"
},
"createdAtString": {
"type": "stringFacet"
}
}
}
}
If you afraid that two documents in your collection can have exact same createdAt
value, you can, for example, concatenate document id to that string, so the value would look like ‘2023-08-11T15:30:00.000Z__M1’. But, in this case, you will have to add additional stages to disassemble this string in order to work with dates.
Again, it is a dirty solution, but it works with $searchMeta 