mongodb match all conditions with a single array element - mongodb

I have an array of tests like this,
{
"tests" : [
{
"testId" : "1",
"details" : {
"status" : "Completed"
}
},
{
"testId" : "2",
"details" : {
"status" : "InProgress"
}
},
{
"testId" : "3",
"details" : {
"status" : "Completed"
}
},
]
}
I want to search for complete tests with id as 1 or 2.
So I wrote a match query like this
{
$match : { $and : [ { "tests.testId" : { $in : [1, 2] } }
,{ "tests.details.status" : "Completed" }
]
}
}
The problem with this query is it fetches all documents with Ids as 1 or 2 even though the status is not Completed. For example the above query fetches these two documents
{
"testId" : "1",
"details" : {
"status" : "Completed"
}
},
{
"testId" : "2",
"details" : {
"status" : "InProgress"
}
},
What I need is to the query to search for a parituclat array element for both the condition.
I also tried with $elemMatch. It still doesn't work. Any help is much appreciated.

We can use aggregate pipeline with $filter to return the matched array elements only
try this
db.collection.aggregate([
{
$match: { // to filter the documents
$and: [
{
"tests.testId": { $in: ["1", "2"] }
},
{
"tests.details.status": "Completed"
}
]
}
},
{
$project: { // then use this $project stage to use the $filter operator in it
tests: {
$filter: { // this is to filter the tests array
input: "$tests",
as: "test",
cond: {
$and: [
{
$in: [
"$$test.testId", ["1", "2"]
]
},
{
$eq: [
"$$test.details.status",
"Completed"
]
}
]
}
}
}
}
}
])
you can test it here Mongo Playground
hope it helps
Note, If testId is of type ObjectId, then you have to use ObjectIds not strings in the aggregate pipeline

Related

MongoDb $filter condition to retrieve documents with redundant values in array

I have a MongoDb collection as below :
Each document has a "bricks" array inside "currentVersion".
Now for few of Bricks array , there are some redundant brick ids.
I need to retrieve all the documents which are having redundant brick Ids.
{
name: "page1",
"currentVersion" : {
"bricks" : [
{
"id" : 1.0
},
{
"id" : 2.0
}
]
}
}
{
name: "page2",
"currentVersion" : {
"bricks" : [
{
"id" : 13.0
},
{
"id" : 13.0
}
]
}
}
{
name: "page3",
"currentVersion" : {
"bricks" : [
{
"id" : 20.0
},
{
"id" : 30.0
}
]
}
}
{
"name" : "page4",
"currentVersion" : {
"bricks" : [
{
"id" : 50.0
},
{
"id" : 50.0
},
{
"id" : 70.0
}
]
}
}
My approach is below:
I'm creating two arrays - 'origValuesSize' and 'allValuesSize'
If these two values are not equal , then that is the document with redundant values
Can you please suggest how I could filter the documents based on above condition.
Need help in the below piece of code
db.pages.find({},db.aggregate(
[
{ $project: {
origValuesSize: { $size: {$concatArrays : [ "$currentVersion.bricks.id" ]} },
allValuesSize: {$size: {$setUnion : [ "$currentVersion.bricks.id" ]}},
$filter: --> Need Help Here to compare the above two sizes and filter accordingly
}}
]
))
Edit : 1
My Expected Result would be as below:
I need to display only those Documents which are having redundant brick Ids in "bricks" array .
{
name: "page2",
"currentVersion" : {
"bricks" : [
{
"id" : 13.0
},
{
"id" : 13.0
}
]
}
}
{
"name" : "page4",
"currentVersion" : {
"bricks" : [
{
"id" : 50.0
},
{
"id" : 50.0
},
{
"id" : 70.0
}
]
}
}
Thanks in advance,
You can use expression with aggregation operator in match stage,
$size to get total elements in currentVersion.bricks
$setUnion to get total uniue ids in currentVersion.bricks.id, and $size tot get total elements
$ne to match both size should not same
db.pages.aggregate([
{
$match: {
$expr: {
$ne: [
{ $size: "$currentVersion.bricks" },
{ $size: { $setUnion: "$currentVersion.bricks.id" } }
]
}
}
}
])
Playground
MongoDB +3.2,
$project to show required fields
get size of both and match $eq conditions, it will return true when both same otherwise false
$match isEqual is false
db.pages.aggregate([
{
$project: {
name: 1,
currentVersion: 1,
isEqual: {
$eq: [
{ $size: "$currentVersion.bricks" },
{ $size: { $setUnion: "$currentVersion.bricks.id" } }
]
}
}
},
{ $match: { isEqual: false } }
])
Playground

MongoDB: projection $ when find document into nested arrays

I have the following document of collection "user" than contains two nested arrays:
{
"person" : {
"personId" : 78,
"firstName" : "Mario",
"surname1" : "LOPEZ",
"surname2" : "SEGOVIA"
},
"accounts" : [
{
"accountId" : 42,
"accountRegisterDate" : "2018-01-04",
"banks" : [
{
"bankId" : 1,
"name" : "Bank LTD",
},
{
"bankId" : 2,
"name" : "Bank 2 Corp",
}
]
},
{
"accountId" : 43,
"accountRegisterDate" : "2018-01-04",
"banks" : [
{
"bankId" : 3,
"name" : "Another Bank",
},
{
"bankId" : 4,
"name" : "BCT bank",
}
]
}
]
}
I'm trying to get a query that will find this document and get only this subdocument at output:
{
"bankId" : 3,
"name" : "Another Bank",
}
I'm getting really stucked. If I run this query:
{ "accounts.banks.bankId": "3" }
Gets the whole document. And I've trying combinations of projection with no success:
{"accounts.banks.0.$": 1} //returns two elements of array "banks"
{"accounts.banks.0": 1} //empty bank array
Maybe that's not the way to query for this and I'm going in bad direction.
Can you please help me?
You can try following solution:
db.user.aggregate([
{ $unwind: "$accounts" },
{ $match: { "accounts.banks.bankId": 3 } },
{
$project: {
items: {
$filter: {
input: "$accounts.banks",
as: "bank",
cond: { $eq: [ "$$bank.bankId", 3 ] }
}
}
}
},
{
$replaceRoot : {
newRoot: { $arrayElemAt: [ "$items", 0 ] }
}
}
])
To be able to filter accounts by bankId you need to $unwind them. Then you can match accounts to the one having bankId equal to 3. Since banks is another nested array, you can filter it using $filter operator. This will give you one element nested in items array. To get rid of the nesting you can use $replaceRoot with $arrayElemAt.

Mongodb Aggregation : Project an array without the last element

Using aggregation pipeline in spring Data, I have documents with nested arrays, and I want to project an array except the last item of it.
for example for each document like :
{
"_id" : ObjectId("59ce411c2708c97154d13150"),
"doc1" : [
{
"nodeValue" : "AAA"
},
{
"nodeValue" : "BBB"
},
{
"nodeValue" : "CCC"
},
{
"nodeValue" : "DDD"
}
],
"field2" : 20170102,
"field3" : 4,
}
I want as result :
{
"_id" : ObjectId("59ce411c2708c97154d13150"),
"doc1" : [
{
"nodeValue" : "AAA"
},
{
"nodeValue" : "BBB"
},
{
"nodeValue" : "CCC"
},
{
"nodeValue" : "DDD"
}
],
"doc1_without_last" : [
{
"nodeValue" : "AAA"
},
{
"nodeValue" : "BBB"
},
{
"nodeValue" : "CCC"
}
],
"field2" : 20170102,
"field3" : 4,
}
I tried something like this, but I didn't find a operator that can $pop the array and remove the last item from it.
Aggregation agg = Aggregation.newAggregation(
Aggregation.project()
.andInclude("doc1","field2","field3"),
Aggregation.project().and(ArrayOperators.arrayOf("doc1")..).as("doc1_without_last")
new OutOperation("newCollection")
).withOptions(Aggregation.newAggregationOptions().allowDiskUse(true).build());
Thank you for your help
The shell query can be like this:
db.collection.aggregate([
{ $project: {
doc1: 1,
doc1_without_last: { $slice: [
"$doc1",
{ $subtract: [ { $size: "$doc1" }, 1 ] }
] }
} }
])
Docs:
$size
$slice
$subtract
If there are any limitations in spring that don't let you build such query, you may employ $let to use query variables:
db.collection.aggregate([
{ $project: {
doc1: 1,
doc1_without_last: { $let: {
vars: { size: { $subtract: [ { $size: "$doc1" }, 1 ] } },
in: { $slice: [ "$letters", "$$size"] }
} }
} }
])
Finally, After Blowing my mind, the trick was simply that :
Aggregation agg = Aggregation.newAggregation(
Aggregation.project()
.andInclude("doc1","field1","field2")
.andExpression("slice(doc1, size(doc1)-1)").as("doc1_without_last"),
new OutOperation("newCollection")
).withOptions(Aggregation.newAggregationOptions().allowDiskUse(true).build());
the andExpression in projections serve to include some mongo operators in a String format.

Mongodb $nin querying sub-document

Here is the sample document of my MongoDB:
user: {
_id:1,
name:'xyz',
age:12,
mobile:21321312,
transaction:[
{
trans_id:1,
prod:'a',
purchasedAt:ISODate("2015-02-01"),
},
{
trans_id:2,
prod:'b',
purchasedAt:ISODate("2015-02-01")
},
{
trans_id:3,
prod:'c',
purchasedAt:ISODate("2014-11-24")
}
]
}
I want to get the users who have purchased product 'a' on date '2015-02-01' but not have purchased the product 'b' or 'c' on same day. So I tried querying:
db.user.find({transaction:{$elemMatch:{prod:'a',purchasedAt:ISODate("2015-02-01")}}, transaction:{$elemMatch:{prod:{$nin:['b', 'c']}, purchasedAt:ISODate("2015-02-01")}}})
But the query seems to return wrong result. It contains some users who have purchased product 'c' on the same day. So I tried:
db.user.find({transaction:{$elemMatch:{prod:{$in:['a'], $nin:['b','c']}, purchasedAt:ISODate("2015-02-01")}}})
db.user.find({$and:[{transaction:{$elemMatch:{prod:'a',purchasedAt:ISODate("2015-02-01")}}}, {transaction:{$elemMatch:{prod:{$nin:['b', 'c']}, purchasedAt:ISODate("2015-02-01")}}}]})
But none seems to work. I always get the product with same purchased date which is in $nin part. I have tried other queries also but those are of same kind as above (like querying with dot '.' operator) and are trivial to mention here. Is there any way to get the result I want?
Try this:
db.test.find({
"$and" : [
{ "transaction" : {
"$elemMatch" : { "prod" : "a", "purchasedAt" : ISODate("2015-02-01") }
} },
{ "transaction" : { "$not" : {
"$elemMatch" : { "prod" : "b", "purchasedAt" : ISODate("2015-02-01") }
} } },
{ "transaction" : { "$not" : {
"$elemMatch" : { "prod" : "c", "purchasedAt" : ISODate("2015-02-01") }
} } }
]
})
Following two ways you can find out your results
1> Using mongo aggregation
db.collectionName.aggregate({
"$unwind": "$user.transaction"
}, {
"$match": {
"$and": [{
"user.transaction.prod": "a"
}, {
"user.transaction.prod": {
"$nin": ["b", "c"]
}
}, {
"user.transaction.purchasedAt": ISODate("2015-02-01T00:00:00Z")
}]
}
}).pretty()
2> Using find with projection
db.collectionName.find({
"user.transaction.prod": "a",
"user.transaction.purchasedAt": ISODate("2015-02-01T00:00:00Z")
},
{
"user.transaction.$": 1
}).pretty()

Find exactly match array or having all value of array in MongoDb

I have collection entry like that
[
{
shape : [{id:1,status:true},{id:2,status:false}]
},
{
shape : [{id:1,status:true}]
}
]
I want to fetch data which exactly match array , means contain all ele. of array.
Ex. where shape.id = [1,2] / [ {id: [1,2] } ] (any one is prefer)
then it should return only
[
{
shape : [{id:1,status:true},{id:2,status:false}]
}
]
So help me if is there any native mongodb query .
Thanks
--ND
Here is much simpler query;
db.shapes.find({'shape.id':{$all:[1,2]},shape:{$size:2}});
If mongo documents as below
{
"_id" : ObjectId("54eeb68c8716ec70106ee33b"),
"shapeSize" : [
{
"shape" : [
{
"id" : 1,
"status" : true
},
{
"id" : 2,
"status" : false
}
]
},
{
"shape" : [
{
"id" : 1,
"status" : true
}
]
}
]
}
Then used below aggregation to match the criteria
db.collectionName.aggregate({
"$unwind": "$shapeSize"
}, {
"$match": {
"$and": [{
"shapeSize.shape.id": 2
}, {
"shapeSize.shape.id": 1
}]
}
}, {
"$project": {
"_id": 0,
"shape": "$shapeSize.shape"
}
})