I have a document like this
{
"_id": {
"$oid": "5c7369826023661073802f63"
},
"participants": [
{
"id": "ABC",
"nickname": "USER1",
},
{
"id": "DEF",
"nickname": "USER2",
}
]},... etc, et
I want to find the record that has the two ids that you provide
I try with this.
moodel.aggregate([
{
$match:{'participants.id':idOne}
},{
$project:{
list:{
$filter:{
input:'$list',
as:'item',
cond: {$eq: ['$$item.participants.id', idTwo]}
}
},
}
}
])
but the result is:
[ { _id: 5c7369826023661073802f63, list: null }]
I want it to return only the record that match the two ids.
use $elematch and $in
https://docs.mongodb.com/manual/reference/operator/query/elemMatch/
https://docs.mongodb.com/manual/reference/operator/query/in/
db.moodel.find({"participants": {$elemMatch: {id: {$in: [idOne, idTwo]}}}})
Related
How can I push value into multiple nested array with specific conditions? I have a document like this
[
{
"_id": "class_a",
"students": [
{
"_id": "1a",
"name": "John",
"grades": []
},
{
"_id": "1b",
"name": "Katie",
"grades": []
},
{
"_id": "1c",
"name": "Andy",
"grades": []
},
]
}
]
Query to insert into nested array. (Not sure what is missing here)
db.collection.update({
"_id": "class_a",
"students": {
$elemMatch: {
"_id": {
"$in": [
"1a",
"1b"
]
}
}
}
},
{
$push: {
"students.$.grades": "A+"
}
})
Got the following result. But I was expecting both John and Katie have A+ in grades
[
{
"_id": "class_a",
"students": [
{
"_id": "1a",
"grades": ["A+"],
"name": "John"
},
{
"_id": "1b",
"grades": [],
"name": "Katie"
},
{
"_id": "1c",
"grades": [],
"name": "Andy"
}
]
}
]
Expected result
[
{
"_id": "class_a",
"students": [
{
"_id": "1a",
"grades": ["A+"],
"name": "John"
},
{
"_id": "1b",
"grades": ["A+"],
"name": "Katie"
},
{
"_id": "1c",
"grades": [],
"name": "Andy"
}
]
}
]
Mongo playground to test the code
You can use $[<identifier>] to update only the items that match a condition. Your first {} is to find the relevant documents, while the arrayFilters is to find the relevant items inside the document nested array:
db.collection.update(
{_id: "class_a", students: {$elemMatch: {_id: {$in: ["1a", "1b"]}}}},
{$push: {"students.$[item].grades": "A+"}},
{arrayFilters: [{"item._id": {$in: ["1a", "1b"]}}], upsert: true}
)
See how it works on the playground example
You should really use arrayFilters for these otherwse it'll only match the first entity. You don't need to use $elemMatch at all.
Playground - https://mongoplayground.net/p/_7y89KB83Ho
db.collection.update({
"_id": "class_a"
},
{
$push: {
"students.$[students].grades": "A+"
}
},
{
"arrayFilters": [
{
"students._id": {
"$in": [
"1a",
"1b"
]
}
}
]
})
In MongoDB, I'm trying to filter a collection down to only those documents that contain the most recent date by their respective group.
In traditional SQL I'd do something like:
Select *
From table a
Join (Select my_group, max(date) as max_date
From table group by my_group) b
ON a.my_group = b.my_group AND
a.date = b.max_date
With the following sample collection:
[
{
"_id": "123",
"item1": "group 1",
"item2": "abc",
"item3": "abc",
"date": "2022-01-01"
},
{
"_id": "234",
"item1": "group 1",
"item2": "abc",
"item3": "abc",
"date": "2022-01-02"
},
{
"_id": "345",
"item1": "group 1",
"item2": "abc",
"item3": "abc",
"date": "2022-01-02"
},
{
"_id": "789",
"item1": "group 2",
"item2": "abc",
"item3": "abc",
"date": "2022-01-01"
},
{
"_id": "678",
"item1": "group 2",
"item2": "abc",
"item3": "abc",
"date": "2022-01-02"
},
{
"_id": "456",
"item1": "group 2",
"item2": "abc",
"item3": "abc",
"date": "2022-01-02"
}
]
The expected output is:
[
{
"_id": "234",
"date": "2022-01-02",
"item1": "group 1",
"item2": "abc",
"item3": "abc"
},
{
"_id": "345",
"date": "2022-01-02",
"item1": "group 1",
"item2": "abc",
"item3": "abc"
},
{
"_id": "678",
"date": "2022-01-02",
"item1": "group 2",
"item2": "abc",
"item3": "abc"
},
{
"_id": "456",
"date": "2022-01-02",
"item1": "group 2",
"item2": "abc",
"item3": "abc"
}
]
My current best attempt is:
db.collection.aggregate([
{
$group: {
"_id": "$item1",
"max_date": {
$max: "$date"
},
"records": {
$push: "$$ROOT"
}
}
},
{
"$project": {
items: {
"$filter": {
"input": "$records",
"as": "records",
"cond": {
$eq: [
"$$records.date",
"$max_date"
]
}
}
}
}
},
{
$replaceRoot: {
newRoot: {
results: "$items"
}
}
}
])
Unfortunately, this returns the results partitioned by group. I've tried a few alternatives suggested by other posts & get a similar problem, eg:
How to group and select document corresponding to max within each group in MongoDB?
MongoDB get rows where max value grouped
Get all rows, groupped and with max value
Here's a playground example with the query & sample data.
You're close to the answer.
For the last 2 stages:
$unwind - Deconstruct the items array field to multiple documents.
$replaceWith - Replace the output document with items document.
db.collection.aggregate([
{
$group: {
"_id": "$item1",
"max_date": {
$max: "$date"
},
"records": {
$push: "$$ROOT"
}
}
},
{
"$project": {
items: {
"$filter": {
"input": "$records",
"as": "records",
"cond": {
$eq: [
"$$records.date",
"$max_date"
]
}
}
}
}
},
{
$unwind: "$items"
},
{
$replaceWith: "$items"
}
])
Sample Mongo Playground
Bonus
Although the query above is better, also would like to share the MongoDB query that is similar to SQL implementation.
$group - Group by item1 and get the max value of date.
$lookup - Self join the collection with item1 and date. And returns items array field.
$match - Filter the document with items not an empty array.
$unwind - Deconstruct the items array into multiple documents.
$replaceWith - Replace the output document with items document.
db.collection.aggregate([
{
$group: {
"_id": "$item1",
"max_date": {
$max: "$date"
}
}
},
{
$lookup: {
from: "collection",
let: {
item1: "$_id",
max_date: "$max_date"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [
"$item1",
"$$item1"
]
},
{
$eq: [
"$date",
"$$max_date"
]
}
]
}
}
}
],
as: "items"
}
},
{
$match: {
items: {
$ne: []
}
}
},
{
$unwind: "$items"
},
{
$replaceWith: "$items"
}
])
Sample Mongo Playground (Bonus)
{
"orderNo": "123",
"bags": [{
"type": "small",
"products": [{
"id": "1",
"name": "ABC",
"returnable": true
}, {
"id": "2",
"name": "XYZ"
}
]
},{
"type": "big",
"products": [{
"id": "3",
"name": "PQR",
"returnable": true
}, {
"id": "4",
"name": "UVW"
}
]
}
]
}
I have orders collection where documents are in this format. I want to get a total count of products which has the returnable flag. e.g: for the above order the count should be 2. I am very new to MongoDB wanted to know how to write a query to find this out, I have tried few things but did not help:
this is what I tried but not worked:
db.orders.aggregate([
{ "$unwind": "$bags" },
{ "$unwind": "$bags.products" },
{ "$unwind": "$bags.products.returnable" },
{ "$group": {
"_id": "$bags.products.returnable",
"count": { "$sum": 1 }
}}
])
For inner array you can use $filter to check returnable flag and $size to get number of such items. For the outer one you can take advantage of $reduce to sum the values from inner arrays:
db.collection.aggregate([
{
$project: {
totalReturnable: {
$reduce: {
input: "$bags",
initialValue: 0,
in: {
$add: [
"$$value",
{
$size: {
$filter: {
input: "$$this.products",
as: "prod",
cond: {
$eq: [ "$$prod.returnable", true ]
}
}
}
]
}
}
}
}
}
}
])
Mongo Playground
I have a mongo Database I'll like to "join" two of them and then merge some other fields:
Let's see the schemas:
Students Schema (and data):
{
"_id": ObjectId("5fbd564981b1313de790b580"),
"name": "John Doe",
"age": "21",
"image": "https://XXXX/481.png",
"subjects": [
{
"_id": ObjectId("5fbd4e6881b1313de790b56b"),
"passed": true,
},
{
"_id": ObjectId("5fcb63fa8814d96876c687bf"),
}
],
"__v": NumberInt("1"),
}
and Subject schema:
{
"_id": ObjectId("5fbd4e6881b1313de790b56b"),
"course": 3,
"teacher": "John Smith",
"name": "Math",
},
{
"_id": ObjectId("5fcb63fa8814d96876c687bf"),
"name": "IT",
"course": 8,
"teacher": "John Peter",
}
What I'll like to make a query with the subjects (all info) of a student, also if the student have additional fields in subject like passed add it to the subject subdocument.
Here is my query till now:
db.students.aggregate([
{
$match:
{
_id : ObjectId('5fbd564981b1313de790b580')
}
},
{
$lookup :
{
from : "subjects",
localField : "subjects._id",
foreignField : "_id",
as : "FoundSubject"
}
}
]);
which correctly make the "join" but the merge is still missing, I got as result:
{
"_id": ObjectId("5fbd564981b1313de790b580"),
"name": "John Doe",
"age": "21",
"image": "https://XXXX/481.png",
"subjects": [
{
"_id": ObjectId("5fbd4e6881b1313de790b56b"),
"passed": true,
},
{
"_id": ObjectId("5fcb63fa8814d96876c687bf"),
}
],
"__v": NumberInt("1"),
"FoundSubject": [
{
"_id": ObjectId("5fbd4e6881b1313de790b56b"),
"course": 3,
"teacher": "John Smith",
"name": "Math"
},
{
"_id": ObjectId("5fcb63fa8814d96876c687bf"),
"name": "IT",
"course": 8,
"teacher": "John Peter"
}
]
}
but I'll like to have:
{
"_id": ObjectId("5fbd564981b1313de790b580"),
"name": "John Doe",
"age": "21",
"image": "https://XXXX/481.png",
"subjects": [
{
"_id": ObjectId("5fbd4e6881b1313de790b56b"),
"course": 3,
"teacher": "John Smith",
"name": "Math",
"passed": true,
},
{
"_id": ObjectId("5fcb63fa8814d96876c687bf"),
"name": "IT",
"course": 8,
"teacher": "John Peter"
}
],
"__v": NumberInt("1"),
}
with merged data and field "passed" added. How can accomplish that?
I'm new to MongoDB coming from MySQL.
Thanks
You need to merge both objects, add below stage after $lookup,
MongoDB Version From 3.4
$map to iterate loop of students array
$reduce to iterate loop of FoundSubject array, check condition if condition match then return required fields otherwise return initial value
$project to remove FoundSubject from result
{
$addFields: {
subjects: {
$map: {
input: "$subjects",
as: "s",
in: {
$reduce: {
input: "$FoundSubject",
initialValue: {},
in: {
$cond: [
{ $eq: ["$$s._id", "$$this._id"] },
{
_id: "$$this._id",
course: "$$this.course",
name: "$$this.name",
teacher: "$$this.teacher",
passed: "$$s.passed"
},
"$$value"
]
}
}
}
}
}
}
},
{ $project: { FoundSubject: 0 } }
Playground
MongoDB Version From 4.4
$map to iterate loop of students array,
$filter to get matching document from FoundSubject array and $first to get first object from array returned by filter
$mergeObjects to merge current objects with found result object from filter
remove FoundSubject using $$REMOVE
// skipping your stages
{
$addFields: {
FoundSubject: "$$REMOVE",
subjects: {
$map: {
input: "$subjects",
as: "s",
in: {
$mergeObjects: [
"$$s",
{
$first: {
$filter: {
input: "$FoundSubject",
cond: { $eq: ["$$s._id", "$$this._id"] }
}
}
}
]
}
}
}
}
}
Playground
I need to query mongoDB (mongoose) in very specific way. Here is the challenge:
My schema:
const eventSchema = new Schema(
{ name: { type: 'String' },
sessions: {
type: [
{
id: { type: 'Number' },
name: { type: 'String' }
}
]
}
});
Actual data
{"_id": "234", "name": "ng-nl", "sessions":[
{"id":"1", "name": "Testing Angular 4 Workshop"},
{"id":"2", "name": "Angular 4 and Firebase"}
]}
{"_id": "896", "name": "ng-conf 2037", "sessions":[
{"id":"1", "name": "How Elm Powers React"},
{"id":"2", "name": "Angular and React together"}
]}
The challenge:
When searching for string "angular"
I would like to get following data (the structure may be different):
{"_id": "234", session: {"id":"1", "name": "Testing Angular 4 Workshop"}}
{"_id": "234", session: {"id":"2", "name": "Angular 4 and Firebase"}}
{"_id": "896", session: {"id":"2", "name": "Angular and React together"}}
I've tried
{sessions.name: {$regex: 'angular', $options: 'i'}}
works correctly but returns full objects, I need only relevant sessions.
I've thought to do something with
db.test.aggregate([
{
$project: {
sessions: {
$filter: {
input: "$sessions",
as: "session",
cond: { $match: [{"$$session.name": {$regex: 'albert', $options: 'i'}}] }
}
}
}
}
])
But I have an error "MongoError: Unrecognized expression '$match'".
Any suggestions?
You can use $unwind and $match to get the expected result like this:
db.test.aggregate([
{
"$unwind": "$sessions"
},
{
$match: {
"sessions.name": {
"$regex": "angular",
"$options": "i"
}
}
}
])
Output
[
{ "_id": "234","name": "ng-nl","sessions": {"id": "1","name": "Testing Angular 4 Workshop"}},
{"_id": "234", "name": "ng-nl","sessions": {"id": "2","name": "Angular 4 and Firebase"}},
{ "_id": "896","name": "ng-conf 2037","sessions": {"id": "2","name": "Angular and React together"}}
]
In your expected output above, you have excluded outer name field. If you don't want to show the all fields in the result you can use $project like this:
db.test.aggregate([
{
"$unwind": "$sessions"
},
{
$match: {
"sessions.name": {
"$regex": "angular",
"$options": "i"
}
}
},
{
$project: {
_id: "$_id",
"sessions": 1
}
}
])
Output
[
{"_id": "234","sessions": {"id": "1","name": "Testing Angular 4 Workshop"}},
{"_id": "234","sessions": {"id": "2","name": "Angular 4 and Firebase"}},
{"_id": "896","sessions": {"id": "2","name": "Angular and React together"}}
]
Try $unwind like this
db.test.aggregate([
{
$project: {
sessions: {
$filter: {
input: "$sessions",
as: "session",
cond: { $match: [{"$$session.name": {$regex: 'albert', $options: 'i'}}] }
}
}
}
},
{
$unwind:"$sessions"
}
])