Sorting Null values last in MongoDB - javascript

I'm using the following query to populate items from MongoDB, in ascending order, according to a field called sortIndex.
Sometimes though items in the DB don't have the sortIndex field. With the following query, the items with a null sortIndex are showing up at the top, and I'm wondering how to get them to show up at the bottom. Would I need two queries for this or is there a way to use one query?
.populate({path: 'slides', options: { sort: { 'sortIndex': 'ascending' } } })

You can do something like this:
db.collection.aggregate([
{ $addFields:
{
hasValue : { $cond: [ { $eq: [ "$value", null ] }, 2, 1 ] },
}
},
])
.sort({hasValue : 1, value : 1});

Duplicate of: How to keep null values at the end of sorting in Mongoose?
Anyway posting the same solution ...
Am not sure about the solution am about to say. I cant test this out as I dont have a mongo db set right now, but I think that you can use <collection>.aggregate along with $project and $sort to achieve this.
Sample code:
db.inventory.aggregate(
[
{
$project: {
item: 1,
description: { $ifNull: [ "$amount", -1*(<mimimum value>)* ] }
}
},
{
$sort : {
amount : (-1 or 1 depending on the order you want)
}
}
]
)
Hope this helps !!

Related

Is it possible to update multiple documents with different values using mongo? [duplicate]

I have the following documents:
[{
"_id":1,
"name":"john",
"position":1
},
{"_id":2,
"name":"bob",
"position":2
},
{"_id":3,
"name":"tom",
"position":3
}]
In the UI a user can change position of items(eg moving Bob to first position, john gets position 2, tom - position 3).
Is there any way to update all positions in all documents at once?
You can not update two documents at once with a MongoDB query. You will always have to do that in two queries. You can of course set a value of a field to the same value, or increment with the same number, but you can not do two distinct updates in MongoDB with the same query.
You can use db.collection.bulkWrite() to perform multiple operations in bulk. It has been available since 3.2.
It is possible to perform operations out of order to increase performance.
From mongodb 4.2 you can do using pipeline in update using $set operator
there are many ways possible now due to many operators in aggregation pipeline though I am providing one of them
exports.updateDisplayOrder = async keyValPairArr => {
try {
let data = await ContestModel.collection.update(
{ _id: { $in: keyValPairArr.map(o => o.id) } },
[{
$set: {
displayOrder: {
$let: {
vars: { obj: { $arrayElemAt: [{ $filter: { input: keyValPairArr, as: "kvpa", cond: { $eq: ["$$kvpa.id", "$_id"] } } }, 0] } },
in:"$$obj.displayOrder"
}
}
}
}],
{ runValidators: true, multi: true }
)
return data;
} catch (error) {
throw error;
}
}
example key val pair is: [{"id":"5e7643d436963c21f14582ee","displayOrder":9}, {"id":"5e7643e736963c21f14582ef","displayOrder":4}]
Since MongoDB 4.2 update can accept aggregation pipeline as second argument, allowing modification of multiple documents based on their data.
See https://docs.mongodb.com/manual/reference/method/db.collection.update/#modify-a-field-using-the-values-of-the-other-fields-in-the-document
Excerpt from documentation:
Modify a Field Using the Values of the Other Fields in the Document
Create a members collection with the following documents:
db.members.insertMany([
{ "_id" : 1, "member" : "abc123", "status" : "A", "points" : 2, "misc1" : "note to self: confirm status", "misc2" : "Need to activate", "lastUpdate" : ISODate("2019-01-01T00:00:00Z") },
{ "_id" : 2, "member" : "xyz123", "status" : "A", "points" : 60, "misc1" : "reminder: ping me at 100pts", "misc2" : "Some random comment", "lastUpdate" : ISODate("2019-01-01T00:00:00Z") }
])
Assume that instead of separate misc1 and misc2 fields, you want to gather these into a new comments field. The following update operation uses an aggregation pipeline to:
add the new comments field and set the lastUpdate field.
remove the misc1 and misc2 fields for all documents in the collection.
db.members.update(
{ },
[
{ $set: { status: "Modified", comments: [ "$misc1", "$misc2" ], lastUpdate: "$$NOW" } },
{ $unset: [ "misc1", "misc2" ] }
],
{ multi: true }
)
Suppose after updating your position your array will looks like
const objectToUpdate = [{
"_id":1,
"name":"john",
"position":2
},
{
"_id":2,
"name":"bob",
"position":1
},
{
"_id":3,
"name":"tom",
"position":3
}].map( eachObj => {
return {
updateOne: {
filter: { _id: eachObj._id },
update: { name: eachObj.name, position: eachObj.position }
}
}
})
YourModelName.bulkWrite(objectToUpdate,
{ ordered: false }
).then((result) => {
console.log(result);
}).catch(err=>{
console.log(err.result.result.writeErrors[0].err.op.q);
})
It will update all position with different value.
Note : I have used here ordered : false for better performance.

Query for documents to iterate over Array and take sum on a particular property of JSON in mongodb using $cond

I have a array of JSON like this:
let x = [{"Data":"Chocolate","Company":"FiveStar"},{"Data":"Biscuit","Company":"Parle"},{"Data":"Chocolate","Company":"DairyMilk"}]
This is a sample array of JSON. What I want to do is how to use MongoDB $cond to take count of all fields having "Data" equals Chocolate?
If you wanted to stick to $cond then you could run the query like this:
db.collection.aggregate([
{
$match: {
Data: "Chocolate"
}
},
{
$group: {
_id: "$Data",
count: {
$sum: {
$cond: [
{
$eq: [
"$Data",
"Chocolate"
]
},
1,
0
]
}
}
}
}
])
You can see the results of this query here.
You can see that firstly I get all elements where Data is equal to "Chocolate" with $match.
Later, we can use $sum to get a count of the elements that match the conditions with $cond and return 1 if it is equal and 0 if not.
In this case, we only are left with the Data that is equal to "Chocolate" anyway.
Of course, it would be faster and easier to simply run:
db.collection.aggregate([
{
$match: {
Data: "Chocolate"
}
},
{
$count: "count"
}
])
The example of this query here.

$all not working in mongodb

I have two document in my collections
{ participants: [ '5ab8fcf6d8bfca2cc0aebb37', '5ab8fd15d8bfca2cc0aebb38' ],
_id: 5ab9a5a0cb274a2064b65d1b,
__v: 0
},
{ participants: [ '5ab8fcf6d8bfca2cc0aebb37', '5ab8fcf6d8bfca2cc0aebb37' ],
_id: 5ab9a5a7cb274a2064b65d1c,
__v: 0
}
and i have an array of persons like
persons = [ 5ab8fcf6d8bfca2cc0aebb37, '5ab8fcf6d8bfca2cc0aebb37' ]
Now I am trying to find a document which contains which contain participants fields similar to array persons using this query.
Participant.find({participants: {$all: persons}}).exec()
.then(connected => {
console.log(connected);
// perform some stuff
});
it throws me both document as an output.
I don't know what is the problem.
Thanx in advance.
I think what you want is the $setEquals operator.
db.collection.find({ $expr: { $setEquals: [ persons, "$participants" ] } } )
You can use the $setEquals operator with the $redact operator if the $expr operator is not available in the mongod version you're running.
you can use $eq operator as well
Participant.find( { participants: { $eq: persons } })
.then(connected => {
console.log(connected);
// perform some stuff
});
for more https://docs.mongodb.com/manual/reference/operator/query/eq/

Mongoose: Sorting

what's the best way to sort the following documents in a collection:
{"topic":"11.Topic","text":"a.Text"}
{"topic":"2.Topic","text":"a.Text"}
{"topic":"1.Topic","text":"a.Text"}
I am using the following
find.(topic:req.body.topic).(sort({topic:1}))
but is not working (because the fields are strings and not numbers so I get):
{"topic":"1.Topic","text":"a.Text"},
{"topic":"11.Topic","text":"a.Text"},
{"topic":"2.Topic","text":"a.Text"}
but i'd like to get:
{"topic":"1.Topic","text":"a.Text"},
{"topic":"2.Topic","text":"a.Text"},
{"topic":"11.Topic","text":"a.Text"}
I read another post here that this will require complex sorting which mongoose doesn't have. So perhaps there is no real solution with this architecture?
Your help is greatly appreciated
i will suggest you make your topic filed as type : Number, and create another field topic_text.
Your Schema would look like:
var documentSchema = new mongoose.Schema({
topic : Number,
topic_text : String,
text : String
});
Normal document would look something like this:
{document1:[{"topic":11,"topic_text" : "Topic" ,"text":"a.Text"},
{"topic":2,"topic_text" : "Topic","text":"a.Text"},
{"topic":1,"topic_text" : "Topic","text":"a.Text"}]}
Thus, you will be able to use .sort({topic : 1}) ,and get the result you want.
while using topic value, append topic_text to it.
find(topic:req.body.topic).sort({topic:1}).exec(function(err,result)
{
var topic = result[0].topic + result[0].topic_text;//use index i to extract the value from result array.
})
If you do not want (or maybe do not even can) change the shape of your documents to include a numeric field for the topic number then you can achieve your desired sorting with the aggregation framework.
The following pipeline essentially splits the topic strings like '11.Topic' by the dot '.' and then prefixes the first part of the resulting array with a fixed number of leading zeros so that sorting by those strings will result in 'emulated' numeric sorting.
Note however that this pipeline uses $split and $strLenBytes operators which are pretty new so you may have to update your mongoDB instance - I used version 3.3.10.
db.getCollection('yourCollection').aggregate([
{
$project: {
topic: 1,
text: 1,
tmp: {
$let: {
vars: {
numStr: { $arrayElemAt: [{ $split: ["$topic", "."] }, 0] }
},
in: {
topicNumStr: "$$numStr",
topicNumStrLen: { $strLenBytes: "$$numStr" }
}
}
}
}
},
{
$project: {
topic: 1,
text: 1,
topicNumber: { $substr: [{ $concat: ["_0000", "$tmp.topicNumStr"] }, "$tmp.topicNumStrLen", 5] },
}
},
{
$sort: { topicNumber: 1 }
},
{
$project: {
topic: 1,
text: 1
}
}
])

mongodb find with opposite of $elemMatch

I have a collection like this:
posts = {
title: 'Hey',
content: '...',
authors: [{
id: 'id',
name: 'John'
}, {
id: 'id2',
name: 'david'
}]
};
I want to make a query. I have found the $elementMatch, but I would like the opposite.
I have also found $nin but I don't if it works for mycase.
Here is what I want to do:
db.posts.find({
authors: {
$notElemMatch: {
id: 'id'
}
}
});
I want to find every posts except those writing by someone.
You don't even need $elemMatch since you only have a single field condition. Just use $ne instead:
db.posts.find({ "authors.id": { "$ne": 'id' } });
There is a $not condition, but it really does not need apply here.
While the original question does not require the use of $elemMatch (as answered by Blakes), here is one way to "invert" the $elemMatch operator.
Use $filter + $and as a subsitute for $elemMatch and check that the result has 0 length.
$expr allows the use of aggregation expressions in simple "find" queries.
All conditions in $elemMatch will be translated to items in the array supplied as an argument to $and.
Tested to work with MongoDB server version 5.0.9
{
"$expr": {
"$eq": [
{
"$size": {
"$filter": {
"input": "$authors",
"cond": {
"$and": [
{
"$eq": ["$$this.id", "id"]
}
]
}
}
}
},
0
]
}
}
Here is a better way to "invert" the $elemMatch operator with aggregation.
Use $expr $eq and $in
$in checks if the item is in the array, since we want opposite meaning false so we check if the result of the "$in" operation is $eq (equal) to false
{
"$expr": {
"$eq": [{ "$in": { 'id', "authors.id"} }, false]
}
}

Categories

Resources