Or statement returns the first result only (should be 2) - mongodb

I have an OR statement where the expected result should return 2 documents but the return result is only 1 document. I have a list of cars docs:
[
{
"_id": ObjectId("60a2c0621e5f043b735e36ef"),
"car_id": 78,
"terminal": "JFK",
"timestamp": ISODate("2020-01-01T17:00:00.000Z"),
},
{
"_id": ObjectId("60a2c0621e5f043b735e36f0"),
"car_id": 78,
"terminal": "LAX",
"timestamp": ISODate("2020-02-08T17:00:00.000Z"),
},
{
"_id": ObjectId("60a2c0621e5f043b735e36f1"),
"car_id": 78,
"terminal": "ORD",
"timestamp": ISODate("2020-03-01T17:00:00.000Z"),
},
]
and my query asks for 2 instances with SAME CAR ID but with DIFFERENT timestamp:
db.collection.aggregate([
{
"$match": {
"$or": [
{
"car_id": 78,
"timestamp": {
"$lte": ISODate("2020-02-15T05:00:11.000Z")
}
},
{
"car_id": 78,
"timestamp": {
"$lte": ISODate("2020-03-02T11:07:27.000Z")
}
}
]
}
},
{
"$sort": {
"timestamp": 1
}
},
{
"$group": {
"_id": "$car_id",
"last": {
"$last": "$$ROOT"
}
}
}
])
So I expect to get 2 results (for each request).
Expected result:
[
{
"_id": 78,
"last": {
"_id": ObjectId("60a2c0621e5f043b735e36f1"),
"car_id": 78,
"terminal": "ORD",
"timestamp": ISODate("2020-03-01T17:00:00Z")
}
},
{
"_id": 78,
"last": {
"_id": ObjectId("60a2c0621e5f043b735e36f0"),
"car_id": 78,
"terminal": "LAX",
"timestamp": ISODate("2020-02-08T17:00:00Z")
}
}
]
But I only get the first result. How can I get the desired 2 results?
mongoplayground

Because all of the returned docs has the same car_id and you use car_id in $group stage so they will be grouped into one result. To get your expected result, you can add a field depend on timestamp of the doc then use that filed in $group:
db.collection.aggregate([
{
"$match": {
"$or": [
{
"car_id": 78,
"timestamp": {
"$lte": ISODate("2020-02-15T05:00:11.000Z")
}
},
{
"car_id": 78,
"timestamp": {
"$lte": ISODate("2020-03-02T11:07:27.000Z")
}
}
]
}
},
{
"$sort": {
"timestamp": 1
}
},
{
$addFields: {
group: {
$cond: {
if: {
$lte: [
"$timestamp",
ISODate("2020-02-15T05:00:11.000Z")
]
},
then: 1,
else: 2
}
}
}
},
{
"$group": {
"_id": "$group",
"last": {
"$last": "$$ROOT"
}
}
}
])
Mongoplayground
Note: If you have more than one car_id in the result, you can change the $group to:
{
"$group": {
"_id": {
car_id: "$car_id",
group: "$group",
},
"last": {
"$last": "$$ROOT"
}
}
}
Mongoplayground

Related

Mongo aggregate on array objects using count

I have a collection with documents in below format: (shown below 2 sample document)
1st doc:
{
"date": 20221101,
"time":1500,
"productCode": "toycar",
"purchaseHistory": [
{
"clientid": 123,
"status": "SUCCESS"
},
{
"clientid": 456,
"status": "FAILURE"
}
]
}
2nd doc:
{
"date": 20221101,
"time": 1500,
"productCode": "toycar",
"purchaseHistory": [
{
"clientid": 890,
"status": "SUCCESS"
},
{
"clientid": 678,
"status": "SUCCESS"
}
]
}
I want to query above and print output in below format where purchaseHistory.status = 'SUCCESS' and date = 20221101:
{productCode:"toycar", "time": 1500, "docCount": 2, "purchaseHistCount":3}
How can I achieve this?
I tried below:
db.products.aggregate({
$match : {date:20221101, 'purchaseHistory.status':'SUCCESS'},
"$group": {
"_id": {
"pc": "$productCode",
"time": "$time"
},
"docCount": {$sum :1}
}
})
Something like this maybe:
db.collection.aggregate([
{
$match: {
date: 20221101,
"purchaseHistory.status": "SUCCESS"
}
},
{
"$addFields": {
"purchaseHistory": {
"$filter": {
"input": "$purchaseHistory",
"as": "ph",
"cond": {
$eq: [
"$$ph.status",
"SUCCESS"
]
}
}
}
}
},
{
$group: {
_id: {
t: "$time",
pc: "$productCode"
},
docCount: {
$sum: 1
},
purchaseHistCount: {
$sum: {
$size: "$purchaseHistory"
}
}
}
}
])
Explained:
Filter the matched documents.
Filter the purchaseHistory SUCCESS only.
Group the result to see count of matching documents & matching purchaseHistory.
Playground

How to add a field to my projected result that is identical to the argument I sent

MY MONGOPLAY
How can I add a field to my result that is identical to the argument I initially sent. Note in my or statement I'm sending a date and I wish to add this date to my results (example requested_by).
I know that '$addFields' would do the trick but couldnt figure out how to integrate it.
My Query:
db.collection.aggregate([
{
"$match": {
"$or": [
{
"car_id": 78,
"timestamp": {
"$lte": ISODate("2020-02-09T00:00:00.000Z") //NEED THIS PARAMETER in my RESULT
}
},
{
"car_id": 79,
"timestamp": {
"$lte": ISODate("2020-03-22T00:00:00.000Z") //NEED THIS PARAMETER in my RESULT
}
}
]
}
},
{
"$sort": {
"timestamp": 1
}
},
{
"$group": {
"_id": "$car_id",
"last": {
"$last": "$$ROOT"
}
}
}
] )
expected result:
[
{
"_id": 78,
"last": {
"_id": ObjectId("60a2c0621e5f043b735e36f0"),
"car_id": 78,
"terminal": "LAX",
"timestamp": ISODate("2020-02-08T17:00:00Z"),
"requested_by": ISODate("2020-02-09T00:00:00.000Z") //<--somethign like this
}
},
{
"_id": 79,
"last": {
"_id": ObjectId("60a2c0621e5f043b735e36f3"),
"car_id": 79,
"terminal": "ORD",
"timestamp": ISODate("2020-03-21T17:00:00Z"),
"requested_by": ISODate("2020-03-22T00:00:00.000Z") //<--somethign like this
}
}
]
You can use '$addFields' in your aggregation as following:
db.collection.aggregate([
{
"$match": {
"$or": [
{
"car_id": 78,
"timestamp": {
"$lte": ISODate("2020-02-09T00:00:00.000Z")
}
},
{
"car_id": 79,
"timestamp": {
"$lte": ISODate("2020-03-22T00:00:00.000Z")
}
}
]
}
},
{
"$sort": {
"timestamp": 1
}
},
{
"$addFields": {
"requested_by": "something"
}
},
{
"$group": {
"_id": "$car_id",
"last": {
"$last": "$$ROOT"
}
}
}
])
If your 'requested_by' field should be calculated based on stored value in the object, you can use pipeline operators to calculate its value. For example:
...
{
"$addFields": {
"requested_by": "$timestamp"
}
},
...
or
...
{
"$addFields": {
"requested_by": {
"$add": [
"$car_id",
20
]
}
}
},
...

How to use aggregate and group by two fields

I'm trying to use aggregate group and match to get my data.
This is my code:
itemShell.aggregate(
[
{
$match: { shell_id_in_whareHouse: {$in:shelfIds}}
},
{
$group: {
_id: "$item",
position:{'$last':'$position'},
total: { $sum: "$amount" }
}
}
],
function(err, results) {
if (err) console.log(err)
else {
res.json(results);
}
}
);
This is how itemShell objects looks like:
{item:1313,position:'2A',amount:500},
{item:1313,position:'2A',amount:200},
{item:1414,position:'1A',amount:500},
{item:1414,position:'2A',amount:800},
{item:1313,position:'1A',amount:300}
My problem is that the outcome of results is: (because of $Last accumulator)
[
{_id:1313,position:'1A',total:1000},
{_id:1414,position:'2A',total:1300},
]
My desired outcome should be:
[
{_id:1313,position:'2A',total:700},
{_id:1414,position:'1A',total:500},
{_id:1414,position:'2A',total:800},
{_id:1313,position:'1A',total:300}
]
So it will group only the objects that item number and position string are the same.
any suggestions ?
You're on the right path, you need to group by two fields using $group stage with _id as an object like below:
ItemShell.aggregate([
{
$match: {}
},
{
$group: {
_id: { item: '$item', position: '$position' },
total: { $sum: '$amount' }
}
},
{
$project: {
_id: 0,
item: '$_id.item',
position: '$_id.position',
total: 1
}
}
]);
Output
[
{
"item": 1313,
"position": "1A",
"total": 300
},
{
"item": 1313,
"position": "2A",
"total": 700
},
{
"item": 1414,
"position": "2A",
"total": 800
},
{
"item": 1414,
"position": "1A",
"total": 500
}
]
Here's a mongo playground: https://mongoplayground.net/p/Ne5h9WkzjEm
I think this should work
itemShell.aggregate([
{
"$group": {
"_id": {
"item": "$item",
"positon": "$positon"
},
"items": {
"$first": "$$ROOT"
},
"amount": {
"$sum": "$amount"
}
}
},
{
"$project": {
"item": "$items.item",
"positon": "$items.position",
"amount": 1,
"_id": "$items._id"
}
}
],
function(err, results) {
if (err) console.log(err)
else {
res.json(results);
}
}
);
You can see the results here : https://mongoplayground.net/p/h7aa_so1Cok
Output:
[
{
"_id": ObjectId("5a934e000102030405000000"),
"amount": 1000,
"item": 1313,
"positon": "2A"
},
{
"_id": ObjectId("5a934e000102030405000002"),
"amount": 1300,
"item": 1414,
"positon": "1A"
}
]

How do I group by day/month in mongoDB?

Here's how one document looks like:
{
"login_Id": "c",
"name": "Abhishek Soni",
"location": "BLAHBLAH",
"work": [
{
"date":ISODate("2014-01-01"),
"total_time": 100,
},
{
"date":ISODate("2014-09-02"),
"total_time": 100,
},
{
"date":ISODate("2014-01-01"),
"total_time": 10,
},
]
}
What I want to do is to run a query that'll give an output like this:
{login_Id: 'c', work:{'01' : 110, '02': 100, ... and so on}}
Basically, I just want to group the work part month wise.
This is what I have tried:
db.employees.aggregate([
{
"$project": {
"_id": 0,
"login_Id": 1,
"time": {
"$sum": "$work.total_time"
}
}
},
{
"$group": {
"_id": {
"$dayOfYear": "$work.date"
},
"time": {
"$sum": "$work.total_time"
}
}
}
]);
But it outputs null. If I remove the group clause, I get the total sum (i.e., 210) What's wrong?
You can try below aggregation
db.collection.aggregate([
{ "$unwind": "$work" },
{ "$match": { "work.date": { "$type": "date" }}},
{ "$group": {
"_id": { "date": { "$dayOfMonth": "$work.date" }},
"time": { "$sum": "$work.total_time" },
"login_Id": { "$first": "$login_Id" }
}},
{ "$group": {
"_id": "$login_Id",
"data": {
"$push": {
"k": { "$toString": "$_id.date" },
"v": "$time"
}
}
}},
{ "$project": {
"work": { "$arrayToObject": "$data" },
"_id": 0,
"login_id": "$_id"
}}
])
Output
[
{
"login_id": "c",
"work": {
"1": 110,
"2": 100
}
}
]

Query MongoDB for nested Arrays

Need help for formatting query to find/get values using search parameters with nested Array.
I have an collection as follows
[
{
"_id": "5b3ad55f66479332a0482961",
"timestamp": "2018-06-17T00:30:00.000Z",
"deviceid": "123456",
"values": [
{
"minval": 1,
"minvalues": [
{
"secval": 51,
"secvalues": {
"alt": "300",
"mcc": "404",
"mnc": "46",
"priority": 1
}
},
{
"secval": 52,
"secvalues": {
"alt": "300",
"mcc": "404",
"mnc": "46",
"priority": 1
}
},
{
"secval": 56,
"secvalues": {
"alt": "300",
"mcc": "404",
"mnc": "46",
"priority": 0
}
}
]
}
]
}
]
need the out as follows with search properties as "values.minvalues.secvalues.priority"
[
{
"_id": "5b3ad55f66479332a0482961",
"timestamp": "2018-06-17T00:30:00.000Z",
"deviceid": "123456",
"values": [
{
"minval": 1,
"minvalues": [
{
"secval": 56,
"secvalues": {
"alt": "300",
"mcc": "404",
"mnc": "46",
"priority": 0
}
}
]
}
]
}
]
I tried the following query but with out success
dbRetval.db('ls_gpsdatabase').collection('gpsevent').aggregate([
{ "$match": { "deviceid": { "$in": idList}}},
{ "$sort": { "_id": -1} },
{"$unwind":"$values.minvalues.secvalues"},
//{"$project":{"deviceid":1,"values.minvalues.secvalues.lat":1,"values.minvalues.secvalues.min":1}} ,
{ "$match": { "values.minvalues.secvalues.priority": { "$eq": 1}}},
{ "$group": { "_id": "$deviceid" , "doc": { "$push": "$values.minvalues.secvalues" }}} ]).toArray();
If any can help that would be great full.
You can use $addFields to replace existing field. Since you have two levels of nested arrays you can use $map for outer and $filter for inner to check your condition:
db.col.aggregate([
{
$match: {
"_id": "5b3ad55f66479332a0482961",
"timestamp": "2018-06-17T00:30:00.000Z"
}
},
{
$addFields: {
values: {
$map: {
input: "$values",
as: "value",
in: {
minval: "$$value.minval",
minvalues: {
$filter: {
input: "$$value.minvalues",
as: "minvalue",
cond: {
$eq: [ "$$minvalue.secvalues.priority", 0 ]
}
}
}
}
}
}
}
}
])