mongodb: remove entire child element with multiple conditions - mongodb

Today is my first day using MongoDB
My collection format:
{ "_id" : "ObjectId" :"4f2ff1d00cf2f86576f91a91"),
"fishStuff" : [
{
"name" : "GreatWhite",
"fID" : 50
},
{
"name" : "Hammerhead",
"fID" : 51
},
{
"name" : "White",
"fID" : 60
}
], "fishSpecies" : "Oceanic"
...
I want to write a query in the shell (straight up mongo) that will delete the third child (name: White) because the 1st child exists in this group of "fishStuff".
I believe the "where" clause should be :
{fishSpecies:"Oceanic","fishStuff.fID":50, "fishStuff.fID:60}
So how do I delete the entire "60" child element? I want to delete every "60" element in the collection when "50" is also in the same "fishStuff" group/array. Also, the reason I've included fishSpecies in the "where" clause is because there is more than one fishSpecies possibility.
UPDATE:
I have tried the suggestions left by the two commenters below and am still getting 7 instances (after running the count below) of child object sets that contain both fID 50 and 60:
> db.fish.update({fishSpecies:"oceanic","fishStuff.fID":50}, {$pull : {"fishStuff" : { fID : 60}}},false, true)
> db.fish.find({"fishSpecies":"oceanic","fishStuff.fID":50, "fishStuff.fID":60}).count()
7
The multi: true flag does not work (or I am not implementing it correctly) as zero records are updated.

I believe this is what you're looking for...
Checks mycollection for documents that match fishSpecies with Oceanic AND fID = 50- removes fID = 60 element if so. multi:true scans the entire collection.
db.mycollection.update({'fishSpecies': 'Oceanic','fishStuff':{fID:50}}, {$pull:{'fishStuff': {fID: 60}}}, {multi:true})

If you're trying to remove based on it's ordinal position (index) then you can't directly.
You can use a technique to $unset the item in the array then $pull it.
db.mycollection.update({}, {$unset : {"fishStuff.50" : 1 }})
db.mycollection.update({}, {$pull : {"fishStuff" : null}})
It's worth noting though, this doesn't take into account the fId, and solely relies on the document's position in the array.
To pull the item matching your fId you need to:
db.mycollection.update({}, {$pull : {"fishStuff" : { fId : 50}}})
EDIT:
If you're wanting to remove the elements where fId == 50 from the array where the document's array contains fId == 50 and fId == 60 you should be able to:
db.mycollection.update({
"fishSpecies":"oceanic",
"fishStuff.fId": { $all : [50,60]}},
// update
{$pull : {"fishStuff" : { fId : 50}}
})

Related

how can i form a mongo best match query for below case to achieve performance

for a collection as below
Document 1
{
"entity" : "university",
"parEnityHRCHY" : "Planet>continent>country>state>city",
"parEnityVal" : "earth>North America>Massachusetts>Boston",
"entityVal" : [
"MIT",
"Harvard",
"New England"
]
}
Document 2
{
"entity" : "university",
"parEnityHRCHY" : "Planet>continent>country>state",
"parEnityVal" : "earth>North America>Massachusetts",
"entityVal" : [
"A",
"B",
"C"
]
}
i want to fetch the best match "entityVal" for the input "entity","parEnityHRCHY","parEnityVal"
if the value is not available at the exact match it should look recursively till the root.
for eg. in above case if "university" value are not available at the city level it should look at the state level like
if matches exact below condition return result.
Input:
"parEnityHRCHY" : "Planet>continent>country>state>city",
"parEnityVal" : "earth>North America>Massachusetts>Boston",
else look at one level up
"parEnityHRCHY" : "Planet>continent>country>state",
"parEnityVal" : "earth>North America>Massachusetts",
and so on until the root element.
please suggest some approach, i am planning to use $text search , max number of documents in collection approx 1 Million, max HRCHY level 10.
You can try something like this:
db.doc.find({"parEnityHRCHY" : "Planet>continent>country>state",'parEnityVal':{$regex:"earth>North America>Massachusetts"}})
But I am not sure If you're looking for something like this.

multi updating a key along the documents of a collection using pymongo

I have lots of documents inside a collection.
The structure of each of the documents inside the collection is as it follows:
{
"_id" : ObjectId(....),
"valor" : {
"AB" : {
"X" : 0.0,
"Y" : 142.6,
},
"FJ" : {
"X" : 0.2,
"Y" : 3.33
....
The collection has currently about 200 documents and I have noticed that one of the keys inside valor has the wrong name. In this case we will say "FJ" shall be "JOF" in all the docs of the collection.
Im pretty sure it is possible to change the key in all the docs using the update function of pymongo. The problem I am facing is that when I visit the online doc available https://docs.mongodb.com/v3.0/reference/method/db.collection.update/ only explains how to change the values(which I would like to remain how they currently are and change only the keys).
This is what I have tried:
def multi_update(spec_key,key_updte):
rdo=col.update((valor.spec_key),{"$set":(valor.key_updte)},multi=True)
return rdo
print(multi_update('FJ','JOF'))
But outputs name 'valor' is not defined . I thought I shall use valor.specific_key to access to the corresponding json
how can I update a key only along the docs of the collection?
You have two problems. First, valor is not an identifier in your Python code, it's a field name of a MongoDB document. You need to quote it in single or double quotes in Python in order to make it a string and use it in a PyMongo update expression.
Your second problem is, MongoDB's update command doesn't allow you set one field to the value of another, nor to rename a field. However, you can reshape all the documents in your collection using the aggregate command with a $project stage and store the results in a second collection using a $out stage.
Here's a complete example to play with:
db = MongoClient().test
collection = db.collection
collection.delete_many({})
collection.insert_one({
"valor" : {
"AB" : {
"X" : 0.0,
"Y" : 142.6,
},
"FJ" : {
"X" : 0.2,
"Y" : 3.33}}})
collection.aggregate([{
"$project": {
"valor": {
"AB": "$valor.AB",
"FOJ": "$valor.FJ"
}
}
}, {
"$out": "collection2"
}])
This is the dangerous part. First, check that "collection2" has all the documents you want, in the desired shape. Then:
collection.drop()
db.collection2.rename("collection")
import pprint
pprint.pprint(collection.find_one())

Mongo db : query on nested json

Sample json object :
{ "_id" : ObjectId( "55887982498e2bef5a5f96db" ),
"a" : "x",
"q" : "null",
"p" : "",
"s" : "{\"f\":{\"b\":[\"I\"]},\"time\":\"fs\"}" }
need all documents where time = fs
My query :
{"s":{"time" : "fs"}}
above returns zero products but that is not true.
There are two problems here. First of all s is clearly a string so your query cannot work. You can use $regex as below but it won't be very efficient:
{s: {$regex: '"time"\:"fs"'}}
I would suggest converting s fields to proper documents. You can use JSON.parse to do it. Documents can be updated based on a current value using db.foo.find().snapshot().forEach. See this answer for details.
Second problem is your query is simply wrong. To match time field you should use dot notation:
{"s.time" : "fs"})

MongoDB unable to eliminate certain fields from mongodb query result

I have got records in my collection as shown below
{
"_id" : ObjectId("53722c39e4b04a53021cf3c6"),
"symbol" : "AIA",
"tbq" : 1356,
"tsq" : 0,
"tquan" : 6831336,
"tvol" : 17331.78,
"bquantity" : 1356,
"squantity" : 0
}
{
"_id" : ObjectId("53722c38e4b04a53021cf3c1"),
"symbol" : "SAA",
"tbq" : 0,
"tsq" : 9200,
"tquan" : 6036143,
"tvol" : 50207.43,
"bquantity" : 0,
"squantity" : 9200
}
I am displaying the results in the ascending order of bquantity, and also at the same time I want to display only certain columns in the result (symbol, bquantity, squantity) and ignore the rest.
I tried with the below query, but still its displaying all the fields .
Please tell me how can i eliminate those fields from the result ?
db.stock.find().sort(
{"bquantity":-1},
{
symbol: 1,
bquantity: 1,
squantity:1 ,
_id:0,
tbq:0,
tsq:0,
tquan:0,
tvol:0
}
)
The field filter is a parameter to the find function, not to the sort function, i.e.:
db.stock.find({}, { _id:0,tbq:0, tsq:0,tquan:0,tvol:0}).sort({"bquantity":-1})
The empty hash used as first parameter to find is required as an 'empty query' which matches all documents in stock.

How do I remove an element in an array based on content?

I am working with MongoDB and Perl. Here is my data structure:
{
"_id" : ObjectId("501976f8005c8b541d000000"),
"err_id" : "err",
"solution" : [
{
"attachment" : "attach",
"macr" : "macrs",
"yammer" : "yam",
"resolution" : "l",
"salesforce" : "salesforce",
"username" : "bob"
},
{
"attachment" : "attach",
"macr" : "macrs",
"yammer" : "yam",
"resolution" : "losssss",
"salesforce" : "salesforce",
"username" : "bob"
}
]
}
As you can see, I have an array with objects inside. I have created this using the Perl MongoDB library.
I am familiar with some syntax for manipulating arrays in the Perl MongoDB lib. For example, I use this to find entries with a username the same as $username.
$users->find({"solution.username" => $username});
I thought removing an element would be as simple:
$users->remove({"solution.username" => $username});
But alas, it is not so. I have tried this and using pull, but to no avail! I've had a hard time finding this. Does anybody know the syntax to remove an array element based on the contents of one of its fields?
The MongoDB::Collection remove() method will remove documents matched by your query .. so definitely not what you are looking for.
To delete specific fields you should use $unset.
Your solution.usernames are actually in an array, so you would have to include an array index for the fields to delete, eg:
$users->update({"_id" => '123'}, {
'$unset' => {
'solution.0.username' => 1,
'solution.1.username' => 1
}
});
I'm not aware of a shorter syntax to unset all fields matching username within the solution array, but you can add multiple solution.#.username fields to the $unset command.
My example above deletes the first two username entries from the array. If the matching document(s) had more than two username entries, each time you ran this update you would delete up to two more entries (if they exist).