mongoose sort by dates from array - node.js

I have a Chat model which has a messages array which holds Messages. Each Message has both a dateSent property and a dateViewed property. The Message items are embedded, not referenced.
How can I sort Chats by most recent not viewed Message (ie, empty dateViewed) first and then by dateSent? (think Facebook Messenger behavior...)
This is what I've tried so far (note that Chat also has a users array):
Chat.find({users._id: userId}).sort({messages.dateViewed: -1, messages.dateSent: -1}).exec(function(err, chats){
// handle err
// handle no docs found
// handle docs found
});
The above does not seem to work, as the Chats come out in a different order (the same as without sort()). Any thoughts?
EDIT: The suggested answer didn't help me because I don't want to sort the Message objects in the message array, but rather sort the Chats resulting from the query, by the dates in the Message object they hold in a messages array.

The Aggregation pipeline should be used in order to achieve the required sort.
chats/messages:
> db.chats.find()
{ "_id" : "c1", "messages" : [ { "_id" : "m1_1", "dv" : -1, "ds" : 9000 }, { "_id" : "m1_2", "dv" : 8010, "ds" : 8000 } ] }
{ "_id" : "c2", "messages" : [ { "_id" : "m2_1", "dv" : -1, "ds" : 11000 } ] }
{ "_id" : "c3", "messages" : [ { "_id" : "m3_1", "dv" : -1, "ds" : 700 }, { "_id" : "m3_2", "dv" : 7010, "ds" : 7000 } ] }
The code to sort the data by the give criteria:
db.chats.aggregate([
{ $unwind: '$messages' },
{ $addFields: {
ts: {
$cond: [
{ $eq: ['$messages.dv', -1 ] },
'$messages.ds',
'$messages.dv']
}
}
},
{
$sort: { 'ts': -1 }
},
{
$group: {
_id: '$_id',
ts: { $max: '$ts'},
messages: { $push: '$messages' }
}
},
{
$sort: {ts: -1}
}]);
The results:
{ "_id" : "c2", "ts" : 11000, "messages" : [ { "_id" : "m2_1", "dv" : -1, "ds" : 11000 } ] }
{ "_id" : "c1", "ts" : 9000, "messages" : [ { "_id" : "m1_1", "dv" : -1, "ds" : 9000 }, { "_id" : "m1_2", "dv" : 8010, "ds" : 8000 } ] }
{ "_id" : "c3", "ts" : 7010, "messages" : [ { "_id" : "m3_2", "dv" : 7010, "ds" : 7000 }, { "_id" : "m3_1", "dv" : -1, "ds" : 700 } ] }

Related

how to check if any of the value in a column is null it shld count value 1 in mongoose?

how to check if any of the value in a column is null it shld count value 1 in mongoose?
i tried
data: {
$max: {
$cond: [{
$or: [{ $eq: ["$mycolumn", null] },
// { $eq: [ "$anotherField","value"] }
]
},
0,
1]
}
},
but this is returning null.how to fix this?Thanks in advance
my collection is
{ _id: 1, item: null ,name:sam,status:open},
{ _id: 2, item: null ,name:arun,status:open},
{ _id: 2, item: 1,name:arun,status:close},
{ _id: 2, item: 1,name:sam,status:close},
also i have to retrieve by grouping status.
Please try this :
db.yourCollectionName.aggregate([{$group: {_id : '$status',
sumOfItem : {$sum : {$cond: [ { $eq: [ "$item", null ] }, 1, '$item' ]}}}}])
(Or)
/** If you've to check if field exists & also field value == null to return 1
or original item value try below - but this might not be needed,
if you don't want to check field exists cause `$ifNull` internally does both checks */
db.yourCollectionName.aggregate([{$group: {_id : '$status', sumOfItem : {$sum : { $ifNull: [ "$item", 1 ] }}}}])
Collection Data :
/* 1 */
{
"_id" : 1.0,
"item" : null,
"name" : "sam",
"status" : "open"
}
/* 2 */
{
"_id" : 2.0,
"item" : null,
"name" : "arun",
"status" : "open"
}
/* 3 */
{
"_id" : 3.0,
"item" : 1.0,
"name" : "arun",
"status" : "close"
}
/* 4 */
{
"_id" : 4.0,
"item" : 1.0,
"name" : "sam",
"status" : "close"
}
/* 5 */
{
"_id" : 5.0,
"item" : 3,
"name" : "sam3",
"status" : "open"
}
Result :
/* 1 */
{
"_id" : "close",
"sumOfItem" : 2.0
}
/* 2 */
{
"_id" : "open",
"sumOfItem" : 5.0
}
Just in case if you don't want sum of item field but just want total open & close statuses(you don't have to check for null) then try this :
db.yourCollectionName.aggregate([{$group: {_id : '$status', total : {$sum : 1}}}])
**Result :**
/* 1 */
{
"_id" : "close",
"total" : 2.0
}
/* 2 */
{
"_id" : "open",
"total" : 3.0
}
By using addFields , We can set 1 value of item if item is null after count the status
db.demo.aggregate(
[{ "$addFields": {
"item":
{
$ifNull: [ '$item', 1 ]
}
},
},
{
$group: {
_id: '$status' ,
count: { '$sum': '$item' },
}
}
]);

How to project specific elements in an Array field in MongoDB?

I have this data in MongoDB:
{
"_id" : ObjectId("5c7e459f875ea5548de25722"),
"Autos" : [
{
"_id" : ObjectId("5cad9759e1c3895999adaceb"),
"deleted" : 1,
},
{
"_id" : ObjectId("5cad9a8be1c3895999adacef"),
"deleted" : 0,
},
{
"_id" : ObjectId("5cad9aa4e1c3895999adacf0"),
"deleted" : 0,
}
]
}
{
"_id" : ObjectId("5c7e45e9875ea5548de25724"),
"Shoemaking" : [
{
"_id" : ObjectId("5cad9770e1c3895999adacec"),
"deleted" : 1,
},
{
"_id" : ObjectId("5cad9a5de1c3895999adaced"),
"deleted" : 0,
},
]
I want to basically select * from table where deleted = 0
show where the deleted records are equal to 0.
Here is what I have tried so far:
db.rental.find({"Autos.deleted":{$ne: 1}}).pretty()
db.rental.find({"Autos": {$elemMatch: {deleted: 1 } } } ).pretty()
db.rental.find({"Autos.deleted": 0},{"Autos": {$elemMatch:
{deleted:0}}});
But none of the above work for me. What am I doing wrong?
Desired output:
{
"_id" : ObjectId("5c7e459f875ea5548de25722"),
"Autos" : [
{
"_id" : ObjectId("5cad9a8be1c3895999adacef"),
"deleted" : 0,
},
{
"_id" : ObjectId("5cad9aa4e1c3895999adacf0"),
"deleted" : 0,
}
]
}
{
"_id" : ObjectId("5c7e45e9875ea5548de25724"),
"Shoemaking" : [
{
"_id" : ObjectId("5cad9a5de1c3895999adaced"),
"deleted" : 0,
},
]
}
I want the output to be something like the above, but all the queries I have tried so far either select only one record of Array or select nothing at all.
db.rental.aggregate([
{
$project: {
Autos: {
$filter: {
input: "$Autos",
as: "auto",
cond: { $eq:["$$auto.deleted",0] }
}
}
}
}
])
db.rental.find({ "Autos.deleted": { $eq: 0 } } )
use $elemMatch to match contain inside array. mongodb-$elemMatch
db.rental.find({
Autos: {
$elemMatch: {
deleted: 0
}
}
})

Need to apply two group in sequence and second group should will have effect on result of first group

I want to group my data on the base of factoryId field and then each factory there will be multiple orders want to again group on basis of orderId as each order can contain multiple items. Here I am giving the example of my data and what I need and first group by which I tried.
{
"_id" : ObjectId("5b3e270c42d8004cea382e87"),
"factoryId" : ObjectId("5aa76190cef23a1561b8056c"),
"productId" : ObjectId("5aa78c66cef23a1561b80893"),
"orderId" : ObjectId("5b3e270c42d8004cea382e86"),
"generatedOrderId" : "3985-166770-4554",
"productName" : "Lakme Lotion"
},
{
"_id" : ObjectId("5b3e270c42d8004cea382e88"),
"factoryId" : ObjectId("5b39aed32832f72062e51c23"),
"productId" : ObjectId("5b3cb96139cec8341df52c4b"),
"orderId" : ObjectId("5b3e270c42d8004cea382e86"),
"generatedOrderId" : "3985-166770-4554",
"productName" : "Coke"
},
{
"_id" : ObjectId("5b3e27b07fe0d94d62b76b2a"),
"factoryId" : ObjectId("5aa76190cef23a1561b8057c"),
"productId" : ObjectId("5ac21075ac347a5fbf355028"),
"orderId" : ObjectId("5b3e27b07fe0d94d62b76b27"),
"generatedOrderId" : "3985-755507-7484",
"productName" : "Spoon"
}
And I want result as:
{
"factoryId":ObjectId("5aa76190cef23a1561b8057c"),
"orders":[
{
"orderId":ObjectId("5b3e270c42d8004cea382e86")
"items":[
{
"productName":"Lakme Lotion"
},
{
"productName":"Coke"
}
]
}
]
}
Can anyone help me with this?. Any help is appreciated.
I tried and It worked for me. Sorry
db.getCollection("transactions").aggregate(
[
{
"$group" : {
"_id" : "$orderId",
"items" : {
"$push" : "$$ROOT"
}
}
},
{
"$project" : {
"orderId" : "$_id",
"items" : "$items",
"_id" : 0
}
},
{
"$unwind" : {
"path" : "$items",
"preserveNullAndEmptyArrays" : false
}
},
{
"$group" : {
"_id" : "$items.factoryId",
"orders" : {
"$push" : "$$ROOT"
}
}
},
{
"$project" : {
"factoryId" : "$_id",
"orders" : "$orders",
"_id" : 0
}
}
]
);

Count where value does not match array

I have following offers collection
Here readBy contains the _id of the users...
Now I want to count the number of unRead offers for the userId = "5add82620d7f5b38240c63d4"
{
"_id" : ObjectId("5aeaab5ed6a9c97d0209260a"),
"expiresIn" : ISODate("2018-05-30T18:30:00.000Z"),
"name" : "Trip ",
"readBy" : [
ObjectId("5add82620d7f5b38240c63d4"),
ObjectId("5add82620d7f5b38240c63c6")
],
"__v" : 0
}
{
"_id" : ObjectId("5aeaab7dd6a9c97d0209260b"),
"expiresIn" : ISODate("2018-05-29T18:30:00.000Z"),
"name" : "Trip",
"readBy" : [ObjectId("5add82620d7f5b38240c63d4")],
"__v" : 0
}
{
"_id" : ObjectId("5aeae233d6a9c97d02092622"),
"expiresIn" : ISODate("2018-05-25T18:30:00.000Z"),
"name" : "two way off",
"readBy" : [],
}
{
"_id" : ObjectId("5aeae49643f10d284726069c"),
"expiresIn" : ISODate("2018-05-25T18:30:00.000Z"),
"name" : "two way off",
"readBy" : [],
}
{
"_id" : ObjectId("5aeae49743f10d284726069d"),
"expiresIn" : ISODate("2018-05-25T18:30:00.000Z"),
"name" : "two way off",
"readBy" : []
}
{
"_id" : ObjectId("5aeae49743f10d284726069e"),
"expiresIn" : ISODate("2018-05-25T18:30:00.000Z"),
"name" : "two way off",
"readBy" : []
}
so for the above collection my output should be
[{
numberOfUnreadOffers: 4
}]
because four of the collection does not have 5add82620d7f5b38240c63d4 in readBy array
You basically use $setIsSubset and $cond here:
var userId= "5add82620d7f5b38240c63d4";
Model.aggregate([
{ "$group": {
"_id": null,
"numberOfUnreadOffers": {
"$sum": {
"$cond": {
"if": {
"$setIsSubset": [[mongoose.Types.ObjectId(userId)], "$readBy"]
},
"then": 0,
"else": 1
}
}
}
}}
])
Of course you also need to "cast" using mongoose.Types.ObjectId from the "string" to a valid ObjectId value.
But really you get better performance from a simple count() instead:
Model.count({ "readBy": { "$ne": userId } })
So you really should not use .aggregate() for this at all.

Mongo Node driver how to get all fields of $max aggregate from an array of objects

I have a collection called "products" which has an array of "bids" objects.
I want to find out the Maximum bid for each product, for this I am aggregating Products on $max with $bids.bidamount field. However this is only giving me the largest bid amount. How do I project all the bid fields for the max aggregation.
Here is a sample document
{
"_id" : ObjectId("58109a5138fe12215cfdc064"),
"product_id" : 2,
"item_name" : "Auction Item1",
"item_description" : "Test",
"seller_name" : "ak#gmail.com",
"item_price" : "20",
"item_quantity" : 7,
"sale_type" : "Auction",
"posted_at" : "2016:10:26 04:58:09",
"expires_at" : "2016:10:30 04:58:09",
"bids" : [
{
"bid_id" : 1,
"bidder" : "ak#gmail.com",
"bid_amount" : 300,
"bit_time" : "2016:10:26 22:36:29"
},
{
"bid_id" : 2,
"bidder" : "ak#gmail.com",
"bid_amount" : 100,
"bit_time" : "2016:10:26 22:37:29"
}
],
"orders" : [
{
"buyer" : "ak#gmail.com",
"quantity" : "2"
},
{
"buyer" : "ak#gmail.com",
"quantity" : "3"
}
]
}
Here is my mongo query:
db.products.aggregate([
{
$project: {
bidMax: { $max: "$bids.bid_amount"}
}
}
])
which gives the following result:
{
"_id" : ObjectId("58109a5138fe12215cfdc064"),
"bidMax" : 300
}
db.products.aggregate([{$unwind:"$bids"},{$group:{_id:"$_id", sum:{$sum:"$bids.bid_amount"}}},{$project:{doc:"$$ROOT", _id:1, sum:1}, {$sort:{"sum":-1}},{$limit:1}]),
which return something like { "_id" : ObjectId("5811b667c50fb1ec88227860"), "sum" : 600, doc:{your document....} }
This should do it:
db.products.aggregate([{
$unwind: '$bids'
}, {
$group: {
_id: '$products_id',
maxBid: {
$max: '$bids.bid_amount'
}
}
}])
db.collectionName.aggregate(
[
{
$group:
{
_id: "$product_id",
maxBidAmount: { $max: "$bids.bid_amount" }
}
}
]
)
Hey use this query, you will get the result.

Resources