get a mongo document based in two different values - mongodb

I have the following document structure
{
"_id" : "aaa0001",
"path" : "/some/path",
"information" : {
"name" : "info"
},
"colors" : {
"colors" : [
{
"key" : "AAAA001",
"name" : "White"
},
{
"key" : "BBBB002",
"name" : "Black"
}
]
}
}
the idea is that I have to return the document by the color key. I have two parameters the "path" and the "color", so, I was trying to make something like this
db.components.find(
{$and:[
{"path" : "/some/path"},
{"colors":{"colors" : {$elemMatch: { "key" : "AAAA001" } } } }
]})
I'm getting the following message "Script is executed successfully, but there is no results to show".
Can anyone give me some directions regarding this?
thanks

Use the following query:
db.components.find({
"path": "/some/path",
"colors.colors.key" : "AAAA001"
})
MongoDB expects query document to contain field-value pairs { <field>: <value> }. So, in your example you're querying for a document with colors field equal to:
{"colors" : {$elemMatch: { "key" : "AAAA001" } } }
As for $and and $elemMatch operators, you don't need them in such a simple query.
For more information read Query Documents.
Update
You can also select only matching subdocument from colors array using Positional Operator $:
db.components.find({
"path": "/some/path",
"colors.colors.key" : "AAAA001"
}, {
_id: 0,
"colors.colors.$": 1
})
Though, you won't be able to change your documents structure, thus getting
{ "colors" : { "colors" : [ { "key" : "AAAA001", "name" : "White" } ] } }

Related

Individual search result in multiple values in arrays

I have following model:
{
"_id" : ObjectId("5d61aaf8108e185191552bbb"),
"serials" : [
"e127av48-0697-4977-b096-5ce79c89a414",
"d163f80a-55ff-40fe-90b4-331ece5bebd5",
"4740021f-e9b5-4ca5-bf0e-8554c123bb94",
"320ffd42-f101-4b1d-8ff4-80bc693a29e6",
"fef5e68b-aed0-4a96-9488-7941c41d1c1f",
"2c0752ba-bf7a-4a3b-bd9f-14db4b2f8bae",
"6c5ff44d-5979-4bff-af12-9e6d282c3789",
"9c91bf91-72d7-4b71-827b-924947d6e93d",
"fb34b28e-afb1-4b6a-a3c1-5a1fe44246ee",
"91ab22ef-702f-4cbd-8919-a67a2b9a684c",
"ee1a7cb2-e088-47e6-a824-c8697df7d94c",
"0dc4c687-4db2-481e-a1a6-491320dede11",
"34612148-3e01-44ee-b262-de2035e63691",
"5ba85baf-e48a-40af-8578-55ff1a873c76",
"19fe3672-b6cb-4bb6-8d21-93412b938584",
"1d0d6f6d-1b49-461b-8661-ecbf43a6595e",
"d9a5455c-65ee-45e1-ae49-33cc15dec841",
"4a690a00-a76c-4d3e-aee3-78b2bb731b0c",
"ae331830-40b4-457c-8cc4-5d548f769c3e",
"fe3e460b-c89d-4ace-8a36-5ba2b53bf4d0",
"2cc6a2a0-e029-475f-a7fc-a46a79afb605",
"a7d07767-eada-4ce3-b083-9b048e9ae9f4"
],
"name" : "ApiCard",
"producer" : "Farmina",
"form" : "syrop",
"__v" : 0
}
I would like to retrive documents (multiple) from collection based on this serial numbers ("serials" field). For example i am finding:
[
"e127av48-0697-4977-b096-5ce79c89a414",
"d163f80a-55ff-40fe-90b4-331ece5bebd5",
"4740021f-e9b5-4ca5-bf0e-8554c123bb94",
"key that doesn't exist",
]
We have to assume that one of the serial number doesn't exist, so would like to get information for individual serial, expected output:
[
{
"serial":"e127av48-0697-4977-b096-5ce79c89a414",
"doc":{
....whole document where above serial is in array field "serials"
}
},
{
"serial":"e127av48-0697-4977-b096-5ce79c89a414",
"doc":{
....whole document where above serial is in array field "serials"
}
},
{
"serial":"e127av48-0697-4977-b096-5ce79c89a414",
"doc":{
....whole document where above serial is in array field "serials"
}
},
{
"serial":"key that doesn't exist",
"doc": null
}
]
I was trying the simplest solution - mongodb find by multiple array items, but unfortunately it'doesn't return info for individual serial number. I'am not sure it's possible to prepare this kind of query. I think some complex aggregation could perform it, but i don't even know this kind of pipelines.
Of course, i can get simple solution by using multiple aggregate or even find, but it could impact on performance, when application will be looking for 10000 records per request.
The following query can do the trick:
db.collection.aggregate([
{
$limit:1
},
{
$project:{
"_id":0,
"serialsToSearch":[
"e127av48-0697-4977-b096-5ce79c89a414",
"d163f80a-55ff-40fe-90b4-331ece5bebd5",
"4740021f-e9b5-4ca5-bf0e-8554c123bb94",
"key that doesn't exist",
]
}
},
{
$unwind:"$serialsToSearch"
},
{
$lookup:{
"from":"collection",
"let":{
"serial":"$serialsToSearch"
},
"pipeline":[
{
$match:{
$expr:{
$in:["$$serial","$serials"]
}
}
},
{
$project:{
"serials":0
}
}
],
"as":"searialsLookup"
}
},
{
$unwind:{
"path":"$searialsLookup",
"preserveNullAndEmptyArrays":true
}
},
{
$project:{
"serial":"$serialsToSearch",
"doc":{
$ifNull:["$searialsLookup",null]
}
}
}
]).pretty()
Data Set:
{
"_id" : ObjectId("5d61aaf8108e185191552bbb"),
"serials" : [
"e127av48-0697-4977-b096-5ce79c89a414",
"d163f80a-55ff-40fe-90b4-331ece5bebd5",
"4740021f-e9b5-4ca5-bf0e-8554c123bb94",
"320ffd42-f101-4b1d-8ff4-80bc693a29e6",
"fef5e68b-aed0-4a96-9488-7941c41d1c1f",
"2c0752ba-bf7a-4a3b-bd9f-14db4b2f8bae",
"6c5ff44d-5979-4bff-af12-9e6d282c3789",
"9c91bf91-72d7-4b71-827b-924947d6e93d",
"fb34b28e-afb1-4b6a-a3c1-5a1fe44246ee",
"91ab22ef-702f-4cbd-8919-a67a2b9a684c",
"ee1a7cb2-e088-47e6-a824-c8697df7d94c",
"0dc4c687-4db2-481e-a1a6-491320dede11",
"34612148-3e01-44ee-b262-de2035e63691",
"5ba85baf-e48a-40af-8578-55ff1a873c76",
"19fe3672-b6cb-4bb6-8d21-93412b938584",
"1d0d6f6d-1b49-461b-8661-ecbf43a6595e",
"d9a5455c-65ee-45e1-ae49-33cc15dec841",
"4a690a00-a76c-4d3e-aee3-78b2bb731b0c",
"ae331830-40b4-457c-8cc4-5d548f769c3e",
"fe3e460b-c89d-4ace-8a36-5ba2b53bf4d0",
"2cc6a2a0-e029-475f-a7fc-a46a79afb605",
"a7d07767-eada-4ce3-b083-9b048e9ae9f4"
],
"name" : "ApiCard",
"producer" : "Farmina",
"form" : "syrop",
"__v" : 0
}
Output:
{
"serial" : "e127av48-0697-4977-b096-5ce79c89a414",
"doc" : {
"_id" : ObjectId("5d61aaf8108e185191552bbb"),
"name" : "ApiCard",
"producer" : "Farmina",
"form" : "syrop",
"__v" : 0
}
}
{
"serial" : "d163f80a-55ff-40fe-90b4-331ece5bebd5",
"doc" : {
"_id" : ObjectId("5d61aaf8108e185191552bbb"),
"name" : "ApiCard",
"producer" : "Farmina",
"form" : "syrop",
"__v" : 0
}
}
{
"serial" : "4740021f-e9b5-4ca5-bf0e-8554c123bb94",
"doc" : {
"_id" : ObjectId("5d61aaf8108e185191552bbb"),
"name" : "ApiCard",
"producer" : "Farmina",
"form" : "syrop",
"__v" : 0
}
}
{ "serial" : "key that doesn't exist", "doc" : null }
Note: The query won't give expected output if the collection would be empty.
Aggregation stages details:
STAGE I: Limiting the records to 1, as initially, our motive is to inject the input array in aggregation. The injection would be done in no time.
STAGE II: Projecting the input array as serialsToSearch
STAGE III: Now we have the input array as a field, we can unwind it
STAGE IV: Lookup in the same collection with each field of the input array and check if the searched serial is present in serials array
STAGE V: unwinding the lookup output
STAGE VI: Projecting fields as per the response required.

How to search document with condition of not having exact object in array of objects?

I have a collection of persons whose schema looks like the collection of following documents.
Document: {
name:
age:
educations:[{
title:xyz,
passed_year:2005,
univercity:abc},
{
title:asd
passed_year:2007,
univercity:mno
}],
current_city:ghi
}
Now I wanna show all the persons who has not done xyz education from abc university in year 2005.
I think two possible queries for this need but not sure which one to use as both of them are giving me the output
Query 1:
db.persons.find({"education":{$ne:{$elemMatch:{"title":"xyz","passed_year":2005,"univercity":"abc"}}}})
Query 2:
db.persons.find({"education":{$not:{$elemMatch:{"title":"xyz","passed_year":2005,"univercity":"abc"}}}})
I'm quite confused about operator $ne and $not, which one should I use with $elemMatch as both of them are giving me the output.
Given this $elemMatch: {"title":"xyz","passed_year":2005,"univercity":"abc"} I think you want to exclude any documents which contain an sub document in the educations array which contains all of these pairs:
"title" : "xyz"
"passed_year" : 2005
"univercity" : "abc"
This query will achieve that:
db.persons.find({
"educations": {
$not: {
$elemMatch:{"title": "xyz", "passed_year": 2005, "univercity": "abc"}
}
}
})
In your question you wrote:
both of them are giving me the output
I suspect this is because your query is specifying education whereas the correct attribute name is educations. By specifying education you are adding a predicate which cannot be evaluated since it references a non existent document attribute so regardless of whether that predicate uses $ne or $not it will simply not be applied.
In answer to the question of which operator to use: $not or $ne: if you run the above query with .explain(true) you'll notice that the parsed query produced by Mongo is very different for each of these operators.
Using $ne
"parsedQuery" : {
"$not" : {
"educations" : {
"$eq" : {
"$elemMatch" : {
"title" : "xyz",
"passed_year" : 2005,
"univercity" : "abc"
}
}
}
}
}
Using $not:
"parsedQuery" : {
"$not" : {
"educations" : {
"$elemMatch" : {
"$and" : [
{
"passed_year" : {
"$eq" : 2005
}
},
{
"title" : {
"$eq" : "xyz"
}
},
{
"univercity" : {
"$eq" : "abc"
}
}
]
}
}
}
}
So, it looks like use of $ne causes Mongo to do something like this psuedo code ...
not educations equalTo "$elemMatch" : {"title" : "xyz", "passed_year" : 2005, "univercity" : "abc"}
... i.e. it treats the elemMatch clause as if it is the RHS of an equality operation whereas use of $not causes Mongo to actually evaluate the elemMatch clause.

Positional operator and field limitation

In a find query projection, fields I specify after the positional operator are ignored and the whole document is always returned.
'myArray.$.myField' : 1 behave exactly like 'myArray.$' : 1
the positional operator selects the right document. But this document is quite big. I would like to project only 1 field from it.
Exemple:
db.getCollection('match').find({"participantsData.id" : 0001}, { 'participantsData.$.id': 1, })
here the response I have
{
"_id" : "myid",
"matchCreation" : 1463916465614,
"participantsData" : [
{
"id" : 0001,
"plenty" : "of",
"other" : "fields",
"and" : "subdocuments..."
}
]
}
This is what I want
{
"_id" : "myid",
"matchCreation" : 1463916465614,
"participantsData" : [
{
"id" : 0001
}
]
}
Is it possible with mongo?
Yes it can be done in mongo
Please try the below query
db.getCollection('match').find(
{"participantsData.id" : 0001},
{"participantsData.id": 1, "matchCreation": 1 })
This will give you the below result
{
"_id" : "myid",
"matchCreation" : 1463916465614,
"participantsData" : [
{
"id" : 1
}
]
}

Mongodb Update/Upsert array exact match

I have a collection :
gStats : {
"_id" : "id1",
"criteria" : ["key1":"value1", "key2":"value2"],
"groups" : [
{"id":"XXXX", "visited":100, "liked":200},
{"id":"YYYY", "visited":30, "liked":400}
]
}
I want to be able to update a document of the stats Array of a given array of criteria (exact match).
I try to do this on 2 steps :
Pull the stat document from the array of a given "id" :
db.gStats.update({
"criteria" : {$size : 2},
"criteria" : {$all : [{"key1" : "2096955"},{"value1" : "2015610"}]}
},
{
$pull : {groups : {"id" : "XXXX"}}
}
)
Push the new document
db.gStats.findAndModify({
query : {
"criteria" : {$size : 2},
"criteria" : {$all : [{"key1" : "2015610"}, {"key2" : "2096955"}]}
},
update : {
$push : {groups : {"id" : "XXXX", "visited" : 29, "liked" : 144}}
},
upsert : true
})
The Pull query works perfect.
The Push query gives an error :
2014-12-13T15:12:58.571+0100 findAndModifyFailed failed: {
"value" : null,
"errmsg" : "exception: Cannot create base during insert of update. Cause
d by :ConflictingUpdateOperators Cannot update 'criteria' and 'criteria' at the
same time",
"code" : 12,
"ok" : 0
} at src/mongo/shell/collection.js:614
Neither query is working in reality. You cannot use a key name like "criteria" more than once unless under an operator such and $and. You are also specifying different fields (i.e groups) and querying elements that do not exist in your sample document.
So hard to tell what you really want to do here. But the error is essentially caused by the first issue I mentioned, with a little something extra. So really your { "$size": 2 } condition is being ignored and only the second condition is applied.
A valid query form should look like this:
query: {
"$and": [
{ "criteria" : { "$size" : 2 } },
{ "criteria" : { "$all": [{ "key1": "2015610" }, { "key2": "2096955" }] } }
]
}
As each set of conditions is specified within the array provided by $and the document structure of the query is valid and does not have a hash-key name overwriting the other. That's the proper way to write your two conditions, but there is a trick to making this work where the "upsert" is failing due to those conditions not matching a document. We need to overwrite what is happening when it tries to apply the $all arguments on creation:
update: {
"$setOnInsert": {
"criteria" : [{ "key1": "2015610" }, { "key2": "2096955" }]
},
"$push": { "stats": { "id": "XXXX", "visited": 29, "liked": 144 } }
}
That uses $setOnInsert so that when the "upsert" is applied and a new document created the conditions specified here rather than using the field values set in the query portion of the statement are used instead.
Of course, if what you are really looking for is truly an exact match of the content in the array, then just use that for the query instead:
query: {
"criteria" : [{ "key1": "2015610" }, { "key2": "2096955" }]
}
Then MongoDB will be happy to apply those values when a new document is created and does not get confused on how to interpret the $all expression.

Modify a document inside an array in MongoDB

Past answers (from mid 2013 and before) don't seem to work and links to the documentation are all out of date.
Example user object:
{
"name": "Joe Bloggs",
"email": "joebloggs#example.com",
"workstations" : [
{ "number" : "10001",
"nickname" : "home" },
{ "number" : "10002",
"nickname" : "work" },
{ "number" : "10003",
"nickname" : "vacation" }
]
}
How can I modify the nickname of a workstation?
I tried using $set, workstations.$ and workstations.nickname but none gave the desired results.
Short answer, you have to use array index. For example, you want to update the nickname of 10002: {$set:{"workstations.1.nickname":"newnickname"}}
Here is the complete example:
> db.test.update({"_id" : ObjectId("5332b7cf4761549fb7e1e72f")},{$set:{"workstations.1.nickname":"newnickname"}})
> db.test.findOne()
{
"_id" : ObjectId("5332b7cf4761549fb7e1e72f"),
"email" : "joebloggs#example.com",
"name" : "Joe Bloggs",
"workstations" : [
{
"number" : "10001",
"nickname" : "home"
},
{
"nickname" : "newnickname",
"number" : "10002"
},
{
"number" : "10003",
"nickname" : "vacation"
}
]
}
>
If you don't know the index (position of the workstations), you can update the doc using $elemMatch:
>db.test.update(
{
"email": "joebloggs#example.com",
"workstations": { "$elemMatch" { "number" : "10002" } }
},
{
"$set": { "workstations.$.nickname": "newnickname2" }
}
)
>
#naimdjon's answer would work. To generalize, you could use the $elemMatch operator in combination with the $ positional operator to update one element in the array using below query:
db.test.update({
// Find the document where name="Joe Bloggs" and the element in the workstations array where number = "10002"
"name": "Joe Bloggs",
"workstations":{$elemMatch:{"number":"10002"}}
},
{
// Update the nickname in the element matched
$set:{"workstations.$.nickname":"newnickname"}
})
Note: $elemMatch is only required if you need to match more than one component in the array. If you are going to match on just the number, you could use "workstations.number":"10002"
As long as you know "which" entry you wish to update then the positional $ operator can be of help. But you need to update your query form:
db.collection.update(
{
"email": "joebloggs#example.com",
"workstations": { "$elemMatch" { "nickname" : "work" } }
},
{
"$set": { "workstations.$.nickname": "new name" }
}
)
So that is the general form. What you need to do here is "match" something in the array in order to get a "position" to use for the update.
Alternately, where you know the position, then you can just "specify" the position with "dot notation":
db.collection.update(
{
"email": "joebloggs#example.com",
},
{
"$set": { "workstations.1.nickname": "new name" }
}
)
Which updates the second element in the array, and does not need the "matching" part in the query.