Adding a field in Document in MongoDb - mongodb

database = {
'__v': 78,
'_id': ObjectId('5de4218d6a2be815b9e215e1'),
'services': [
{
'_id': ObjectId('5de4218e6a2be815b9e2186d'),
'name':'shivam',
},
{
'_id': ObjectId('5de4218e6a2be815b9e2181e'),
'name': 'Shivi'.
}
]
}
catalogues.update_one({'services._id': ObjectId(id)},{'$set': {'age':
"30"}},False,True)
This is one of the documents in mongodb.collection. How can I add a field 'age' in the dictionaries placed in services(list/array) in pymongo and update it in the database.

You need to use the $ positional operator
catalogues.update_one({'services._id': ObjectId(x_id)}, {'$set': {'services.$.age': '30'}})
Full example:
from pymongo import MongoClient
from bson import ObjectId
import pprint
db = MongoClient()['mydatabase']
catalogues = db.catalogues
catalogues.insert_one({
'__v': 78,
'_id': ObjectId('5de4218d6a2be815b9e215e1'),
'services': [
{
'_id': ObjectId('5de4218e6a2be815b9e2186d'),
'name': 'shivam'
},
{
'_id': ObjectId('5de4218e6a2be815b9e2181e'),
'name': 'Shivi'
}
]
})
x_id = '5de4218e6a2be815b9e2181e'
catalogues.update_one({'services._id': ObjectId(x_id)}, {'$set': {'services.$.age': '30'}})
result:
{'__v': 78,
'_id': ObjectId('5de4218d6a2be815b9e215e1'),
'services': [{'_id': ObjectId('5de4218e6a2be815b9e2186d'), 'name': 'shivam'},
{'_id': ObjectId('5de4218e6a2be815b9e2181e'),
'age': '30',
'name': 'Shivi'}]}

Related

PyMongo: Removing a nested object without knowing the key

Let's say I have a collection that looks like this:
{
'_id': ObjectId('abc'),
'customer': 'bob',
'products': {
'1234':
{'name': 'Shirt',
'productID': 5
},
'5679': {
'name': 'Hat',
'productID': 5
}
}
'1011': {
'name': 'Jeans',
'productID': 9
}
}
}
I am looking to remove all nested objects whose 'productID' property is 5, so the collection would look like this afterwards:
{'_id': ObjectId('abc'),
'name': 'bob',
'products': {
'1011': {
'name': 'Jeans',
'productID': 9
}
}
}
I know the following information:
customer: bob
productID: 5
Is it possible to do a wildcard on 'products'? Something like this (it does not work):
db.update({'customer':'bob'}, {'$unset': {'products.*': {'productID': 9}})
If you have a choice, refactor your data to make each item a list element, e.g.
{
'customer': 'bob',
'products': [
{'code': '1234',
'name': 'Shirt',
'productID': 5
},
{'code': '5679',
'name': 'Hat',
'productID': 5
},
{'code': '1011',
'name': 'Jeans',
'productID': 9
}
]
}
Then your update becomes a piece of cake:
db.mycollection.update_one({'customer': 'bob'}, {'$pull': {'products': {'productID': 5}}})
result:
{
"customer": "bob",
"products": [
{
"code": "1011",
"name": "Jeans",
"productID": 9
}
]
}
Persisting with poor choices of schema will no yield long term rewards.

Aggregate across two collections and update documents in first collect in pymongo

I am using pymongo. I have a collection for which I want to update fields based on values from another collection.
Here's a document from the collection1.
{ _id: ObjectId("5fef7a23d0bdc785d4fc94e7"),
path: 'path1.png',
type: 'negative',
xmin: NaN,
ymin: NaN,
xmax: NaN,
ymax: NaN}
And from collection2:
{ _id: ObjectId("5fef7a24d0bdc785d4fc94e8"),
path: 'path1.png',
xmin: 200,
ymin: 200,
xmax: 300,
ymax: 300}
How do I update collection 1 so that the example document looks like:
{ _id: ObjectId("5fef7a23d0bdc785d4fc94e7"),
path: 'path1.png',
type: 'negative,
xmin: 200,
ymin: 200,
xmax: 300,
ymax: 300}
Fetch collection2 into a dict variable and use $set to update collection1, e.g.
for doc in db.collection2.find({}, {'_id': 0}):
db.collection1.update_one({'path': doc.get('path')}, {'$set': doc})
I have found a way to output it into a separate collection, but still not sure how to get it to the same collection.
db.collection1.aggregate[
{
'$match': {
'xmin': 'NaN'
}
}, {
'$lookup': {
'from': 'collection2',
'localField': 'path',
'foreignField': 'path',
'as': 'inferences'
}
}, {
'$project': {
'inferences.xmin': 1,
'inferences.ymin': 1,
'inferences.xmax': 1,
'inferences.ymax': 1,
'path': 1,
'type': 1,
'_id': 0
}
}, {
'$unwind': {
'path': '$inferences',
'preserveNullAndEmptyArrays': False
}
}, {
'$addFields': {
'xmin': '$inferences.xmin',
'ymin': '$inferences.ymin',
'xmax': '$inferences.xmax',
'ymax': '$inferences.ymax'
}
}, {
'$project': {
'path': 1,
'type': 1,
'xmin': 1,
'ymin': 1,
'xmax': 1,
'ymax': 1
}
}, {
'$out': 'collection3'
}
]

MongoDB: Is there a way to compute sum on past records?

Assume I have a dataset like :
yearMonth | amount
201908 | 100
201909 | 100
201910 | 200
201911 | 100
201912 | 200
202001 | 300
202002 | 200
Is there a way I can do a sum/accumulate on pass records to get a result set like :
yearMonth | amount | balance
201908 | 100 | 100
201909 | 100 | 200
201910 | 200 | 400
201911 | 100 | 500
201912 | 200 | 700
202001 | 300 | 1000
202002 | 200 | 1200
Try below aggregation query :
db.collection.aggregate([
/** Sort on entire collection is not preferred, but you need it if 'yearMonth' field is not ordered */
/** Group on empty & push all docs to 'data' array */
{ $group: { _id: "", data: { $push: "$$ROOT" } } },
{
$project: {
data: {
$let: {
vars: {
data: {
$reduce: {
input: "$data", /** Iterate over 'data' array & push newly formed docs to docs array */
initialValue: { amount: 0, docs: [] },
in: {
docs: {
$concatArrays: [
"$$value.docs",
[
{
_id: "$$this._id",
yearMonth: "$$this.yearMonth",
amount: "$$this.amount",
balance: {
$add: ["$$value.amount", "$$this.amount"],
},
},
],
],
},
amount: { $add: ["$$value.amount", "$$this.amount"] },
},
},
},
},
in: "$$data.docs", /** Return only 'docs' array & ignore 'amount' field */
},
},
},
},
/** unwind 'data' array(newly formed 'data' array field) */
{
$unwind: "$data",
},
/** Replace data object as new root for each document in collection */
{
$replaceRoot: {
newRoot: "$data",
},
},
]);
Test : MongoDB-Playground
Ref : aggregation-pipeline-operators
Using the mapReduce collection method with guidance from this answer you can get your desired results.
Here's a pymongo solution using the following options:
map function - this does the initial mapping of the key, value pair to be emitted (the yearMonth and Amount).
reduce function - didn't need any action for this case.
out - specifies where to put the output - could be a collection or as in this case just processed inline.
scope - specifies the rolling total field - just called total
finalize - this does the actual totaling.
Here's the python(pymongo) code:
from pymongo import MongoClient
from bson.code import Code
client = MongoClient()
db = client.tst1
coll = db.mapr1
map1 = Code('''
function () {
emit(
this.yearMonth,
this.amount
);
}
''')
reduce1 = Code('''
function (key, values) {
return value;
}
''')
fin1 = Code('''
function(key, value) {
total += value;
return {amount: value, balance: total};
}
''')
result = coll.map_reduce(map1, reduce1, out={'inline': 1}, scope={'total': 0}, finalize=fin1)
for doc in result['results']:
print(f'The doc is {doc}')
Results:
The doc is {'_id': 201908.0, 'value': {'amount': 100.0, 'balance': 100.0}}
The doc is {'_id': 201909.0, 'value': {'amount': 100.0, 'balance': 200.0}}
The doc is {'_id': 201910.0, 'value': {'amount': 200.0, 'balance': 400.0}}
The doc is {'_id': 201911.0, 'value': {'amount': 100.0, 'balance': 500.0}}
The doc is {'_id': 201912.0, 'value': {'amount': 200.0, 'balance': 700.0}}
The doc is {'_id': 202001.0, 'value': {'amount': 300.0, 'balance': 1000.0}}
The doc is {'_id': 202002.0, 'value': {'amount': 200.0, 'balance': 1200.0}}
Documents in collection:
{'_id': ObjectId('5e89c410b187b1e1abb089af'),
'amount': 100,
'yearMonth': 201908}
{'_id': ObjectId('5e89c410b187b1e1abb089b0'),
'amount': 100,
'yearMonth': 201909}
{'_id': ObjectId('5e89c410b187b1e1abb089b1'),
'amount': 200,
'yearMonth': 201910}
{'_id': ObjectId('5e89c410b187b1e1abb089b2'),
'amount': 100,
'yearMonth': 201911}
{'_id': ObjectId('5e89c410b187b1e1abb089b3'),
'amount': 200,
'yearMonth': 201912}
{'_id': ObjectId('5e89c410b187b1e1abb089b4'),
'amount': 300,
'yearMonth': 202001}
{'_id': ObjectId('5e89c410b187b1e1abb089b5'),
'amount': 200,
'yearMonth': 202002}

mongoose populate and sort nested item

I have a simple model:
user = {
'items': [{
'name': 'abc',
'pages': [ObjectId("58c703a353dbaf37586b885c"), ObjectId("58c703a353dbaf37586b885d"), ..]}
}]
};
I'm trying to sort the pages of current item:
User.findOne({'_id': id}, {'items': {$elemMatch: {'_id': id2}}})
.populate({path: 'items.pages', select: '_id', options: { sort: { _id: -1 } }})
.exec(function(err, user) {
});
But I'm getting an error: Error: Cannot populate withsorton path items.pages because it is a subproperty of a document array. What should I change?

MongoDB: multiple $elemMatch

I have MongoDB documents structured like this:
{_id: ObjectId("53d760721423030c7e14266f"),
fruit: 'apple',
vitamins: [
{
_id: 1,
name: 'B7',
usefulness: 'useful',
state: 'free',
}
{
_id: 2,
name: 'A1',
usefulness: 'useful',
state: 'free',
}
{
_id: 3,
name: 'A1',
usefulness: 'useful',
state: 'non_free',
}
]
}
{_id: ObjectId("53d760721423030c7e142670"),
fruit: 'grape',
vitamins: [
{
_id: 4,
name: 'B6',
usefulness: 'useful',
state: 'free',
}
{
_id: 5,
name: 'A1',
usefulness: 'useful',
state: 'non_free',
}
{
_id: 6,
name: 'Q5',
usefulness: 'non_useful',
state: 'non_free',
}
]
}
I want to query and get all the fruits which have both {name: 'A1', state: 'non_free'} and {name: 'B7', state: 'free'}.
In the worst case I want at least to count these entries if getting them is not possible and if the equivalent code exists for pymongo, to know how to write it.
For the given example I want to retrieve only the apple (first) document.
If I use $elemMatch it works only for one condition, but not for both. E.g. if I query find({'vitamins': {'$elemMatch': {'name': 'A1', 'state': 'non_free'}, '$elemMatch': {'name': 'B7', 'state': 'free'}}}) it will retrieve all the fruits with {'name': 'B7', 'state': 'free'}.
In this case you can use the $and-operator .
Try this query:
find({
$and: [
{'vitamins': {'$elemMatch': {'name': 'A1', 'state': 'non_free'} } },
{'vitamins': {'$elemMatch': {'name': 'B7', 'state': 'free'} } }
]
});
To explain why you received only the result matching the second criteria: The objects inside each {} you pass to MongoDB are key/value pairs. Each key can only exist once per object. When you try to assign a value to the same key twice, the second assignment will override the first. In your case you assigned two different values to the key $elemMatch in the same object, so the first one was ignored. The query which actually arrived in MongoDB was just find({'vitamins': {'$elemMatch': {'name': 'B7', 'state': 'free'}}}).
Whenever you need to apply the same operator to the same key twice, you need to use $or or $and.
var fruits = db.fruits.find({
"vitamins": {
$all: [{
$elemMatch: {
"name": "A1",
"state": "non_free"
}
}, {
$elemMatch: {
"name": "B7",
"state": "free"
}
}]
}
})
let query = [];
query.push({
id: product.id,
});
query.push({ date });
for (const slot of slots) {
query.push({
slots: {
$elemMatch: {
id: slot.id,
spots: { $gte: slot.spots },
},
},
});
}
const cal = await Product.findOne({ $and: query });