Mongodb use $elemMatch into $filter - arrays

I have a document like this:
I need to return the documents and filter the nested array (lessons) where any item of subLessons into input array
[{
"_id": {
"$oid": "6081fedbee5d133dbffb42eb"
},
"name": "my quiz",
"lessons": [
{
"_id": "460c42e1-b0b7-437e-ab63-c59cce8ced0d",
"name": "section",
"subLesson": [
{
"$oid": "6081fed9ee5d133dbffb3cba"
},
{
"$oid": "6081fed9ee5d133dbffb3cc0"
}
]
},
{
"_id": "f7b5c95f-1a68-42ca-880c-22ef3831ff03",
"name": "ffff",
"subLesson": [
{
"$oid": "6081fed9ee5d133dbffb3cbb"
}
]
}
]
}
}]
I wrote the following query but it does not work. I do not know how to use $elemMatch in $filter
db.collection.aggregate([
{
"$project": {
_id: 1,
lessons: {
$filter: {
"input": "$lessons",
"as": "lesson",
"cond": {
"$$lesson.subLesson": {
"$elemMatch": {
"$in": [
ObjectId("6081fed9ee5d133dbffb3cba")
]
}
}
}
}
}
}
}
])
I am trying to find the record such that the result looks like the following.
[{
"_id": {
"$oid": "6081fedbee5d133dbffb42eb"
},
"lessons": [
{
"_id": "460c42e1-b0b7-437e-ab63-c59cce8ced0d",
"name": "zzzz",
"subLesson": [
{
"$oid": "6081fed9ee5d133dbffb3cba"
},
{
"$oid": "6081fed9ee5d133dbffb3cc0"
}
]
}
]
},
}]
Can anyone please help out to understand how can I make this work
thanks

You can use $in directly
db.collection.aggregate([
{
$project: {
lessons: {
$filter: {
input: "$lessons",
cond: {
$in: [ ObjectId("6081fed9ee5d133dbffb3cba"), "$$this.subLesson" ]
}
}
}
}
}
])
Working Mongo playground
Update 1
db.collection.aggregate([
{ "$unwind": "$lessons" },
{
"$match": {
"lessons.subLesson": {
$in: [ ObjectId("6081fed9ee5d133dbffb3cba"), ObjectId("6081fed9ee5d133dbffb3cbb") ]
}
}
},
{
$group: {
_id: "$_id",
name: { $first: "$name" },
lessons: { $push: "$lessons" }
}
}
])
Mongo Playground

Related

Finding ID of mongo documents with duplicated elements in nested array

I would like to extract from the collection the IDs of documents that have duplicate IDs of "drives" objects that are nested in the array that is in "streetModel".
This is my typical document :
{
"_id": {
"$oid": "61375bec4fa522001b608568"
},
"name": "Streetz",
"statusDetail": {},
"streetModel": {
"_id": "3.7389-51.0566",
"name": "Kosheen - Darude - Swedish - Trynidad - Maui",
"countryCode": "DEN",
"drives": [{
"_id": -903500698,
"direction": "WEST"
}, {
"_id": 1915399546,
"direction": "EAST"
}, {
"_id": 1294835467,
"direction": "NORTH"
}, {
"_id": 1248969937,
"direction": "EAST"
}, {
"_id": 1248969937,
"direction": "EAST"
}, {
"_id": 1492411786,
"direction": "SOUTH"
}]
},
"createdAt": {
"$date": "2021-09-07T12:32:44.238Z"
}
}
In this particular document with the ID 61375bec4fa522001b608568, in "streetModel", in "drives" array I have got duplicated drives objects with id 1248969937.
I would like to create a query to the database that will return the ID of all documents with such a problem (duplicate "drives").
Right now I have got this:
db.streets.aggregate([
{
$unwind: "$streetModel"
},
{
$unwind: "$drives"
},
{
$group: {
_id: {
id: "$_id"
},
sum: {
$sum: 1
},
}
},
{
$match: {
sum: {
$gt: 1
}
}
},
{
$project: {
_id: "$_id._id",
duplicates: {
drives: "$_id"
}
}
}
])
but that's not it.
I try in many ways to rewrite this query, but unfortunately it doesn't work.
Query
unwind
group by document id + driverid
keep only those that had more than one time same driveid
replace-root is to make the document better looking, you could $project also instead
if you need any more stage i think you can add it, for examplpe to get the documents that have this problem project only the docid's
Test code here
db.collection.aggregate([
{
"$unwind": {
"path": "$streetModel.drives"
}
},
{
"$group": {
"_id": {
"docid": "$_id",
"driveid": "$streetModel.drives._id"
},
"duplicates": {
"$push": "$streetModel.drives.direction"
}
}
},
{
"$match": {
"$expr": {
"$gt": [
{
"$size": "$duplicates"
},
1
]
}
}
},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [
"$_id",
"$$ROOT"
]
}
}
},
{
"$project": {
"_id": 0
}
}
])

Mongodb transform array using aggregation

I have an array in Mongodb collection:
some_array:
0: title: "Community"
model: "A"
1: title: "News"
model: "B"
How can I get only titles of array and list them as bellow:
titles: "Community, News"
You can group by null means group all titles and then project titles array only
Working Playground
db.collection.aggregate([
{
"$group": {
"_id": null,
"titles": {
"$push": "$title"
}
}
},
{
"$project": {
_id: 0,
titles: {
"$reduce": {
"input": "$titles",
"initialValue": "",
"in": {
"$cond": {
"if": { "$eq": [ { "$indexOfArray": [ "$titles", "$$this" ] }, 0 ] },
"then": { "$concat": [ "$$value", "$$this" ] },
"else": { "$concat": [ "$$value", ", ", "$$this" ] }
}
}
}
}
}
}
])
db.collection.aggregate([
{
$project: {
titles: "$somearray.title"
}
}
])
Working Mongo playground

Incorrect count from aggregation query

In the following document collection, I am trying to find the total words of unique sentences. The total words must come out as 5 (hello\nworld, how are you?) + 5 (hello world, I am fine) + 3(Is it raining?) + 5(Look at the beautiful tiger!) = 18
[
{
"sourceList": [
{
"source": "hello\nworld, how are you?",
"_id": ObjectId("5f0eb9946db57c0007841153")
},
{
"source": "hello world, I am fine",
"_id": ObjectId("5f0eb9946db57c0007841153")
},
{
"source": "Is it raining?",
"_id": ObjectId("5f0eb9946db57c0007841153")
}
]
},
{
"sourceList": [
{
"source": "Look at the beautiful tiger!",
"_id": ObjectId("5f0eb9946db57c0007841153")
},
{
"source": "Is it raining?",
"_id": ObjectId("5f0eb9946db57c0007841153")
}
]
}
]
But with the below query
db.collection.aggregate([
{
"$unwind": "$sourceList"
},
{
$project: {
"sp": {
$split: [
"$sourceList.source",
"\n"
],
$split: [
"$sourceList.source",
" "
]
}
}
},
{
"$group": {
"_id": null,
"elements": {
$addToSet: "$sp"
}
}
},
{
"$unwind": "$elements"
},
{
"$project": {
"sizes": {
"$size": "$elements"
}
}
},
{
"$group": {
"_id": null,
"count": {
"$sum": "$sizes"
}
}
}
])
it gives as 17. What could be the reason for this? I am first trying to split by \n and then by space
EDIT
I am trying to find word count for unique sentences and total unique sentences.
The problem is that here:
"sp": {
$split: [
"$sourceList.source",
"\n"
],
$split: [
"$sourceList.source",
" "
]
}
only the second $split gets executed by MongoDB and it returns hello\nworld as one string. There's no such "cascade" syntax, since it's simply the same JSON key $split so last wins.
In order to fix that you can use $reduce to apply $split by whitespace on an array of split by \n values:
{
$project: {
"sp": {
$reduce: {
input: { $split: [ "$sourceList.source", "\n" ] },
initialValue: [],
in: { $concatArrays: [ "$$value", { $split: [ "$$this", " " ] } ] }
}
}
}
}
Mongo Playground
As per the comments and addition to #micki's answer and my previous answer,
play
db.collection.aggregate([
{
"$unwind": "$sourceList"
},
{
$project: {
"sp": {
$reduce: {
input: {
$split: [
"$sourceList.source",
"\n"
]
},
initialValue: [],
in: {
$concatArrays: [
"$$value",
{
$split: [
"$$this",
" "
]
}
]
}
}
}
}
},
{
"$group": {
"_id": null,
"elements": {
$addToSet: "$sp"
}
}
},
{
"$project": {
"unique_sen": {
"$size": "$elements"
},
"elements": 1
}
},
{
"$unwind": "$elements"
},
{
"$project": {
"sizes": {
"$size": "$elements"
},
"unique_sen": 1
}
},
{
"$group": {
"_id": null,
"unique_count": {
"$sum": "$sizes"
},
"data": {
$push: "$$ROOT"
}
}
},
{
"$project": {
"unique_count": 1,
"unique_sen": {
$first: "$data.unique_sen"
}
}
}
])
Update:
You don't need to escape in the query.
play
db.collection.aggregate([
{
"$match": {
"url": "https://www.rootsresource.in"
}
},
{
"$unwind": "$translations"
},
{
$project: {
"sp": {
$reduce: {
input: {
$split: [
"$translations.source",
"\n"
]
},
initialValue: [],
in: {
$concatArrays: [
"$$value",
{
$split: [
"$$this",
" "
]
}
]
}
}
}
}
},
{
"$group": {
"_id": null,
"elements": {
$addToSet: "$sp"
}
}
},
{
"$project": {
"unique_sen": {
"$size": "$elements"
},
"elements": 1
}
},
{
"$unwind": "$elements"
},
{
"$project": {
"sizes": {
"$size": "$elements"
},
"unique_sen": 1
}
},
{
"$group": {
"_id": null,
"unique_count": {
"$sum": "$sizes"
},
"data": {
$push: "$$ROOT"
}
}
},
{
"$project": {
"unique_count": 1,
"unique_sen": {
$first: "$data.unique_sen"
}
}
}
])
UPDATE:
Above query works from mongo 4.4 - $first is available in project from 4.4
For older versions.
db.test.aggregate([
{
"$match": {
url: "https://www.rootsresource.in"
}
},
{
"$unwind": "$translations"
},
{
$project: {
"sp": {
$reduce: {
input: {
$split: [
"$translations.source",
"\n"
]
},
initialValue: [],
in: {
$concatArrays: [
"$$value",
{
$split: [
"$$this",
" "
]
}
]
}
}
}
}
},
{
"$group": {
"_id": null,
"elements": {
$addToSet: "$sp"
}
}
},
{
"$project": {
"unique_sen": {
"$size": "$elements"
},
"elements": 1
}
},
{
"$unwind": "$elements"
},
{
"$project": {
"sizes": {
"$size": "$elements"
},
"unique_sen": 1
}
},
{
"$group": {
"_id": null,
"unique_count": {
"$sum": "$sizes"
},
"data": {
$push: "$$ROOT"
}
}
},
{
"$project": {
"unique_count": 1,
unique_sen: { $arrayElemAt: [ "$data.unique_sen", 0 ] }
}
}
])

MongoDB $lookup replace array of _id with array of objects without converting arrays to object and removing fields

I have an object like this:
{
"_id": {
"$oid": "5f0047f02fd3fc048aab9ee9"
},
"array": [
{
"_id": {
"$oid": "5f00dcc23e12b8721e4f3672"
},
"name": "NAME",
"sub_array": [
{
"sub_array2": [
{
"$oid": "5f00e367f7b8747beddc6d31"
},
{
"$oid": "5f00f26c1facd18c5158d1d3"
}
],
"_id": {
"$oid": "5f00de99a8802e767885e72b"
},
"week_day": 1
},
{
"sub_array2": [
{
"$oid": "5f00e367f7b8747beddc6d31"
}
],
"_id": {
"$oid": "5f00f2501facd18c5158d1d2"
},
"week_day": 3
}
]
},
{
"_id": {
"$oid": "5f00f2401facd18c5158d1d1"
},
"name": "NAME1",
"sub_array": []
}
]
}
I want to replace sub_array ids with objects from another collection but that results converting array and sub_array to objects and losing all of the data like week_day.
Lookup:
'$lookup': {
'from': 'sati',
'localField': 'array.sub_array.sub_array2',
'foreignField': '_id',
'as': 'array.sub_array.sub_array2'
}
Result:
{
"_id": {
"$oid": "5f0047f02fd3fc048aab9ee9"
},
"array": {
"sub_array": {
"sub_array2": [
{
"_id": {
"$oid": "5f00e367f7b8747beddc6d31"
},
"endTime": "2020-07-03T12:06:50+0000",
"startTime": "2020-07-03T12:05:50+0000",
"data1": {
"$oid": "5f005e63ab1cbf2374d5163f"
}
},
{
"_id": {
"$oid": "5f00e367f7b8747beddc6d31"
},
"endTime": "2020-07-03T12:06:50+0000",
"startTime": "2020-07-03T12:05:50+0000",
"data1": {
"$oid": "5f005e63ab1cbf2374d5163f"
}
},
{
"_id": {
"$oid": "5f00e367f7b8747beddc6d31"
},
"endTime": "2020-07-03T12:06:50+0000",
"startTime": "2020-07-03T12:05:50+0000",
"data1": {
"$oid": "5f005e63ab1cbf2374d5163f"
}
}
]
}
}
}
Is there a way to "replace" the individual ids without converting entire arrays to objects and removing other fields. I know mongoose can do that but I'm not permitted to use it. None of the other questions helped (example).
It will override entire object key:value with $lookup result. Instead, store the lookup result in the sati variable and add an extra stage like shown below.
$map allows use iterate over an array and transform each item.
db.collection.aggregate([
{
"$lookup": {
"from": "sati",
"localField": "array.sub_array.sub_array2",
"foreignField": "_id",
"as": "sati"
}
},
{
$project: {
array: {
$map: {
input: "$array",
as: "array",
in: {
_id: "$$array._id",
name: "$$array.name",
sub_array: {
$map: {
input: "$$array.sub_array",
as: "sub_array",
in: {
_id: "$$sub_array._id",
week_day: "$$sub_array.week_day",
sub_array2: {
$filter: {
input: "$sati",
as: "sati_item",
cond: {
$in: [
"$$sati_item._id",
"$$sub_array.sub_array2"
]
}
}
}
}
}
}
}
}
}
}
}
])
MongoPlayground | Altenative with $mergeObjects

MongoDB get results where date is equal to max date

Assume I have the following document:
[
{
"callId": "17dac51e-125e-499e-9064-f20bd3b1a9d8",
"caller": {
"firstName": "Test",
"lastName": "Testing",
"phoneNumber": "1231231234"
},
"routeHistory": [
{
"assignedUserId": "cfa0ffe9-c77d-4eec-87d7-4430f7772e81",
"routeDate": "2020-01-01T06:00:00.000Z",
"status": "routed"
},
{
"assignedUserId": "cfa0ffe9-c77d-4eec-87d7-4430f7772e81",
"routeDate": "2020-01-03T06:00:00.000Z",
"status": "ended"
}
]
}
]
I want to get results where routeHistory.routeDate is equal to the $max routeDate value in routeHistory. I would expect my results to look like the following:
[
{
"callId": "17dac51e-125e-499e-9064-f20bd3b1a9d8",
"caller": {
"firstName": "Test",
"lastName": "Testing",
"phoneNumber": "1231231234"
},
"routeHistory": [
{
"assignedUserId": "cfa0ffe9-c77d-4eec-87d7-4430f7772e81",
"routeDate": "2020-01-03T06:00:00.000Z",
"status": "ended"
}
]
}
]
Is there a clean way to do this in a single aggregate, so that additional $match criteria can be applied?
You can use $let to define temporary variable being $max date and the use $filter along with $arrayElemAt to get first matching element:
db.collection.aggregate([
{
$addFields: {
routeHistory: {
$let: {
vars: {
maxDate: { $max: "$routeHistory.routeDate" }
},
in: {
$arrayElemAt: [
{ $filter: { input: "$routeHistory", cond: { $eq: [ "$$maxDate", "$$this.routeDate" ] } } },
0
]
}
}
}
}
}
])
Mongo Playground
EDIT:
version without $let:
db.collection.aggregate([
{
$addFields: {
maxDate: {
$max: "$routeHistory.routeDate"
}
}
},
{
$addFields: {
routeHistory: {
$arrayElemAt: [
{
$filter: { input: "$routeHistory", cond: { $eq: [ "$$maxDate", "$$this.routeDate" ] } }
},
0
]
}
}
}
}
])

Resources