MongoDB how do I make an array with custom keys containing subdocument - python

Alright so using Python and MongoDB I am trying to embed a subdocument within an array with a custom key value in the array. I was playing around with all sorts of different ways to do this and I couldn't figure out what I was doing wrong so I temporarily settled on the working code below. Numerous attempts always lead to the error:
in _check_write_command_response
raise OperationFailure(error.get("errmsg"), error.get("code"), error) pymongo.errors.OperationFailure: The dotted field 'first.rule'
in 'followedBy..first.rule' is not valid for storage.
Code:
citizens.update(
{"_id" : userPush},
{"$push": {"followedBy":[field[1], field[2], field[3], field[0]]}})
Produces:
"_id" : ObjectId("5…asfd"),
"uName" : "tim0",
"fName" : "tim",
"lName" : "lost",
"pic" : null,
"bio" : "I <3 MongoDB",
"followedBy" : [
[
"BobTheBomb",
"bobby",
"knight",
NumberInt(2)
],
[
"Robert",
"DROP",
"TABLE",
NumberInt(6)
]
This is what I want:
"_id" : ObjectId("5…asfd"),
"uName" : "tim0",
"fName" : "tim",
"lName" : "lost",
"pic" : null,
"bio" : "I <3 MongoDB",
"followedBy" : [
"BobTheBomb": {
"fName" : "bobby",
"lName" : "knight",
"uID" : NumberInt(2)
},
"Robert": {
"fName" : " DROP ",
"lName" : " TABLE ",
"uID" : NumberInt(6)
}
]

You will need to build that data structure, currently you are saying that "followedBy" is only a list.
so try:
citizens.update(
{"_id" : userPush},
{"$push": {"followedBy":{field[1]: { "fName":field[2], "lName":field[3], "uID":field[0]}}}})
Remove the list and replace it with a dict.
I do hope this helps.
I have realised that I have not given you valid json, I have tested this:
citizens.update(
{"_id" : userPush},
{$push:
{"followedBy":
[
{field[1]:
{ "fName": field[2], "lName": field[3], "uID": field[0]}
}
]
}
})
And it worked...
You might find that the error is caused by the modifier you are using, I found the following on a blog:
MongoDB provides several different modifiers you can use to update documents in place, including the following (for more details see updates):
- $inc Increment a numeric field (generalized; can increment by any number)
- $set Set certain fields to new values
- $unset Remove a field from the document
- $push Append a value onto an array in the document
- $pushAll Append several values onto an array
- $addToSet Add a value to an array if and only if it does not already exist
- $pop Remove the last (or first) value of an array
- $pull Remove all occurrences of a value from an array
- $pullAll Remove all occurrences of any of a set of values from an array
- $rename Rename a field
- $bit Bitwise updates
you might find that because you are inserting many items that you would rather want to use $pushAll or $addToSet rather than $push... Just a speculation...

Related

Mongodb Python: How to use $push overwrites existing value rather than adds to the end of an array within a document

I have a document like this
{ "_id" : 23, "local_id" : 1234, "global_id" : [ "P123", "P345" ] }
If I want to $push new value to the array has the key “global_id” then I can do this
collection.update_one({‘local_id’: l_pid}, {’$push’: {‘global_id’: "P678"}})
and the document looks sth like this , for example : (push P678 to the array)
{ “_id” : 23, “local_id” : 1234, “global_id” : [ “P123”, “P345”,
“P678” ] }
But next time when the same key of “global_id” comes in it keeps appending to the end of array like : (this time the same P678 comes in)
{ “_id” : 23, “local_id” : 1234, “global_id” : [ “P123”, “P345”,
“P678” , “P678”] }
I want it to overwrite to existing value, and the array has to have unique value, the value can’t be the same.
How can I do it?
Thanks
Base on #rickhg12hs answer, use $addToSet solve my issue
collection.update_one({‘local_id’: l_pid}, {’$addToSet’: {‘global_id’: "P678"}})

How to use PyMongo find() to search nested array attribute?

Using PyMongo, how would one find/search for the documents where the nested array json object matches a given string.
Given the following 2 Product JSON documents in a MongoDB collection..
[{
"_id" : ObjectId("5be1a1b2aa21bb3ceac339b0"),
"id" : "1",
"prod_attr" : [
{
"name" : "Branded X 1 Sneaker"
},
{
"hierarchy" : {
"dept" : "10",
"class" : "101",
"subclass" : "1011"
}
}
]
},
{
"_id" : ObjectId("7be1a1b2aa21bb3ceac339xx"),
"id" : "2",
"prod_attr" : [
{
"name" : "Branded Y 2 Sneaker"
},
{
"hierarchy" : {
"dept" : "10",
"class" : "101",
"subclass" : "2022"
}
}
]
}
]
I would like to
1. return all documents where prod_att.hierarchy.subclass = "2022"
2. return all documents where prod_attr.name contains "Sneaker"
I appreciate the JSON could be structured differently, unfortunately that is not within my control to change.
1. Return all documents where prod_attr.hierarchy.subclass = "2022"
Based on the Query an Array of Embedded Documents documentation of MongoDB you can use dot notation concatenating the name of the array field (prod_attr), with a dot (.) and the name of the field in the nested document (hierarchy.subclass):
collection.find({"prod_attr.hierarchy.subclass": "2022"})
2. Return all documents where prod_attr.name contains "Sneaker"
As before, you can use the dot notation to query a field of a nested element inside an array.
To perform the "contains" query you have to use the $regex operator:
collection.find({"prod_attr.name": {"$regex": "Sneaker"}})
Another option is to use the MongoDB Aggregation framework:
collection.aggregate([
{"$unwind": "$prod_attr"},
{"$match": {"prod_attr.hierarchy.subclass": "2022"}}
])
the $unwind operator creates a new object for each object inside the prod_attr array, so you will have only nested documents and no array (check the documentation for details).
The next step is the $match operator that actually perform a query on the nested object.
This is a simple example but playing with the Aggregators Operators you have a lot of flexibility.

trying to update my MongoDB collection via Python (PyMongo) but my logic is wrong

I'm new to programming and I've been learning Flask for about a week now, trying to design an e-commerce website.
I have a collection called users, in which each entry looks like this
{
"_id" : ObjectId("5b9170f1c5eb754e3ab8917f"),
"username" : "suhas",
"password" : "suhas",
"email" : "suhas",
"account_type" : "buyer",
"cart" : [
{
"product_id" : "5b915dd3c5eb754278e160e7",
"quantity" : 7
},
{
"product_id" : "5b915e3fc5eb754278e160e8",
"quantity" : 3
}
]
}
and a users.products collection, one of the elements look like this
{
"_id" : ObjectId("5b915f02c5eb7544108a14b9"),
"product name" : "Laptop",
"price" : 50000,
"description" : "HP laptop",
"user_id" : "5b914fc3c5eb753eaf81770c",
"username" : "chiranth"
}
I wanted to add a feaure "add to cart" that adds the product_id and quantity into a cart(type list) as dictionaries, as seen in the db.users collection.
The code in Python I've written as:
cart_dict = user_info.get("cart")
for dict1 in cart_dict:
if dict1["product_id"]==product_id:
db["users"].update({"_id" : ObjectId(user_id),"cart.product_id":product_id},{ '$inc':{ 'cart.$.quantity':quantity}})
break
else:
db["users"].update({"_id":ObjectId(user_id)},{"$addToSet":{"cart":{"$each":[{"product_id":product_id,"quantity":quantity}]}}})
The problem is, however, that if I increment the second product by adding 2 more to the cart adding to the present 3, the if condition checks the first product, sees that product ID doesn't match and just adds it as a new product AND THEN moves to product 2.
So my question would be: how do I ask the if condition to check all product_id's first AND THEN, if none match, add the product?
EDIT : to clarify,
my issue is with the IF condition. I want it to first compare product A, if that isnt satisfied, move to product B, and IF neither match, THEN it should go to the else loop. How do i do that?
Hey the code you have provided, their is unnecessary second for loop on same data just above the if condition.
you should remove that line. As pr updating data the code i used and works is as :
collection.update({'_id':int(n)},{'$set' : {'CloseDate':TodayDate,'Status':'close'}})
in this the where is provided in _id and the fields CloseDate & Status are being updated, this works, so you should put your new values that are to be updated in DB in some variables and directly assign them in your documents name.
Hope this helps.

Reach to array subdocument in mongodb

How to reach to the last element of scores array with score value: 64.8 ???
I try to use $pull operator,but I have 200 documents like this form. So, I can't make use of exact value of latest element of scores array.
{
"_id" : 198,
"name" : "Timothy Harrod",
"scores" : [
{
"type" : "exam",
"score" : 11.9075674046519
},
{
"type" : "quiz",
"score" : 20.51879961777022
},
{
"type" : "homework",
"score" : 55.85952928204192
},
{
"type" : "homework",
"score" : 64.85650354990375
} ]
}
Easiest way would be to put that document into a python dict if it's in string format:
import json
doc_json = json.loads(doc_string)
Then to access the LAST element of the scores array, just do:
last_element = doc_json['scores'][-1]
The ['scores'] tells us to get the 'scores' array from our json_doc, and the [-1] tells us to access the last element of this 'scores' array. From there, if you wanted the score from the last_element, you would just do:
last_score = last_element['score']
You could have also gotten it straight by doing:
last_score = doc_json['scores'][-1]['score']

Retrieve a single property from document

Good day everyone.
Suppose we have a collection and a document which looks something like this:
test_doc = {
"ID" : "123",
"a" : [
{
'x' : "/",
'y' : "2000",
'z' : "1000"
},
{
'x' : "/var",
'y' : "3500",
'z' : "3000"
}
]
}
What i need is to retrieve a single property a.z .
In MongoDB i'm using the following query:
db.testcol.find({"ID":"123","a.x":"/"},{'a.z':1})
which returns this:
{ "_id" : ObjectId("skipped"), "a" : [ { "z" : "1000" }, { "z" : "3000" } ] }
As you can see it returns all the z properties, but i need only the first one or the second when condition is {"ID":"123","a.x":"/var"}
So, the question is: how do i get a single property in this situation? Is it just a matter of bad design or should i somehow process the returned document in code (python)? Any suggestions will be much appreciated.
In MongoDB 2.0 and older, this is not possible. What you want to do is return a specific element of the array - but that is not what your projection is actually doing, it will just return the whole array and then the z element of each one.
However, with 2.2 (rc2 as of writing this answer), things have gotten a bit better. You can now use $elemMatch as part of your projection (see SERVER-2238 for details) so that you only pull back the required array element. So, try something like this:
db.foo.find({"ID":"123",'a':{$elemMatch:{'x':"/"}}},{_id : 0, 'a.$': 1})
//returns
{ "a" : [ { "x" : "/", "y" : "2000", "z" : "1000" } ] }
Or, just use $elemMatch in the projection itself, which you may think is cleaner:
db.foo.find({"ID":"123"},{_id : 0, 'a':{$elemMatch:{'x':"/"}}})
//returns
{ "a" : [ { "x" : "/", "y" : "2000", "z" : "1000" } ] }
So, now, at least the array returned is only the one containing only the entries you want and you can simply reference the relevant z element (elemMatch projections on a subdocument are not yet supported).
Last but not least, in 2.2 we have the aggregation framework, and one of the things it can do (with the $project operator, is to reshape your documents and change sub documents and array elements into top level arrays. To get your desired result, you would do something like this:
db.foo.aggregate(
{$match : {"ID":"123"}},
{$unwind : "$a"},
{$match : {"a.x":"/"}},
{$project : {_id : 0, z : "$a.z"}}
)
The result looks like this:
{ "result" : [ { "z" : "1000" } ], "ok" : 1 }

Categories