I'm trying to use '$' to update a certain item in a list. The item is determined by both firstName AND lastName, and in this scenario, firstName + lastName combination is unique.
So here is my document
{
"_id" : ObjectId("592403125ec4f65fb02e36a0"),
"friends" : [
{
"age" : 30.0,
"fname" : "qiang",
"lname" : "he"
},
{
"age" : 31.0,
"fname" : "deng",
"lname" : "pan"
},
{
"age" : 22.0,
"fname" : "xiong",
"lname" : "lan"
},
{
"age" : 23.0,
"fname" : "qiang",
"lname" : "lan"
}
]
}
So I'm trying to change the age of Qiang Lan, which is item 3 in the list. But he has the same firstName with item 0 and same lastName with item 2. The code I use is:
db.getCollection('test').update({'friends.fname': 'qiang', 'friends.lname': 'lan'}, {$set:{'friends.$.age': 50}})
This code did not change the age of Qiang Lan, but it changed item 0 Qiang He's age to 50, which is the earliest match of one of my terms. So the terms is actually treated as OR, not AND.
I know this can be solved by adding a fullName field which equals to firstName + lastName, and match the fullName on update. I'm just trying to figure out, is '$' designed to act like this on purpose, or am I using it wrong?
Thanks!
This is a common mistake. You in fact want $elemMatch here, as it's intended usage is for "multiple conditions on the same array item":
db.getCollection('test').update(
{ "friends": { "$elemMatch": { "fname": 'qiang', "lname": 'lan'} },
{ "$set":{ "friends.$.age": 50 } }
)
Without the $elemMatch the query conditions are considering the "whole" array, and will match either "fname" or "lname" in any array element. This is why update() complains.
try using and condition.
$and performs a logical AND operation on an array of two or more
expressions (e.g. , , etc.) and selects the
documents that satisfy all the expressions in the array.
db.getCollection('test').update({$and:[{'friends.fname': 'qiang'}, {'friends.lname': 'lan'}], {$set:{'friends.$.age': 50}})
Related
I'm struggling to create a find query that finds nodes that contain "Item1".
{
"_id" : ObjectId("589274f49bd4d562f0a15e07"),
"Value" : [["Item1", {
"Name" : "John",
"Age" : 45
}], ["Item2", {
"Address" : "123 Main St.",
"City" : "Hometown",
"State" : "ZZ"
}]]
}
In this example, "Item1" is not a key/value pair, but rather just a string that is part of an array that is part of a larger array. This is a legacy format so I can't adjust it unfortunately.
I've tried something like: { Value: {$elmemmatch:{$elemmatch:{"Item1"}}}, but that is not returning any matches. Similarly, $regex is not working since it only seems to match on string objects (and the overall object is not a string, but a string in an array in an array).
It seems like you should use the $in or $eq operator to match value.
So try this:
db.collection.find({'Value':{$elemMatch:{$elemMatch:{$in:['Item1']}}}})
Or run this to get the specific Item
db.collection.find({},{'Value':{$elemMatch:{$elemMatch:{$in:['Item1']}}}})
Hope this helps.
var data = {
"_id":"ObjectId('589274f49bd4d562f0a15e07')",
"Value":[
[
"Item1",
{
"Name":"John",
"Age":45
}
],
[
"Item2",
{
"Address":"123 Main St.",
"City":"Hometown",
"State":"ZZ"
}
]
]
}
data.Value[0][0] // 'Item1'
Copy and paste on repl it works.
There was an error on structure ofr your data
I have a document in Mongodb collection, where I want to remove an object, using title key.
I tried using $unset, but it only removes the title key not the object to which it belongs.
{
"_id" : ObjectId("576b63d49d20504c1360f688"),
"books" : [
{
"art_id" : ObjectId("574e68e5ac9fbac82489b689"),
"title" :"abc",
"price" : 40
},
{
"art_id" : ObjectId("575f9badada0500d192c53f4"),
"title" : "xyz",
"price" : 20
},
{
"art_id" : ObjectId("57458224d86b3d1561150f17"),
"title" : "def",,
"price" : 30
}
],
"user_id" : "575570c315e27d13167dfc0d"
}
To remove the entire object that contains the query object use db.remove() query.
For your case:
db.yourcollection.remove({"books.title": "abc"});
Please double check the format in which the element of array is referenced.
This removes the entire objects that contains the embedded query obj. To remove only a single object, provide it with another field to uniquely identify it.
If you only want to remove the object that contains the title field from the array but wants to keep the object that contains the array, then please use the $pull operator. This answer will be of help.
Example: if you want to remove object
{
"art_id" : ObjectId("574e68e5ac9fbac82489b689"),
"title" :"abc",
"price" : 40
}
just from the array but keep the parent object like
{
"_id" : ObjectId("576b63d49d20504c1360f688"),
"books" : [
{
"art_id" : ObjectId("575f9badada0500d192c53f4"),
"title" : "xyz",
"price" : 20
},
{
"art_id" : ObjectId("57458224d86b3d1561150f17"),
"title" : "def",,
"price" : 30
}
],
"user_id" : "575570c315e27d13167dfc0d"
}
use
db.mycollection.update(
{'_id': ObjectId("576b63d49d20504c1360f688")},
{ $pull: { "books" : { "title": "abc" } } },
false,
true
);
$unset won't remove the object from an array. The $unset operator deletes a particular field. doc.
Use $pull instead.
The $pull operator removes from an existing array all instances of a value or values that match a specified condition.
Try following query
db.collName.update({$pull : {books:{title:abc}}})
Refer $pull-doc
Hope this will help you.
And if... ¿Do I want to delete an object that is inside a document and not as an array?
{
"_id" : ObjectId("576b63d49d20504c1360f688"),
"books" : {
"574e68e5ac9fbac82489b689": {
"art_id" : ObjectId("574e68e5ac9fbac82489b689"),
"title" :"abc",
"price" : 40
},
"575f9badada0500d192c53f4": {
"art_id" : ObjectId("575f9badada0500d192c53f4"),
"title" :"xyz",
"price" : 20
},
"57458224d86b3d1561150f17": {
"art_id" : ObjectId("57458224d86b3d1561150f17"),
"title" : "def",
"price" : 30
}
},
"user_id" : "575570c315e27d13167dfc0d"
}
The solutions is this:
db.auctions.update(
{'_id': ObjectId("576b63d49d20504c1360f688")},
{$unset: {"books.574e68e5ac9fbac82489b689":
{_id: "574e68e5ac9fbac82489b689"}}})
Try using pull.
https://docs.mongodb.com/manual/reference/operator/update/pull/
$pull
The $pull operator removes from an existing array all instances of a value or values that match a specified condition.
The $pull operator has the form:
{ $pull: { <field1>: <value|condition>, <field2>: <value|condition>, ... } }
To specify a <field> in an embedded document or in an array, use dot notation.
Try in the mongo shell
db.yourcollection.remove({books:[{title:'title_you_want'}]})
Careful with the braces.
Is there a way to use db.collection.find() to query for a specific value in a sub-document and find those documents that match. For example:
{
{ 'Joe' : {eyecolor : 'brown'},
{ 'Mary' : {eyecolor : 'blue'},
....
}
I want to return the names of all people whose eyecolor is blue.
You need to specify the full path to a value for search to work:
db.people.find({ "Joe.eyecolor" : "brown" })
You can't switch to an array of people instead of an associative array style you're using now, as there is no way to return only array elements that match conditions. You can use $elemMatch to return the first match, but that's not likely what you'd want. Or, you could still use arrays, but you'd need to filter the array further within your client code (not the database).
You might be able to use the Aggregation framework, but it wouldn't use indexes efficiently, as you'd need to $unwind the entire array, and then do filtering, brute force. And if the data contained is more complex, the fact that projections when using the AF require you to manually specify all fields, it becomes a bit cumbersome.
To most efficiently do the query you're showing, you'd need to not use subdocuments, and instead place the people as individual documents:
{
name: "Joe",
eyecolor: "brown"
}
Then, you could just do a simple search like:
db.people.find({eyecolor: "brown"})
Yes and no. You can query for all documents that have a matching person, but you can't query for all persons directly. In other words, subdocuments are not virtual collections, you'll always have the 'parent' document returned.
The example you posted comes with the additional complexity that you're using the name as a field key, which prevents you from using the dot notation.
In general, if you have a number of similar things, it's best to put them in a list, e.g.
{
"_id" : 132,
"ppl" : [ { "Name" : "John", "eyecolor" : "blue" },
{ "Name" : "Mary", "eyecolor" : "brown" },
...
]
}
Then, you can query using the aggregation framework:
db.collection.aggregate([
// only match documents that have a person w/ blue eyes (can use indexing)
{$match : { "ppl.eyecolor" : "blue" } },
// unwind the array of people
{$unwind : "$ppl" },
// match only those with blue eyes
{$match : { "ppl.eyecolor" : "blue" }},
// optional projection to make the result a list of people
{$project : { Name : "$ppl.Name", EyeColor: "$ppl.eyecolor" }} ]);
Which gives a result like
"result" : [
{
"_id" : 132,
"Name" : "John",
"EyeColor" : "blue"
},
{
"_id" : 12,
"Name" : "Jimmy",
"EyeColor" : "blue"
},
{
"_id" : 4312,
"Name" : "Jimmy",
"EyeColor" : "blue"
},
{
"_id" : 4312,
"Name" : "Marc",
"EyeColor" : "blue"
}
],
"ok" : 1
So, what I'm trying to do is query all documents that have a City of 'Paris' and a State of 'France'. I need to do some kind of join, but I haven't been able to figure out how to construct it.
I'm using the c# driver, but I'll gladly accept help using any method.
{
"_id" : ObjectId("519b407f3c22a73a7c29269f"),
"DocumentID" : "1",
"Meta" : [{
"Name" : "City",
"Value" : "Paris",
}, {
"Name" : "State",
"Value" : "France",
}
}]
}
{
"_id" : ObjectId("519b407f3c22a73a7c29269g"),
"DocumentID" : "2",
"Meta" : [{
"Name" : "City",
"Value" : "Paris",
}, {
"Name" : "State",
"Value" : "Texas",
}
}]
}
The $elemMatch operator is used to indicate that all the conditions within it must be matched by the same array element. So (to switch to shell syntax) to match all documents which have meta city Paris you would do
db.collection.find( {Meta:{$elemMatch:{Name:"City",Value:"Paris"}}} )
This assures you won't match something which has Name: "somethingelse", Value: "Paris" somewhere in its array with a different array element matching the Name:"City".
Now, default combination for combining query conditions is "and" so you can continue adding attributes:
db.collection.find( {Meta: {
$elemMatch:{Name:"City",Value:"Paris"},
$elemMatch:{Name:"State",Value:"France"}
}
}
)
Now if you want to add another condition you keep adding it but if you want a NOT then you do it like this:
db.collection.find( {Meta: {
$elemMatch:{Name:"City",Value:"Paris"},
$elemMatch:{Name:"State",Value:"France"},
$not: {$elemMatch:{Name:"Arrondissement",Value:"Louvre"}}
}
}
)
I might be answering my own question here, but I'm new to MongoDB, so while this appears to give me the results I'm after, it might not be the optimum approach.
var result = collection.Find(
Query.And(
Query.ElemMatch("Meta", Query.EQ("Name", "City")),
Query.ElemMatch("Meta", Query.EQ("Value", "Paris")),
Query.ElemMatch("Meta", Query.EQ("Name", "State")),
Query.ElemMatch("Meta", Query.EQ("Value", "France")))
);
Which leads to a follow up - how would I get all of the documents whose 'City' is 'Paris' and 'State' is 'France' but whose 'Arrondissement' is not 'Louvre'?
Here is my data structure.
[{
"name": "David",
"lastname": "",
},
{
"name": "Angela"
}]
"lastname" is sometimes present and sometimes not and sometime is "".
I want to get all rows that have lastname not equal to "". But this does not work. It returns both the rows when lastname is "" and when lastname is not present at all. in the example above I want to only get the David node.
db.collection.find( {"lastname": {"$ne": ""}} )
db.collection.find({"lastname" : {"$exists" : true, "$ne" : ""}})
In the mongo shell (id's omitted to save space)
> db.collection.find()
{ "name" : "Angela" }
{ "name" : "David", "lastname" : "" }
{ "name" : "Kyle", "lastname" : "Test" }
{ "name" : "John", "lastname" : null }
> db.collection.find({"lastname" : {"$exists" : true, "$ne" : ""}})
{ "name" : "Kyle", "lastname" : "Test" }
{ "name" : "John", "lastname" : null }
In case you also want to filter out matches against null values you need to adjust the criteria as follows (we can also get rid of $exists as "$ne": null takes care of this)
> db.collection.find({$and:[{"lastname": {"$ne": null}}, {"lastname": {"$ne": ""}}]})
{ "name" : "Kyle", "lastname" : "Test" }
Facing this problem I thought in another solution:
db.collection.find({"lastname": {"$gte": " "}})
With this I could get only the not empty strings, also ignoring null and not existent field, because any printable value (ASCII) has a greater value than space (32).
https://en.wikipedia.org/wiki/ASCII
You can use a regex query:
db.test.find({ "lastname": /(.|\s)*\S(.|\s)*/ })
This regex matches strings beginning or ending with 0 or N whitespaces (.|\s) and it have to be one or more non-whitespaces \S in the middle.
I'm not sure if this helps, but it has worked for me. The regex .+ returns anything that contains more than 1 character, whereas .* returns 0 or more. So it won't return anything less than 0 characters.
In terms of strings containing only whitespace, I don't think this solution can handle that.