Find in subdocuments returning document - mongodb

I have a collection looking somewhat like this:
{
"colors": ["blue","white"],
"items": {
"old": {
"name": "test"
}
"current": {
"name": "new_test"
}
}
},
{
"colors": ["red","green"],
"items": {
"old": {
"name": "test2"
}
"current": {
"name": "new_test2"
}
}
},
Is it possible to use find like this:
db.collection.find({"items": { "old": { "name": "test" } } })
So the command would return:
{
"colors": ["blue","white"],
"items": {
"old": {
"name": "test"
}
"current": {
"name": "new_test"
}
}
}
Is this possible?

Yes, you can use the 'dot notation' to reach into the object:
db.collection.find({"items.old.name": "test" })
The query syntax you used also works, but it has different semantics: It will match the entire subdocument for equality instead of just a single field. For instance, the following query would also return a result:
db.foo.find({"items.old": {"name" : "test"} }),
butdb.collection.find({"items": { "old": { "name": "test" } } }) does not, because items also contains a current field.

Related

MongoDb : Find and filter values from nested map

I have some date in mongo db
[
{
"_id": ObjectId("5a934e000102030405000000"),
"orgId": "606abce197dc265ac41ae82c",
"registrations": {
"id1": {
"status": "status",
"topStage": {
"id": "stage1",
"name": "stage1"
}
},
"id2": {
"status": "status",
"topStage": {
"id": "stage1",
"name": "stage1"
}
},
"id3": {
"status": "status",
"topStage": {
"id": "stage2",
"name": "stage2"
}
}
}
}
]
I am expecting to pass a stage id (at path registrations-> topStage -> id) and return all matching key values.
i have written following query
db.collection.aggregate([
{
$project: {
teams: {
$objectToArray: "$registrations"
},
original: "$$ROOT"
}
},
{
"$project": {
"teams": {
"$filter": {
"input": "$teams",
"as": "team",
"cond": {
"$eq": [
"$$team.v.topStage.id",
"stage1"
]
}
}
}
}
},
{
"$project": {
"registrations": {
"$arrayToObject": "$teams"
}
}
}
])
It does return me right values
for stage1 as stage id
[
{
"_id": ObjectId("5a934e000102030405000000"),
"registrations": {
"id1": {
"status": "status",
"topStage": {
"id": "stage1",
"name": "stage1"
}
},
"id2": {
"status": "status",
"topStage": {
"id": "stage1",
"name": "stage1"
}
}
}
}
]
and for stage2 as stage id, it returns
[
{
"_id": ObjectId("5a934e000102030405000000"),
"registrations": {
"id3": {
"status": "status",
"topStage": {
"id": "stage2",
"name": "stage2"
}
}
}
}
]
Can someone let me know if this is the best way to write this query or this can be simplified ??
It's the correct way to do it but there will be performance impact in the following cases.
If you don't have any other match condition against the indices
if you have a match condition and it matches few docs where registrations has more objects
Other best option you could do is that altering the schema.
you can keep registrations.id1 as registrations : { id:1, status_id: 2}
or you could alter the way such that it will not need to use objectToArray on larger set
if your data is huge, I would recommend to add an index on nested status Id field.
And mongo documentation itself suggests to evaluate multiple schemas for any data to get the best out of it.

MongoDb regex search in array objects

I have the following collection:
{
"invoice": {
"data": [{
"name": "VOUCHERNUMBER",
"value": "59302311"
}, {
"name": "VOUCHERDATE",
"value": "2020-02-20"
}
]
}
},
{
"invoice": {
"data": [{
"name": "VOUCHERNUMBER",
"value": "59112389"
}, {
"name": "VOUCHERDATE",
"value": "2020-02-20"
}
]
}
},
{
"invoice": {
"data": [{
"name": "VOUCHERNUMBER",
"value": "59302378"
}, {
"name": "VOUCHERDATE",
"value": "2020-02-11"
}
]
}
}
My task is to build a query that find all invoices which invoicenumbers includes "11" (or any other substring).
So I built the following statement:
{"invoice.data.name": "VOUCHERNUMBER", "invoice.data.value": {$regex : "11"} }
I'm expecting a result of the first two objects, but because of the second value in the third object, mongodb returns me all three objects. Then I tried
{$and : [{"invoice.data.name": "VOUCHERNUMBER"}, {"invoice.data.value": {$regex : "11"}}]}
with the same result ...
So I'm running out of ideas. Is there a solution to search for the string only in the value field where the corresponding "name" field contains "VOUCHERNUMBER"?
You need $elemMatch.
The $elemMatch operator matches documents that contain an array field with at least one element that matches all the specified query criteria.
db.collection.find({
"invoice.data": {
"$elemMatch": {
"name": "VOUCHERNUMBER",
"value": {
$regex: "11"
}
}
}
})
Sample Mongo Playground

Return only matching subdocument from array of subdocuments

I'm trying to return only the subdocument that has a matching e-mail. I've done what the documentation says here, I believe. Here's what I'm trying:
function lookupReferral(email) {
return getConnection().then(db => db.collection('Referrals').findOne(
{
emails: {$elemMatch: {name: email}}
},
{
"emails.$": 1
}
));
}
Here's an example of a document (I cut down the emails array for brevity):
{
"_id": {
"$oid": "5b65979d84b8942e04f4e346"
},
"accountCode": "auth0|5b4de18d8bed60110409ded5",
"accountEmail": "abc#gmail.com",
"emails": [
{
"name": "a#ddd.dpp",
"signedUp": false,
"updated": {
"$date": "2018-08-04T12:10:05.752Z"
}
},
{
"name": "a#ddd.dpp",
"signedUp": false,
"updated": {
"$date": "2018-08-04T12:10:05.752Z"
}
}
],
"created": {
"$date": "2018-08-04T12:10:05.985Z"
},
"updated": {
"$date": "2018-08-04T12:10:05.985Z"
}
For some reason it returns null (meaning it doesn't exists I guess?), but if I remove what I specifically want, then I get the entire document returned.
You can try this
db.collection.find({
emails: {
$elemMatch: {
name: "a#ddd.dpp"
}
}
},
{
emails: {
$elemMatch: {
name: "a#ddd.dpp"
}
}
})
See the example

Get Distinct Document by Max Value of a Field

I have a requirement where i should query on two fields out of which one is unique field and one is maximum field.
Here is my sample collection
{
"_id": ObjectId('59537b7fe08062b9ee8dfdf6'),
"admin": {
"model": "abc",
"version": "00",
"name":"john",
"age":"30"
}
}
{
"_id": ObjectId('59537b7fe08062b9ee8dfdf7'),
"admin": {
"model": "abc",
"version": "01" ,
"name":"john",
"age":"30"
}
}
{
"_id": ObjectId('59537b7fe08062b9ee8dfdf8'),
"admin": {
"model": "def",
"version": "00" ,
"name":"cena",
"age":"30"
}
}
I have two same models with different versions.I want to query for model with maximum version. I tried by simply sorting the version it does not work for me.
I am expecting output like this
{
"_id": ObjectId('59537b7fe08062b9ee8dfdf7'),
"admin": {
"model": "abc",
"version": "01" ,
"name":"john",
"age":"30"
}
}
{
"_id": ObjectId('59537b7fe08062b9ee8dfdf8'),
"admin": {
"model": "def",
"version": "00" ,
"name":"cena",
"age":"30"
}
}
Any suggestions will be really helpful.
As Neil said, it is $sort, $group, and $replaceRoot, but with correct values in the query:
db.collection.aggregate([
{ "$sort": { "admin.version": -1 } },
{ "$group": {
"_id": "$admin.model" ,
"admin": { "$first": "$$ROOT" }
}},
{ "$replaceRoot": { "newRoot": "$admin" } }
])

How to project only what was found in Mongo when searching at an arbitrary depth?

I have a similar problem to this question, which is searching at an arbitrary depth.
However, I want to retrieve only what was actually found and the hierarchy above it, not the entire document which looks like the default behaviour of find()
If you specify no projection, the db.collection.find() method returns all fields of all documents that match the query.
For example, I have an object that look like this*:
{
"_id": 1,
"template": {
"a": {
"node1": {
"options": {
"configuration": "true"
}
},
"node2": {
"options": {
"configuration": "false"
},
"a":{
"node1": {
"options": {
"configuration": "false"
}
}
},
"b":{}
},
"node3": {
"options": {
"configuration": "false"
},
"a":{
"node1": {
"options": {
"configuration": "true"
}
}
},
"b":{}
}
},
"b": {
"node1": {
"options": {
"configuration": "true"
},
"a":{
"node1": {
"options": {
"configuration": "true"
}
}
},
"b":{
"node1": {
"options": {
"configuration": "false"
}
}
}
}
}
}
}
And I'm trying to find any match where configuration = false, and expect to retrieve only the match and everything above it like:
{
"_id": 1,
"template": {
"a": {
"node2": {
"options": {
"configuration": "false"
},
"a": {
"node1": {
"options": {
"configuration": "false"
}
}
}
},
"node3": {
"options": {
"configuration": "false"
}
}
},
"b": {
"node1": {
"options": {
"configuration": "true" <-- still get this as it has a matching child
},
"b": {
"node1": {
"options": {
"configuration": "false" <-- Matches here
}
}
}
}
}
}
}
I had a look at $project, but I just don't know how to use it in this case.
OBS:
* Before you say anything about how the data is structured, I understand that is does not look like a good design without any context, but similar to the question I've mentioned this data is also got from some sort of XML importation (kind of) and anyway it's already attend our needs, and there is no way to change it.
You can use the positional operator '$' as in:
db.students.find( { grades: { $elemMatch: {
mean: { $gt: 70 },
grade: { $gt:90 }
} } },
{ "grades.$": 1 } )
But that's only on one level.
There's no big advantage of doing this on the server side over doing it on the client side. The hardest part of the operation is the search itself, so that should be as optimal as possible. For the projection, MongoDB only implemented some basic requirements, and leaves more complicated stuff to the client.
For optimization, we did a separate query with a separate index at each depth:
db.brand.ensureIndex({products.navigations.subNavigations._id : 1}, {unique : true, sparse: true, name : "unique.subnavigation1.id"})
db.brand.ensureIndex({products.navigations.subNavigations.subNavigations._id : 1}, {unique : true, sparse: true, name : "unique.subnavigation2.id"})
db.brand.ensureIndex({products.navigations.subNavigations.subNavigations.subNavigations._id : 1}, {unique : true, sparse: true, name : "unique.subnavigation3.id"})