Match a specific item in a collection using MongoDB aggregation query - mongodb

In the following document, I'd like to find the table object which has the column matching the criteria
(label is 'abc' or (name is 'abc' and label is empty))
{
"tables": [
{
"name": "table1",
"columns": [
{
"name": "abc",
"label": "xyz"
},
{
"name": "def",
"label": ""
}
]
},
{
"name": "table2",
"columns": [
{
"name": "xxx",
"label": "yyy"
},
{
"name": "zzz",
"label": "abc"
}
]
}
]
}
The expected answer is table2 object.
The following query returns both table1 and table2. I think the 'match' clause is applied to the entire column array not column by column. I'd like to get the table object as the final result So unwinding at column level is not an option.
db.getCollection('test').aggregate(
{$unwind : "$tables"},
{"$match":
{"$or":
[
{"tables.columns.label": "abc"},
{"tables.columns.name":"abc", "tables.columns.label": ""}
]
}
}
)
How do I match column by column?

You should use the $elemMatch operator:
db.getCollection('test').aggregate(
{
$unwind: "$tables"
},
{
$match:
{
"$or":
[
{
"tables.columns.label": "abc"
},
{
"tables.columns":
{
$elemMatch:
{
"name": "abc",
"label": ""
}
}
}
]
}
}
)

Related

Mongodb, filter nested array of objects

I am new to MongoDB and need some help with building a query.
I have a collection of documents like this:
{
"userId": "AAAA",
"settings": [
{
"key": "theme",
"value": "Dark"
},
{
"key": "btnColor",
"value": "blue"
},
{
"key": "otherSetting",
"value": "otherValue"
}
]
}
I have an array of keys (passing to the server), like [theme, btnColor, someNonExistingKey]
And I need to get a document by userId with settings which keys are inside the array of keys, so I need to get that:
{
"userId": "AAAA",
"settings": [
{
"key": "theme",
"value": "Dark"
},
{
"key": "btnColor",
"value": "blue"
}
]
}
Can anyone please help me with correct query?
$match - Filter the document by userId and settings.key in provided input array.
$project - Decorate output documents. For settings field, filter the document by key is existed in provided input array.
db.collection.aggregate([
{
$match: {
userId: "AAAA",
"settings.key": {
$in: [
"theme",
"btnColor",
"abc"
]
}
}
},
{
$project: {
"userId": 1,
"settings": {
$filter: {
input: "$settings",
cond: {
$in: [
"$$this.key",
[
"theme",
"btnColor",
"abc"
]
]
}
}
}
}
}
])
Sample Mongo Playground

Delete objects that met a condition inside an array in mongodb

My collection has array "name" with objects inside. I need to remove only those objects inside array where "name.x" is blank.
"name": [
{
"name.x": [
{
"_id": "607e7fcca57aa56e2a06b57b",
"name": "abc",
"type": "123"
}
],
"_id": {
"$oid": "62232cd70ce38c5007de31e6"
},
"qty": "1.0",
"Unit": "pound,lbs"
},
{
"name.x": [
{
"_id": "607e7fcca57aa56e2a06b430",
"name": "xyz",
"type": "123"
}
],
"_id": {
"$oid": "62232cd70ce38c5007de31e7"
},
"qty": "1.0",
"Unit": "pound,lbs"
},{
"name.x": []
,
"_id": {
"$oid": "62232cd70ce38c5007de31e7"
},
"qty": "1.0",
"Unit": "pound,lbs"
}
I tried to get all the ids where name.x is blank using python and used $pull to remove objects base on those ids.But the complete array got deleted.How can I remove the objects that meet the condition.
Think MongoDB update with aggregation pipeline meets your requirement especially to deal with the field name with ..
$set - Update the name array field by $filter name.x field is not an empty array.
db.collection.update({},
[
{
$set: {
name: {
$filter: {
input: "$name",
cond: {
$ne: [
{
$getField: {
field: "name.x",
input: "$$this"
}
},
[]
]
}
}
}
}
}
],
{
multi: true
})
Sample Mongo Playground

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

Mongo Query - Filter on nested array and return documents that do not contain a specific field

I have the following schema:
{
"_id": 0,
"games": {
"gamesList": [
{
"franchiseName": "Tekken",
"genre": "Fighting",
"gamesInFranchise": [
{
"name": "Tekken 7",
"releaseDate": "03/18/2015",
"co-op": true,
"platforms": [
"playstation 3",
"xbox 360"
]
},
{
"name": "Tekken 6",
"releaseDate": "11/26/2007",
"co-op": true
},
{
"name": "Tekken 5",
"releaseDate": "01/01/2004",
"co-op": true
},
]
},
.................
]
}
}
I would like to filter documents based on specific "_id" that do not have the property "platforms". So essentially, the result would ideally look like this:
{
"_id": 0,
"games": {
"gamesList": [
{
"franchiseName": "Tekken",
"genre": "Fighting",
"gamesInFranchise": [
{
"name": "Tekken 6",
"releaseDate": "11/26/2007",
"co-op": true
},
{
"name": "Tekken 5",
"releaseDate": "01/01/2004",
"co-op": true
}
]
}
]
}
}
I tried using aggregation specifically with projection/filter query, but I can't seem to reach "platforms" to check if it exists or not.
The problem is that you have multiple embedded array before reaching platforms.
You have to use the aggregation framework to deal with them.
Here's the query :
db.collection.aggregate([
{
$match: {
_id: 0
}
},
{
$addFields: {
"games.gamesList": {
$map: {
input: "$games.gamesList",
as: "franchise",
in: {
"franchiseName": "$$franchise.franchiseName",
"genre": "$$franchise.genre",
"gamesInFranchise": {
$filter: {
input: "$$franchise.gamesInFranchise",
as: "gameInFranchise",
cond: {
$eq: [
null,
{
$ifNull: [
"$$gameInFranchise.platforms",
null
]
}
]
}
}
}
}
}
}
}
}
])
I used $addFields to keep your other fields safe.
Note that you have to describe whole element in th 'in' field of
'$map' operator.
The first $map operator is relative to the first level array, and $filter to the second level array.
$ifNull is the trick to check if element exists, or return something (here set to null). By checking the equality (or not equality) with null, you can check if the element exists
You can test the query here

How to write the following MongoDB query

Suppose I have the following foobar collection:
{
"foobar": [
{
"_id": "abc",
"history": [
{
"type": "foo",
"timestamp": 123456789.0
},
{
"type": "bar",
"timestamp": 123456789.0
}
]
},
{
"_id": "dfg",
"history": [
{
"type": "baz",
"timestamp": 123456789.0
},
{
"type": "bar",
"timestamp": 123456789.0
}
]
},
{
"_id": "hij",
"history": [
{
"type": "foo",
"timestamp": 123456789.0
},
{
"type": "bar",
"timestamp": 123456789.0
},
{
"type": "foo",
"timestamp": 123456789.0
}
]
}
]
}
How can I query ($gte/$lte) the foobar items based on the timestamp prop inside their history items, but using the timestamp from the item where type: "foo"?
If there is no subdocuments with type equals to foo, the entire document is filtered out, if there is more than one subdocument with type equals to foo then it could match anyone.
You can try below aggregation:
var threshold = 123456788;
db.foobar.aggregate([
{
$addFields: {
foobar: {
$filter: {
input: "$foobar",
as: "doc",
cond: {
$let: {
vars: {
foo: {
$arrayElemAt: [
{
$filter: {
input: "$$doc.history",
as: "history",
cond: {
$eq: [ "$$history.type", "foo" ]
}
}
},
0]
}
},
in: {
$gt: [ "$$foo.timestamp", threshold ]
}
}
}
}
}
}
}
])
$addFields can be used to overwrite existing field (foobar). To filter out all subdocuments not matching your condition you can use $filter (the outer one). For each foobar document you can use $let to define temporary variable foo. You need inner $filter to get all history elements where type is foo and then $arrayElemAt to get first one. Then you just need $gt or $lt to apply your comparison.
For those subdocuments where there's no foo there will be undefined returned which will be then filtered out on $gt stage.