Mongodb Aggregation : Project an array without the last element - mongodb

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.

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 match all conditions with a single array element

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

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.

Create new properties based on embedded array elements in MongoDB

I have this kind of document
{
"_id" : ObjectId("573342930348ce88ff1685f3"),
"presences" : [
{
"_id" : ObjectId("573342930348ce88ff1685f2"),
"createdAt" : NumberLong(1458751869000)
},
{
"_id" : ObjectId("573342930348ce88ff1685f5"),
"createdAt" : NumberLong(1458751885000)
},
{
"_id" : ObjectId("573342930348ce88ff1685f7"),
"createdAt" : NumberLong(1458751894000)
}
]
}
How can I extract first and last presences element to new properties firstPresence and lastPresence like this?
{
"_id" : ObjectId("573342930348ce88ff1685f3"),
"firstPresence": {
"_id" : ObjectId("573342930348ce88ff1685f2"),
"createdAt" : NumberLong(1458751869000)
},
"lastPresence": {
"_id" : ObjectId("573342930348ce88ff1685f7"),
"createdAt" : NumberLong(1458751894000)
},
"presences" : [
...
]
}
I want to use a query that can be applied to all documents in one time.
You need to $unwind your presences array to do the aggregation. Before grouping you can sort them by createdAt to utilize $first and $last operators.
db.collection.aggregate(
[
{ $unwind: "$presences" },
{ $sort: { "presences.createdAt": 1 } },
{
$group: {
_id: "$_id",
"presences": { $push: "$presences" },
"lastPresence": { $last: "$presences" },
"firstPresence": { $first: "$presences" },
}
},
{ $out : "collection" }
])
Last aggregation pipeline ($out) will replace existing collection.
According to above mentioned description as a solution to it please try executing following aggregate query into MongoDB shell
db.collection.aggregate(
// Pipeline
[
// Stage 1
{
$project: {
first: {
$arrayElemAt: ["$presences", 0]
},
last: {
$arrayElemAt: ["$presences", -1]
},
presences: 1
}
},
]
);

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"
}
})