How match nested fields in mongo by using the match clause? - mongodb

I am trying to match nested fields in a document in mongo but my query is not working.
Configuration:
[
{
linenumber: "car",
type: "004",
nested: {
info: {
subinfo: "B"
}
},
},
{
linenumber: "car",
type: "005",
nested: {
info: {
subinfo: "G"
}
},
}
]
query:
db.collection.aggregate([
{
$match: {
$and: [
{
linenumber: "car"
},
{
nested: {
info: {
subinfo: {
$in: [
"B",
"G"
]
}
}
}
}
]
}
},
{
$group: {
_id: null,
types: {
$addToSet: "$type"
}
}
}
])
I am using the mongoplayground to test. Here is the link:
https://mongoplayground.net/p/nND59iO1339
I am getting no documents found.

You are almost there. You need to use dot notation.
play
db.collection.aggregate([
{
$match: {
$and: [
{
linenumber: "car"
},
{
"nested.info.subinfo": { //Dot notation
$in: [
"B",
"G"
]
}
}
]
}
},
{
$group: {
_id: null,
types: {
$addToSet: "$type"
}
}
}
])

Related

How to remove substring of value of a specific field in mongodb?

I have the following mongo document:
{ Name: Peter,
Email: peter#mail.com,
EmailSuffix: peter#mail.com
}
I want to remove the substring before "#" of "EmailSuffix" field in all documents of the collection, so that the result will be:
{ Name: Peter,
Email: peter#mail.com,
EmailSuffix: #mail.com
}
I have tried the following:
db.collection.aggregate( [
{
$match: { EmailSuffix: { $regex: /.+?(?=#)/ } }
},
{
$addFields: {
EmailSuffix: { $split: [ "$EmailSuffix", "?" ] }
}
}
] ).forEach( doc => db.collection.updateMany( { _id: doc._id }, { $set: { EmailSuffix: doc.EmailSuffix[0] } } )`
But it doesn't run.
Use $indexOfCP to locate the first #. Then use $substrCP to do usual substring extraction.
db.collection.update({},
[
{
"$addFields": {
"EmailSuffix": {
"$substrCP": [
"$EmailSuffix",
{
"$indexOfCP": [
"$EmailSuffix",
"#"
]
},
{
"$strLenCP": "$EmailSuffix"
}
]
}
}
}
],
{
multi: true
})
Mongo Playground

How to remove field conditionally mongoodb

I have a collection and its documents look like:
{
_id: ObjectId('111111111122222222223333'),
my_array: [
{
id: ObjectId('777777777788888888889999')
name: 'foo'
},
{
id: ObjectId('77777777778888888888555')
name: 'foo2'
}
//...
]
//more attributes
}
However, some documents have my_array: [{}] (with one element which is an empty array).
How can I add conditionally a projection or remove it?
I have to add it to a mongo pipeline at the end of the query, and I want to get my_array only when it has at least one element which is not an empty object. If there's an empty object remove it.
I tried with $cond and $eq in a projection stage but it is not supported. Any suggestion to solve this?
Suppose you have documents like this with my_array field:
{ "my_array" : [ ] }
{ "my_array" : [ { "a" : 1 } ] } // #(1)
{ "my_array" : null }
{ "some_fld" : "some value" }
{ "my_array" : [ { } ] }
{ "my_array" : [ { "a" : 2 }, { "a" : 3 } ] } // #(2)
And, the following aggregation will filter and the result will have the two documents (1) and (2):
db.collection.aggregate([
{
$match: {
$expr: {
$and: [
{ $eq: [ { $type: "$my_array" }, "array" ] },
{ $gt: [ { $size: "$my_array" }, 0 ] },
{ $ne: [ [{}], "$my_array" ] }
]
}
}
}
])
This also works with a find method:
db.collection.find({
$expr: {
$and: [
{ $eq: [ { $type: "$my_array" }, "array" ] },
{ $gt: [ { $size: "$my_array" }, 0 ] },
{ $ne: [ [{}], "$my_array" ] }
]
}
})
To remove the my_array field, from a document when its empty, then you try this aggregation:
db.collection.aggregate([
{
$addFields: {
my_array: {
$cond: [
{$and: [
{ $eq: [ { $type: "$my_array" }, "array" ] },
{ $gt: [ { $size: "$my_array" }, 0 ] },
{ $ne: [ [{}], "$my_array" ] }
]},
"$my_array",
"$$REMOVE"
]
}
}
}
])
The result:
{ }
{ "my_array" : [ { "a" : 1 } ] }
{ }
{ "a" : 1 }
{ }
{ "my_array" : [ { "a" : 2 }, { "a" : 3 } ] }
You can't do that in a query, however in an aggregations you can add $filter to you pipeline, like so:
db.collection.aggregate([
{
$project: {
my_array: {
$filter: {
input: "$my_array",
as: "elem",
cond: {
$ne: [
{},
"$$elem"
]
}
}
}
}
}
])
Mongo Playground
However unless this is "correct" behavior I suggest you clean up your database, it's much simpler to maintain "proper" structure than to update all your queries everywhere.
You can use this update to remove these objects:
db.collection.update({
"myarray": {}
},
[
{
"$set": {
"my_array": {
$filter: {
input: "$my_array",
as: "elem",
cond: {
$ne: [
{},
"$$elem"
]
}
}
}
}
},
],
{
"multi": false,
"upsert": false
})
Mongo Playground

How to use $addFields in mongo to add elements to just existing documents?

I am trying to add a new field to an existing document by using a combination of both $ifnull and $cond but an empty document is always added at the end.
Configuration:
[
{
line: "car",
number: "1",
category: {
FERRARI: {
color: "blue"
},
LAMBORGHINI: {
color: "red"
}
}
},
{
line: "car",
number: "2",
category: {
FERRARI: {
color: "blue"
}
}
}
]
Query approach:
db.collection.aggregate([
{
$match: {
$and: [
{ line: "car" },
{ number: { $in: ["1", "2"] } }
]
}
},
{
"$addFields": {
"category.LAMBORGHINI.number": {
$cond: [
{ "$ifNull": ["$category.LAMBORGHINI", false] },
"$number",
"$$REMOVE"
]
}
}
},
{
$group: {
_id: null,
CATEGORIES: {
$addToSet: "$category.LAMBORGHINI"
}
}
}
])
Here is the link to the mongo play ground:
https://mongoplayground.net/p/RUnu5BNdnrR
I tried the mentioned query but I still get that ugly empty set added at the end.
$$REMOVE will remove last field/key, from your field category.LAMBORGHINI.number the last field is number that is why it is removing number from the end, you can try another approach,
specify just category.LAMBORGHINI, if condition match then it will return object of current category.LAMBORGHINI and number object after merging using $mergeObjects
{
"$addFields": {
"category.LAMBORGHINI": {
$cond: [
{ "$ifNull": ["$category.LAMBORGHINI", false] },
{
$mergeObjects: [
"$category.LAMBORGHINI",
{ number: "$number" }
]
},
"$$REMOVE"
]
}
}
}
Playground

Aggregate and project with multiples conditions

I have a collection myCollection with array of members :
{
name : String,
members: [{status : Number, memberId : {type: Schema.Types.ObjectId, ref: 'members'}]
}
and i have this data
"_id" : ObjectId("5e83791eb49ab07a48e0282b")
"members" : [
{
"status" : 1,
"_id" : ObjectId("5e83791eb49ab07a48e0282c"),
"memberId" : ObjectId("5e7dbf5b257e6b18a62f2da9")
},
{
"status" : 2,
"_id" : ObjectId("5e837944b49ab07a48e0282d"),
"memberId" : ObjectId("5e7de2dbe027f43adf678db8")
}
],
I want to check by aggregate query if member 5e7dbf5b257e6b18a62f2da9 exists with status 1 but it didn't return true
db.getCollection('myCollection').aggregate([
{$match: {_id: ObjectId("5e83791eb49ab07a48e0282b")}},
{
$project: {
isMember: {
$cond: [
{ $and: [ {$in: [ObjectId("5e7dbf5b257e6b18a62f2da9"), '$members.memberId']}, {$eq: ['$members.status', 1]} ] },
// if
true, // then
false // else
]
}
}
}
])
Thank you for your responses.
If you want to get just true/false you can shortcut like this:
db.collection.aggregate([
{ $match: { _id: ObjectId("5e83791eb49ab07a48e0282b") } },
{
$project: {
isMember: {
$map: {
input: "$members",
in: {
$and: [
{ $eq: [ObjectId("5e7dbf5b257e6b18a62f2da9"), '$$this.memberId'] },
{ $eq: [1, '$$this.status'] }
]
}
}
}
}
},
{ $set: { isMember: { $anyElementTrue: "$isMember" } } }
])
A different style would be this:
db.collection.aggregate([
{ $match: { _id: ObjectId("5e83791eb49ab07a48e0282b") } },
{
$project: {
isMember: {
$map: {
input: "$members",
in: {
$eq: [
{ memberId: ("5e7dbf5b257e6b18a62f2da9"), status: 1 },
{ memberId: "$$this.memberId", status: "$$this.status" }
]
}
}
}
}
},
{ $set: { isMember: { $anyElementTrue: "$isMember" } } }
])

Query datevalue of a inner Array element

Need help with some MongoDB query:
The document I have is below and I am trying to search based on 2 conditions
The meta.tags.code = "ABC"
Its LastSyncDateTime should
meta.extension.value == "" (OR)
the meta.extension.value is less than meta.lastUpdated
Data :
{
"meta" : {
"extension" : [
{
"url" : "LastSyncDateTime",
"value" : "20190206-00:49:25.694"
},
{
"url" : "RetryCount",
"value" : "0"
}
],
"lastUpdate" : "20190207-01:21:41.095",
"tags" : [
{
"code" : "ABC",
"system" : "type"
},
{
"code" : "XYZ",
"system" : "SourceSystem"
}
]
}
}
Query:
db.proc_patients_service.find({
"meta.tags.code": "ABC",
$or: [{
"meta.extension.value": ""
}, {
$expr: { "$lt": [{ "mgfunc": "ISODate", "params": [{ "$arrayElemAt": ["$meta.extension.value", 0] }] }, { "mgfunc": "ISODate", "params": ["$meta.lastUpdate"] }] }
}]
})
But it is only fetching ABC Patients whose LastSyncDateTime is empty and ignores the other condition.
Using MongoDB Aggregation, I have converted your string to date with operator $dateFromString and then compare the value as per your criteria.
db.proc_patients_service.aggregate([
{ $match: { "meta.tags.code": "ABC", } },
{ $unwind: "$meta.extension" },
{
$project: {
'meta.tags': '$meta.tags',
'meta.lastUpdate': { '$dateFromString': { 'dateString': '$meta.lastUpdate', format: "%Y%m%d-%H:%M:%S.%L" } },
'meta.extension.url': '$meta.extension.url',
'meta.extension.value': {
$cond: {
if: { $ne: ["$meta.extension.value", "0"] }, then: { '$dateFromString': { 'dateString': '$meta.extension.value', format: "%Y%m%d-%H:%M:%S.%L" } }, else: 0
}
}
}
},
{
$match: {
$or: [
{ "meta.extension.value": 0 },
{ $expr: { $lt: ["$meta.extension.value", "$meta.lastUpdate"] } }
]
}
},
{
$group: { _id: '_id', 'extension': { $push: '$meta.extension' }, "lastUpdate": { $first: '$meta.lastUpdate' }, 'tags': { $first: '$meta.tags' } }
},
{
$project: { meta: { 'extension': '$extension', lastUpdate: '$lastUpdate', 'tags': '$tags' } }
}
])