I have the following data structures:
playlist collection:
{
_id:123,
artistId:959789,
title:'playlist1',
tracks:[{trackId:123456,createdDate:03.02.2017},
{trackId:213556,createdDate:04.02.2017},
{trackId:956125,createdDate:05.02.2017}]
},
{
_id:456,
artistId:456456,
title:'playlist2',
tracks:[{trackId:956336,createdDate:03.02.2017},
{trackId:213556,createdDate:09.02.2017},
{trackId:785556,createdDate:011.02.2017}]
},
{
_id:456,
artistId:456456,
title:'playlist3',
tracks:[{trackId:636985,createdDate:01.02.2017},
{trackId:456585,createdDate:06.02.2017},
{trackId:785556,createdDate:09.02.2017}]
}
The trackId in the tracks array of a playlist is the _id of a track in track collection
tracks collection:
{_id:956336,title:'abc'},
{_id:785556,title:'cdf'},
{_id:456585,title:'ghi'},
{_id:213556,title:'xyz'},
{_id:636985,title:'lmn'}
What i did was an aggregate $lookup using the trackId in the tracks array and i got the result. But the playlistTracks was sorted in some other order not in the order of the tracks array order.
{
$match: {artistId: 456}
},
{
$lookup: {
from: 'tracks',
localField: 'tracks.trackId',
foreignField: '_id',
as: 'playlistTracks'
}
},
Now what I need is to get the list of playlists by a particular artist having the following structure :
The playlistTracks should be sorted in the order on the createdDate in the tracks array.
{
_id:456,
title:'playlist2',
tracks:[{trackId:636985,createdDate:01.02.2017},
{trackId:456585,createdDate:06.02.2017},
{trackId:785556,createdDate:09.02.2017}]
playlistTracks:[{_id:956336,title:'abc'},
{_id:213556,title:'xyz'},
{_id:785556,title:'cdf'}]
},
{
_id:456,
title:'playlist2',
tracks:[{trackId:636985,createdDate:01.02.2017},
{trackId:456585,createdDate:06.02.2017},
{trackId:785556,createdDate:09.02.2017}]
playlistTracks:[{_id:636985,title:'lmn'},
{_id:456585,title:'ghi'},
{_id:785556,title:'cdf'}]
}
Follow below steps
1 unwind the tracks array in playlist collection
2 $lookup match with tracks collection
3 add createddate of tracks array to lookup result as a new key
4 sort based on new key
5 group the results for your requirements
So these are the documents I added, to reproduce your use case:
Playlist collection
{
"_id" : NumberInt(123),
"artistId" : NumberInt(959789),
"title" : "playlist1",
"tracks" : [
{
"trackId" : NumberInt(123456),
"createdDate" : "03.02.2017"
},
{
"trackId" : NumberInt(213556),
"createdDate" : "04.02.2017"
},
{
"trackId" : NumberInt(956125),
"createdDate" : "05.02.2017"
}
]
}
{
"_id" : NumberInt(456),
"artistId" : NumberInt(456456),
"title" : "playlist2",
"tracks" : [
{
"trackId" : NumberInt(956336),
"createdDate" : "03.02.2017"
},
{
"trackId" : NumberInt(213556),
"createdDate" : "09.02.2017"
},
{
"trackId" : NumberInt(785556),
"createdDate" : "11.02.2017"
}
]
}
{
"_id" : NumberInt(457),
"artistId" : NumberInt(456456),
"title" : "playlist3",
"tracks" : [
{
"trackId" : NumberInt(636985),
"createdDate" : "01.02.2017"
},
{
"trackId" : NumberInt(456585),
"createdDate" : "06.02.2017"
},
{
"trackId" : NumberInt(785556),
"createdDate" : "09.02.2017"
}
]
}
I changed the last duplicate _id on the playlist collection with _id: 457. I don't know how you could have two documents with same _id. _id field has to be unique. And I'm not sure I understand correct your desired result, because in your $match query your write the following: $match: {artistId: 456} but in your data there is no artiseId with 456.
and this date
{trackId:785556,createdDate:011.02.2017}
from document id_ 456 I changed to
{trackId:785556,createdDate:"11.02.2017"}
cause the date looked weird. It also looks like your date fields are strings, cause it certainly doesn't look like a date field. Either way the $sort works for both usecases.
The tracks collection I left as in your example.
So this seems to be what you need?
db.playlist.aggregate([
{
$match: {_id: {$in: [456]}}
},
{ $unwind: "$tracks"},
{$sort: {"tracks.createdDate": 1}},
{
$lookup: {
from: 'tracks',
localField: 'tracks.trackId',
foreignField: '_id',
as: 'playlistTracks'
}
},
{
$group:{
_id: "$_id",
artistId: {$first: "$artistId"},
title: {$first: "$title"},
tracks: { $push: { item: "$tracks.trackId", quantity: "$tracks.createdDate" } },
playlistTracks: { $push: "$playlistTracks" }
}
}
])
This puts both arrays into same order. You can specify here {$sort: {"tracks.createdDate": 1}} if you want ascending or descending -1 order
So before looking up the fields you can unwind and sort you playlist array.
Hope this works
Related
Can someone please help me with this query ??
Query >>> Find all warehouses that keep item "Planner" and having in-stock quantity less than 20
This is the sample document in the items collection of the Inventory database :
{
"_id" : ObjectId("6067640da9a907175caaca34"),
"id" : 101,
"name" : "Planner",
"status" : "A",
"height" : 12,
"tags" : [
"mens",
"womens"
],
"warehouses" : [
{
"name" : "Phoenix",
"quantity" : 25
},
{
"name" : "Quickshift",
"quantity" : 15
},
{
"name" : "Poona",
"quantity" : 10
}
]
}
This is what I have tried doing :
db.items.find({"name":"Planner","warehouses.quantity":{"$lt":20}},{"warehouses":1,"_id":0}).pretty()
But it gives me the result as
{
"warehouses" : [
{
"name" : "Phoenix",
"quantity" : 25
},
{
"name" : "Quickshift",
"quantity" : 15
},
{
"name" : "Poona",
"quantity" : 10
}
]
}
Demo - https://mongoplayground.net/p/IpD5ypWSZyt
Use aggregation query
db.collection.aggregate([
{ $match: { "name": "Planner" } },
{ $unwind: "$warehouses" }, // break into individual documents
{ $match: { "warehouses.quantity": { $lt: 20 } } }, // query the data
{ $group: { _id: "_id", warehouses: { $push: "$warehouses" } } } // join them back
])
Demo - https://mongoplayground.net/p/pdTY0IkIqgF
Use $elemMatch only if you think there will be only 1 array element matching per document
The $elemMatch operator matches documents that contain an array field with at least one element that matches all the specified query criteria.
The $elemMatch operator limits the contents of an field from the query results to contain only the first element matching the $elemMatch condition.
db.collection.find({
"name":"Planner",
"warehouses": { "$elemMatch": { "quantity": { $gt: 20 } } }
},
{ "warehouses.$": 1})
https://docs.mongodb.com/manual/reference/method/db.collection.find/#find-projection
I would like to write a query for summing each field payment of the first object inside an array, for each element of my database.
The schema is the following:
var schema = new Schema({
plate : String,
category : String,
brand : String,
model : String,
sign : String,
tax : [{
date : { type: Date, default: Date.now },
payment : { type: Number, default: 0 },
}],
});
I wrote the following function for my query:
function(callback){
Machine.aggregate(
[
{$unwind: "$tax"},
{$group : {
_id : null ,
tot : { $sum: "$tax.payment"}
}}
]
,callback);
}
But in this way I retrieve the sum of all the payments inside the array tax. My goal is to take only the first, so I tried with $tax.0.payment and using arrayElemAt : [$tax,0] but all my trials gave a tot = 0.
The idea here is pick out the first element of each of payment array field via $arrayElemAt with projection and then group-sum the field $group $sum.
Query:
db.collection.aggregate([
{
$project: {
firstPayment: {
$arrayElemAt: [
"$tax",
0
]
}
}
},
{
$group: {
_id: null,
PaymentSum: {
$sum: "$firstPayment.payment"
}
}
}
]);
Demo O/P:
[
{
"PaymentSum": 11,
"_id": null
}
]
Machine.aggregate({$unwind:
{path: "$tax"}
},
{$group:{
_id: "$_id",
payment: {$first: "$tax.payment"}
}},
{$group: {
_id: null,
total: {$sum: "$payment"}
}}
)
Explanation:
First I used $unwind on tax, then in the first $group stage I grouped them according to _id,
that way I will get the first payment information from unwinded tax array.
Then I used $sum to add them in the second $group stage.
I tested with this data:
Machine collection docs:
{
"_id" : ObjectId("5dbf09a4d7912bcbc61ee9e4"),
"tax" : [
{
"payment" : 10
},
{
"payment" : 20
}
]
},
{
"_id" : ObjectId("5dbf09aad7912bcbc61ee9e5"),
"tax" : [
{
"payment" : 30
},
{
"payment" : 40
}
]
},
{
"_id" : ObjectId("5dbf09afd7912bcbc61ee9e6"),
"tax" : [
{
"payment" : 50
},
{
"payment" : 60
}
]
}
The result I got is:
{ "_id" : null, "tot" : 90 }
I hope this fulfills your requirements.
Add $arrayElemAt in your aggregate like this..
Machine.aggregate(
[
{$unwind: "$tax"},
{$group : {
_id : null ,
tot : { $sum: { $arrayElemAt : [ "$tax.payment", 0 ]}
}}
]
,callback);
I have a collection called Visitor which has an array of chats and each array has a document called user.
I need to find some documents on this collection and sort them by if they have some specific user in their chats first.
The path for the user id is:
chats.user._id
where:
chats // array
user // document
_id // ObjectId
The below script does sort the documents correctly, however, it expands the chats array and multiplies the document for each chat in the array.
I only need the sorting, so can I sort and not use the unwind pipeline or make it somehow not multiply the documents?
db.getCollection('Visitor').aggregate([
{$unwind: "$chats"},
{ $match: {'event._id':ObjectId('5c942a3591deb389bfd92579'), 'chats.enabled': {$exists: true}}},
{
"$project": {
"_id": 1,
"chats.user._id": 1,
"weight": {
"$cond": [
{ "$eq": [ "$chats.user._id", ObjectId("5c942a3591deb389bfd92579") ] },
10,
0
]
}
}
},
{ "$sort": { "weight": -1 } },
])
EDIT: I don't need to sort the inner array, but sort the find command by checking if a specific user is in the chats array.
Some sample of Visitor collection:
[
{
"_id" : ObjectId("5c9a3a1bd86e0ba64106e90e"),
"event" : {
"_id" : ObjectId("5c942a3591deb389bfd92579")
},
"chats" : [
{
"enabled" : false,
"user" : {
"_id" : ObjectId("5c81232f09a923b559763418")
},
"_id" : ObjectId("5c9a3a1bd86e0ba64106e915")
}
]
},
{
"_id" : ObjectId("5c9a3a35d86e0ba64106e950"),
"event" : {
"_id" : ObjectId("5c942a3591deb389bfd92579")
},
"chats" : [
{
"enabled" : true,
"user" : {
"_id" : ObjectId("5c81232f09a923b559763418")
},
"_id" : ObjectId("5c9a3a35d86e0ba64106e957")
},
{
"enabled" : true,
"user" : {
"_id" : ObjectId("5c942a3591deb389bfd92579")
},
"_id" : ObjectId("5c9a3a34d86e0ba64106e91d")
}
]
}
]
In the above sample, I need to make the second document to be sorted first because it has the user with the _id ObjectId("5c942a3591deb389bfd92579").
The problem here is that using $unwind you modify initial structure of your documents (you will get one document per chats. I would suggest using $map to get an array of weights based on specified userId and then you can use $max to get final weight
db.col.aggregate([
{ $match: {'event._id':ObjectId('5c942a3591deb389bfd92579'), 'chats.enabled': {$exists: true}}},
{
"$project": {
"_id": 1,
"chats.user._id": 1,
"weight": {
$max: { $map: { input: "$chats", in: { $cond: [ { $eq: [ "$$this.user._id", ObjectId("5c942a3591deb389bfd92579") ] }, 10, 0 ] } } }
}
}
},
{ "$sort": { "weight": -1 } },
])
I am programming a MongoDB function in AngularJS and Node and I have a single collection that has a field called documentId that is not unique. I want to find all docs in that collection where NONE of the status fields of a particular document contains the word open and only include the documents where ALL of the status' are either "closed" or "canceled".
Ex:
_id: <val> , companyId : "CO-000001", documentId : "001", status:"closed", value:"123.45"
_id: <val> , companyId : "CO-000001", documentId : "002", status:"closed", value:"323.67"
_id: <val> , companyId : "CO-000001", documentId : "001", status:"open", value:"434.56"
_id: <val> , companyId : "CO-000001", documentId : "002", status:"canceled", value:"523.16"
I want my results to ONLY include documentId 002 and not documentId 001 because one of the documents with documentId 001 contains a status of open. But since all documents with documentId 002 have a status of closed or canceled, I want to include it in my result.
I can get the aggregate of what documents I want to exclude from a db.collection.find() using the following...
db.tickets.aggregate(
[
{
"$match" : {
"companyId" : "CO-000001",
"$and" : [
{
"status" : {
"$in" : [
"open","on hold"
]
}
}
]
}
},
{
"$group" : {
"_id" : null,
"uniqueValues" : {
"$addToSet" : "$documentId"
}
}
}
],
{
"allowDiskUse" : false
}
);
but now I want to use the array results from the aggregate in a query when doing my...
db.tickets.find("companyId":"CO-000001", $and:[{documentId:{$nin:[<results from aggregation>]}}])
Is there a way to use the results from the aggregation inside another query? or is there a way to do all of this within an aggregation?
Change the projections according to your need. observe how I used group and match stage later to get the required data. Hope this solves your problem.
db.tickets.aggregate(
[
{
"$match" : {
"companyId" : "CO-000001"
}
},
{
"$group" : {
"_id" : "$documentId",
"status" :{
"$addToSet" : "$status"
}
}
},
{
$match:{ status:{$nin:["open","on hold"]} }
},{
$lookup:{
from:"tickets",
as: "final",
localField: "_id",
foreignField: "documentId",
}
},
{
$project :{
_id:0,
final:1
}
}
],
{
"allowDiskUse" : false
}
).pretty();
In mongoDB, how can we get the count of particular key in an array
{
"_id" : ObjectId("52d9212608a224e99676d378"),
"business" : [
{
"name" : "abc",
"rating" : 4.5
},
{
"name" : "pqr"
},
{
"name" : "xyz",
"rating" : 3.6
}
]
}
in the above example, business is an array (with "name" and/or "rating" keys)
How can i get the count of business array with only "rating" key existing ?
Expected output is : 2
Looks like you have to use Aggregation Framework. In particular you need to $unwind your array, then match only elements with rating field included, then $group documents back to original format.
Try something like this:
db.test.aggregate([
{ $match: { /* your query criteria document */ } },
{ $unwind: "$business" },
{ $match: {
"business.rating": { $exists: 1 }
}
},
{ $group: {
_id: "$_id",
business: { $push: "$business" },
business_count: { $sum: 1 }
}
}
])
Result will look like the following:
{
_id: ObjectId("52d9212608a224e99676d378"),
business: [
{ name: "abc", rating: 4.5 },
{ name: "xyz", rating: 3.6 }
],
business_count: 2
}
UPD Looks like OP doesn't want to group results by wrapping document _id field. Unfortunately $group expression must specify _id value, otherwise it fails with exception. But, this value can actually be constant (e.g. plain null or 'foobar') so there will be only one resulting group with collection-wise aggregation.