Here I have two query for one table. Second query's $match contain result from first query.
db.histories.aggregate([
{$match: {
from: userid,
connectTime: {"$gte":yesterday},
status:'completed'
}},
{$group: {
_id: '$groupId',
groupId: { $last: '$groupId' },
callId:{ $last: '$callId' },
}},
{ $sort: {
connectTime:-1
} }
])
The result of first query contain "callId", using that "callId" I'm aggregating second query.
db.histories.aggregate([
{$match: {
$or: [{ callId: {"$in": groupCallIdArray} }, { connectedCallId: {"$in": groupCallIdArray} }],
status:'completed'
}},
{$group: {
_id: {'groupId':'$groupId'},
from: { $last: '$from' },
to: { $last: '$to' },
minimumTime: { $last: '$minimumTime' },
noOfcalls: {$sum:1},
duration:{ $sum: '$duration' },
}}
])
Do we have any way to merge two queries into a single query.
Input JSON
[{
"_id" : ObjectId("60fe4bf5c0fe3d0017059776"),
"callId" : "CAbfadc16eed3f493f742b208e283848af",
"connectedCallId" : "CA0c61d90d1694ef219b42412246570c63",
"from" : ObjectId("6062f39c9ccebd00178bf302"),
"to" : ObjectId("606d59547db42d00178234a6"),
"callConnectTime" : 2021-07-26 05:44:19.573Z,
"status" : "completed",
"minimumTime" : 15,
"duration" : 24,
"groupId" : ObjectId("60c8908e55242c00170e3e00"),
}]
First QUERY OUTPUT
[
{
_id:60fe4bf5c0fe3d0017059776,
gigsId: 60fe4bf5c0fe3d0017059776,
callSid: 'CAbfadc16eed3f493f742b208e283848af'
}
]
using first queries output I'm creating match query for second query.
groupCallIdArray = ["CAbfadc16eed3f493f742b208e283848af"]
Second Query Input
[{
_id:{groupId:ObjectId("60fe4bf5c0fe3d0017059776")},
from:ObjectId("6062f39c9ccebd00178bf302"),
to:ObjectId("606d59547db42d00178234a6"),
minimumTime:15,
noOfcalls:1,
duration:24,
}...
]
You could use $lookup to pull in the matches.
Something like
db.histories.aggregate([
{$match: {
from: userid,
connectTime: {"$gte":yesterday},
status:'completed'
}},
{ $sort: {
connectTime:-1
} }
{$group: {
_id: '$groupId',
groupId: { $last: '$groupId' },
callId:{ $last: '$callId' },
}},
{$group: {
_id: null,
groupCallIdArray: { $push: '$callId'}
}},
{$lookup: {
from: 'histories',
let: { groupCallIdArray: '$groupCallIdArray'},
pipeline: [
{$match:{
status:'completed',
{$expr: {
$or: [{ callId: {"$in": '$$groupCallIdArray'} }, {
connectedCallId: {"$in": '$$groupCallIdArray'} }]}}
}
},
{$group: {
_id: {'groupId':'$groupId'},
from: { $last: '$from' },
to: { $last: '$to' },
minimumTime: { $last: '$minimumTime' },
noOfcalls: {$sum:1},
duration:{ $sum: '$duration' },
}}
],
as: 'results'
}}
])
Related
This is my user document
{
"_id":"02a33b9a-284c-4869-885e-d46981fdd679",
"context":{
"email":"someemail#gmail.com",
"firstName":"John",
"lastName":"Smith",
"company":[
"e2467c93-114b-4613-a842-f311a8c537b3"
],
},
}
and a company document
{
"_id":"e2467c93-114b-4613-a842-f311a8c537b3",
"context":{
"name":"Coca Cola",
"image":"someimage",
},
};
This is my query for users
let users = await Persons.aggregate(
[{$project:
{
name: {$concat: ['$context.firstName', ' ', '$context.lastName']},
companyId: {$arrayElemAt: ['$context.company', 0]}}
},
{$match: {name: searchRegExp}},
{$lookup: {from: 'companies', let: {company_id: {$arrayElemAt: ['$context.company', 0]}}, pipeline:
[
{
$match: {
$expr: {
$eq: ['$_id', '$$company_id']
}
}
},
{
$project: {name: '$context.name'}
}
],
as: 'company'}}
]).toArray()
When I run this query I get company field as an empty array, what am I doing wrong here?
Your first pipeline stage $project only outputs _id, name and companyId so then when you're trying to refer to $context.company in your $lookup there will be an empty value. You can use $addFields instead:
{
$addFields: {
name: {
$concat: [
"$context.firstName",
" ",
"$context.lastName"
]
},
companyId: {
$arrayElemAt: [
"$context.company",
0
]
}
}
}
Mongo Playground
When you add field companyId: {$arrayElemAt: ['$context.company', 0]}}, then you can use the simple version of $lookup. There is no need to set it twice, once as companyId: ... and in let: {company_id: ...}
db.user.aggregate([
{
$addFields: {
name: { $concat: ["$context.firstName", " ", "$context.lastName"] },
companyId: { $arrayElemAt: ["$context.company", 0] }
}
},
{
$lookup: {
from: "company",
localField: "companyId",
foreignField: "_id",
as: "company"
}
}
])
persons collection:
{
name: "jhon",
msgs: {
chat_one: [
{id: 1234, msg: "hi", date: "18/05/2018"},
{id: 1234, msg: "hello", date: "19/05/2018"}
],
chat_two: [
{id: 1234, msg: "hi", date: "18/05/2018"},
{id: 1234, msg: "hello", date: "19/05/2018"}
]
}
}
How to query using mongoose and get data like below.
sort on date.
And return chat_one messages of a single person.
{
chat_one: [
{id: 1234, msg: "hello", date: "19/05/2018"},
{id: 1234, msg: "hi", date: "18/05/2018"}
]
}
As per your new update and requirement below is the aggregation to retrieve only chat_one messages.
db.collection.aggregate([
{ $match: { name: "jhon" }},
{ $unwind: "$msgs.chat_one" },
{ $sort: { "msgs.chat_one.date": -1 }},
{ $group: { _id: "$name", chat: { $push: "$msgs.chat_one" }}},
{ $project: { _id: 0 }} // To remove `_id`
])
Output:
[{
"chat": [
{ "date": "19/05/2018", "id": 1234, "msg": "hello" },
{ "date": "18/05/2018", "id": 1234, "msg": "hi" }
]
}]
One way to do this is via aggregation like this:
db.collection.aggregate([
{ $match: { name: "jhon" }},
{ $unwind: "$msgs" },
{ $sort: { "msgs.date": -1 }},
{ $group: { _id: "$name", msgs: { $addToSet: "$msgs" }}},
{ $project: { _id: 0 }}
])
You can see it working here
The idea is to $match first on the name then $unwind so we can $sort on the date and then $group and $project to get to the desired output.
Now consider the case , i have one document containing below collection like structure.
Below is the order collection
{
"_id" : ObjectId("5788fcd1d8159c2366dd5d93"),
"color" : "Blue",
"code" : "1",
"category_id" : ObjectId("5693d170a2191f9020b8c815"),
"description" : "julia tried",
"name" : "Order1",
"brand_id" : ObjectId("5b0e52f058b8287a446f9f05")
}
There is also a collection for Brand and Category. This is the
Category collection
{
"_id" : ObjectId("5693d170a2191f9020b8c815"),
"name" : "Category1",
"created_at" : ISODate("2016-01-11T20:32:17.832+0000"),
"updated_at" : ISODate("2016-01-11T20:32:17.832+0000"),
}
Brand Collection
{
"_id" : ObjectId("5b0e52f058b8287a446f9f05"),
"name" : "brand1",
"description" : "brand1",
"updated_at" : ISODate("2017-07-05T09:18:13.951+0000"),
"created_at" : ISODate("2017-07-05T09:18:13.951+0000"),
}
Now after aggregation applied, it should result in below format:
{
'brands': [
{
_id: '*******'
name: 'brand1',
categories: [
{
_id: '*****',
name: 'category_name1',
orders: [
{
_id: '*****',
title: 'order1'
}
]
}
]
}
]
}
You can try below aggregation:
db.brand.aggregate([
{
$lookup: {
from: "order",
localField: "_id",
foreignField: "brand_id",
as: "orders"
}
},
{
$unwind: "$orders"
},
{
$lookup: {
from: "category",
localField: "orders.category_id",
foreignField: "_id",
as: "categories"
}
},
{
$unwind: "$categories"
},
{
$group: {
_id: "$_id",
name: { $first: "$name" },
description: { $first: "$description" },
updated_at: { $first: "$updated_at" },
created_at: { $first: "$created_at" },
categories: { $addToSet: "$categories" },
orders: { $addToSet: "$orders" }
}
},
{
$addFields: {
categories: {
$map: {
input: "$categories",
as: "category",
in: {
$mergeObjects: [
"$$category", {
orders: [ {
$filter: {
input: "$orders",
as: "order",
cond: { $eq: [ "$$category._id", "$$order.category_id" ] }
}
} ]
} ]
}
}
}
}
},
{
$project: {
orders: 0
}
}
])
Basically you have to use $lookup twice to "merge" data from all these collections based on brand_id and category_id fields. Since you expect orders in categories in brands you can use $unwind for both arrays and then use $group to get following shape:
{
"_id" : ObjectId("5b0e52f058b8287a446f9f05"),
"name" : "brand1",
"description" : "brand1",
"updated_at" : ISODate("2017-07-05T09:18:13.951Z"),
"created_at" : ISODate("2017-07-05T09:18:13.951Z"),
"categories" : [
{
"_id" : ObjectId("5693d170a2191f9020b8c814"),
"name" : "Category1",
"created_at" : ISODate("2016-01-11T20:32:17.832Z"),
"updated_at" : ISODate("2016-01-11T20:32:17.832Z")
}
],
"orders" : [
{
"_id" : ObjectId("5788fcd1d8159c2366dd5d93"),
"color" : "Blue",
"code" : "1",
"category_id" : ObjectId("5693d170a2191f9020b8c814"),
"description" : "julia tried",
"name" : "Order1",
"brand_id" : ObjectId("5b0e52f058b8287a446f9f05")
}
]
}
Now you have brand1 with all its subcategories and all orders that should be placed in one of those categories. The only thing is how to "nest" orders in categories. One way to do that might be $map where you can merge each category with all orders that match that category (using $mergeObjects you don't have to specify all properties from categories object).
To match category with orders you can perform $filter on orders array.
Then you can drop orders since those are nested into categories so you don't need them anymore.
EDIT: 3.4 version
In MongoDB 3.4 you can't use $mergeObjects so you should specify all properties for `categories:
db.brand.aggregate([
{
$lookup: {
from: "order",
localField: "_id",
foreignField: "brand_id",
as: "orders"
}
},
{
$unwind: "$orders"
},
{
$lookup: {
from: "category",
localField: "orders.category_id",
foreignField: "_id",
as: "categories"
}
},
{
$unwind: "$categories"
},
{
$group: {
_id: "$_id",
name: { $first: "$name" },
description: { $first: "$description" },
updated_at: { $first: "$updated_at" },
created_at: { $first: "$created_at" },
categories: { $addToSet: "$categories" },
orders: { $addToSet: "$orders" }
}
},
{
$addFields: {
categories: {
$map: {
input: "$categories",
as: "category",
in: {
_id: "$$category._id",
name: "$$category.name",
created_at: "$$category.created_at",
updated_at: "$$category.updated_at",
orders: [
{
$filter: {
input: "$orders",
as: "order",
cond: { $eq: [ "$$category._id", "$$order.category_id" ] }
}
}
]
}
}
}
}
},
{
$project: {
orders: 0
}
}
])
I have a MongoDB Collection that contains timecards. I have a 2nd collection that contains locations. Normally I would do this if I was looking for anyone that was oncall at the time (you have to have a signout for every signin):
TimeCard.aggregate([
{ $match: {agency_id: req.query.agency_id}},
{ $sort: { user_id: 1, received_time: -1 }},
{ $group: { _id: "$user_id", count_types: { $sum: 1 }, lastTime: { $last: "$received_time" }}},
{ $match: { count_types: { $mod: [2, 1] }}},
{ $lookup: { from: "locations", localField: "_id", foreignField: "user_id", as: "Test" }}
], function(err, docs){
if(err){console.log(err);}
else {res.json(docs);}
});
And this would give me this result:
[
{
"_id": "123-88",
"count_types": 5,
"lastTime": "2017-04-20T01:30:18.713Z",
"Test": [
{
"_id": "58fa4021ffa99100116585e0",
"user_id": "123-88",
"agency_id": "44",
"contract_region_id": "contract-region-id-007",
"department_id": "department-id-42",
"shift_id": "shift-id-45",
"unit_id": "unit-id-88",
"received_time": "2017-04-21T17:23:45.672Z",
"location": "Science Lab",
"__v": 0
},
{
"_id": "58fed3efdac1bd00112a914b",
"user_id": "123-88",
"agency_id": "44",
"contract_region_id": "contract-region-id-007",
"department_id": "department-id-42",
"shift_id": "shift-id-45",
"unit_id": "unit-id-88",
"received_time": "2017-04-25T04:43:27.501Z",
"location": "Gym",
"__v": 0
}
]
}
]
Now I could have multiple users in the array all with their own location data. What I want is only the Last location they were at (based on received_time inside Test array). So my best guess is that I would need to first just get a list of user_ids and then call the second collection and pass in the array to get the results but I am not sure how to do this correctly or if that is even the best way to do this. Thanks again for your help.
Use the $filter operator to get the only element in the Test array with the received_time key matching the aggregated lastTime field. You can apply this within an $addFields pipeline step if your MongoDB Server version is 3.4 or greater:
TimeCard.aggregate([
{ $match: {agency_id: req.query.agency_id}},
{ $sort: { user_id: 1, received_time: -1 }},
{ $group: { _id: "$user_id", count_types: { $sum: 1 }, lastTime: { $last: "$received_time" }}},
{ $match: { count_types: { $mod: [2, 1] }}},
{ $lookup: { from: "locations", localField: "_id", foreignField: "user_id", as: "Test" }},
{
$addFields: {
Test: {
$filter: {
input: "$Test",
as: "user",
cond: { $eq: ["$lastTime", "$$user.received_time"] }
}
}
}
}
], function(err, docs){
if(err){console.log(err);}
else {res.json(docs);}
});
If your MongoDB server does not have support for $addFields then use the $project pipeline instead:
TimeCard.aggregate([
{ $match: {agency_id: req.query.agency_id}},
{ $sort: { user_id: 1, received_time: -1 }},
{ $group: { _id: "$user_id", count_types: { $sum: 1 }, lastTime: { $last: "$received_time" }}},
{ $match: { count_types: { $mod: [2, 1] }}},
{ $lookup: { from: "locations", localField: "_id", foreignField: "user_id", as: "Test" }},
{
$project: {
count_types: 1,
lastTime: 1,
Test: {
$filter: {
input: "$Test",
as: "user",
cond: { $eq: ["$lastTime", "$$user.received_time"] }
}
}
}
}
], function(err, docs){
if(err){console.log(err);}
else {res.json(docs);}
});
I'm trying to concatenate two nested arrays (using $concatArrays) into one new field. I'd like to sort the output of the concatenation (Model.timeline) by a property that exists in both sets of objects. I can't seem to get it working with $unwind. Here's the query without any sorting:
Model.aggregate([
{
$match: {
'id': id
}
},
{
$project: {
id: 1,
name: 1,
flagged: 1,
updatedAt: 1,
lastEvent: {
$arrayElemAt: ['$events', -1]
},
lastimage: {
$arrayElemAt: ['$images', -1]
},
timeline: {
$concatArrays: [
{ $filter: {
input: '$events',
as: 'event',
cond: { $and: [
{ $gte: ['$$event.timestamp', startAt] },
{ $lte: ['$$event.timestamp', endAt] }
]}
}},
{ $filter: {
input: '$images',
as: 'image',
cond: { $and: [
{ $gte: ['$$image.timestamp', startAt] },
{ $lte: ['$$image.timestamp', endAt] }
]}
}}
]
}
}
}
]);
Am I missing something obvious?
You need three pipeline stages after your match and project. First $unwind, then $sort and then re $group. Use the $first operator to retain all the fields.
{
$undwind : "$timeline",
},
{
$sort : {"your.sortable.field" : 1}
},
{
$group : {
_id : "$_id",
name : {$first : 1},
flagged : {$first : 1},
updatedAt : {$first : 1},
lastEvent : {$first : 1},
lastimage : {$first : 1},
timeline : {$push : "$timeline"}
}
}
Please note that this will work even when you have more than one document after the match phase. So basically this will sort the elements of an array within each document.
Your $match and $project aggregation stages worked after I substituted id with _id, and filled in the values for id, startAt and endAt like so:
db.items.aggregate([
{
$match: {
'_id': '58'
}
},
{
$project: {
'_id': 1,
name: 1,
flagged: 1,
updatedAt: 1,
lastEvent: {
$arrayElemAt: ['$events', -1]
},
lastimage: {
$arrayElemAt: ['$images', -1]
},
timeline: {
$concatArrays: [
{ $filter: {
input: '$events',
as: 'event',
cond: { $and: [
{ $gte: ['$$event.timestamp', ISODate("2016-01-19T20:15:31Z")] },
{ $lte: ['$$event.timestamp', ISODate("2016-12-01T20:15:31Z")] }
]}
}},
{ $filter: {
input: '$images',
as: 'image',
cond: { $and: [
{ $gte: ['$$image.timestamp', ISODate("2016-01-19T20:15:31Z")] },
{ $lte: ['$$image.timestamp', ISODate("2016-12-01T20:15:31Z")] }
]}
}}
]
}
}
}
]);