MongoDB - filter by several array elements - mongodb

Suppose I have documents like this:
{
"_id", "1",
"myarray": [
{
"address": "abc",
"role": "input",
}
{
"address": "def",
"role": "output",
}
]
},
{
"_id", "2",
"myarray": [
{
"address": "xyz",
"role": "input",
}
{
"address": "abc",
"role": "output",
}
]
}
I want to return documents where myarray.address is abc and myarray.role is output, but not the documents where exists myarray.address='abc' and exists myarray.role='output', but the documents where exists element of myarray array:
address: "abc",
role: "output"
Using the example above, I want only the document with _id=2.

You can use $elemMatch query operator to match multiple fields inside an array
db.collection.find({ myArray: { $elemMatch: { address: 'abc', role: 'output' }}})

Related

Delete objects that met a condition inside an array in mongodb

My collection has array "name" with objects inside. I need to remove only those objects inside array where "name.x" is blank.
"name": [
{
"name.x": [
{
"_id": "607e7fcca57aa56e2a06b57b",
"name": "abc",
"type": "123"
}
],
"_id": {
"$oid": "62232cd70ce38c5007de31e6"
},
"qty": "1.0",
"Unit": "pound,lbs"
},
{
"name.x": [
{
"_id": "607e7fcca57aa56e2a06b430",
"name": "xyz",
"type": "123"
}
],
"_id": {
"$oid": "62232cd70ce38c5007de31e7"
},
"qty": "1.0",
"Unit": "pound,lbs"
},{
"name.x": []
,
"_id": {
"$oid": "62232cd70ce38c5007de31e7"
},
"qty": "1.0",
"Unit": "pound,lbs"
}
I tried to get all the ids where name.x is blank using python and used $pull to remove objects base on those ids.But the complete array got deleted.How can I remove the objects that meet the condition.
Think MongoDB update with aggregation pipeline meets your requirement especially to deal with the field name with ..
$set - Update the name array field by $filter name.x field is not an empty array.
db.collection.update({},
[
{
$set: {
name: {
$filter: {
input: "$name",
cond: {
$ne: [
{
$getField: {
field: "name.x",
input: "$$this"
}
},
[]
]
}
}
}
}
}
],
{
multi: true
})
Sample Mongo Playground

MongoDB aggregate merging fields

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

Upsert update of large amount of documents in mongoDB

I have mongoDB collection items with following document structure:
{ name: string, values: string[] }
Then I have large amount of documents outside of database, which I want add to the db.
If document with same name already exists in database, push its value to values of db item,
If document with same name doesn't exist, create new document.
For example, let's have these records in the database:
[
{ "name": "A", "values": ["Alaska"] },
{ "name": "B", "values": [] }
]
Now add these records:
[
{ "name": "A", "value": "Australia" },
{ "name": "C", "value": "Canada" }
]
Result should be:
[
{ "name": "A", "values": ["Alaska", "Australia"] },
{ "name": "B", "values": [] },
{ "name": "C", "values": ["Canada"] }
]
However the number of documents can be hundreds of thousands. Is there any better way to upsert array of records than one by one?
db.items.update({ "name": "A" }, { "$set": { "name": "A" }, "$push": { "values": "Australia" } }, { "upsert": true })
db.items.update({ "name": "C" }, { "$set": { "name": "C" }, "$push": { "values": "Canada" } }, { "upsert": true })
...
You can use bulk writes to aggregate multiple updates into a single network request, but if the changes are individually determined per document and cannot be expressed as a query applying to multiple documents, each change must be its own insert/update command.

How to make query that find inside of find in mongodb

I have a document as below in Mongodb.
{
"vatInfo":{
"company": "apple"
},
"type": "manager",
"parent": "123"
}
And I have another document in same collection as below.
{
"type": "member",
"parentId": "123",
"id": "3"
}
And when I make client.find({id: 3, type: 'member'}), I want to get this that finds vatInfo automatically inside of find.
{
"type": "member",
"parentId": "123",
"id": "3",
"vatInfo":{
"company": "apple"
},
}
How should I make aggregate for this find? I dont want to make double find.
Thank you so much for reading it.
You can use the aggregation pipelines for that.
$match - find the parent documen.
$lookup - join the document from the other collection.
$project - modify the structure of the result.
// collection `client`
{
"vatInfo":{ "company": "apple" },
"type": "manager",
"parent": "123"
},
{
"type": "member",
"parentId": "123",
"id": "3"
}
// query example
db.getCollection('client').aggregate([
{ $match: { 'id': '3', 'type': 'member' } },
{
$lookup: {
from: 'client',
localField: 'parentId',
foreignField: 'parent',
as: 't01'
}
},
{
$project: {
'_id': 0,
'type': 1,
'parentId': 1,
'id': 1,
'vatInfo': { $arrayElemAt: [ "$t01.vatInfo", 0 ] }
}
}
])
// result
{
"type" : "member",
"parentId" : "123",
"id" : "3",
"vatInfo" : {
"company" : "apple"
}
}
To add the field of another document, you can use populate in Mongoose :
For this to work you need to have a reference in your schema between the client document and the parent document :
const clientSchema = Schema({
[...]
parentId: { type: Schema.Types.ObjectId, ref: 'ParentCollection' }
});
Then, you can use populate anywhere in your code :
client.findOne({id: 3, type: 'member'}).populate('parentId');

Add conditional and in array of object, mongodb

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]}}}})