mongodb find with opposite of $elemMatch - javascript

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]
}
}

Related

mongoose get only those documents if all subdocuments pass the criteria

Let's say I have a Lesson schema and it contains an array of question subdocuments.
How would I get those lessons where it is marked as completed but all the questions in the array have isDeleted: true
I thought of something like
Lesson.find({ isComplete: true, 'questions.isDeleted': true })
but that gets the lessons if at least one of the questions is deleted. How would I assert the condition for all of the questions?
You can't use the dot notation as it flattens the array, What you can do is use $expr, this allows the use of aggregation operators, like $reduce.
Now we can iterate over the array and see if all the documents satisfy the conditions, like so:
db.collection.find({
isComplete: true,
$expr: {
$eq: [
{
$reduce: {
input: "$questions",
initialValue: 0,
in: {
$sum: [
"$$value",
{
$cond: [
{
$ne: [
"$$this.isDeleted",
true
]
},
1,
0
]
}
]
}
}
},
0
]
}
})
Mongo Playground

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/

Sorting Null values last in MongoDB

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 !!

How to weight documents to create sort criteria?

I'm trying to aggregate a collection in which there are documents that look like this:
[
{
"title" : 1984,
"tags" : ['dystopia', apocalypse', 'future',....]
},
....
]
And I have a criteria array of keywords, for instance:
var keywords = ['future', 'google', 'cat',....]
What I would like to achieve is to aggregate the collection in order to $group it according to a "convenience" criteria in order to sort the document by the one that contains the more of the keywords in its tags field.
This means, if one document contains in its tags: 'future', 'google', 'cat' it will be sorted before another one that has 'future', 'cat', 'apple'.
So far, I have tried something like this:
db.books.aggregate(
{ $group : { _id : {title:"$title"} , convenience: { $sum: { $cond: [ {tags: {$in: keywords}}, 1, 0 ] } } } },
{ $sort : {'convenience': -1}})
But the $in operator is not a boolean so it does not work. I've looked around and didn't find any operator that could help me with this.
As you said you need a logical operator in order to evaluate $cond. It's a bit terse, but here is an implementation using $or :
db.books.aggregate([
{$unwind: "$tags" },
{$group: {
_id: "$title",
weight: {
$sum: {$cond: [
// Test *equality* of the `tags` value against any of the list
{$or: [
{$eq: ["$tags", "future"]},
{$eq: ["$tags", "google"]},
{$eq: ["$tags", "cat"]},
]},
1, 0 ]}
}
}}
])
I'll leave the rest of the implementation up to you, but this should show the basic construction to the point of the matching you want to do.
Addition
From your comments there also seem to be a programming issue you are struggling with, related to how you perform an aggregation like this where you have an Array of items to query in the form you gave above:
var keywords = ['future', 'google', 'cat',....]
Since this structure cannot be directly employed in the pipeline condition, what you need to do is transform it into what you need. Each language has it's own approach, but in a JavaScript version:
var keywords = ['future', 'google', 'cat'];
var orCondition = [];
keywords.forEach(function(value) {
var doc = {$eq: [ "$tags", value ]};
orCondition.push(doc);
});
And then just define the aggregation query with the orCondition variable in place:
db.books.aggregate([
{$unwind: "$tags" },
{$group: {
_id: "$title",
weight: {
$sum: {$cond: [
{$or: orCondition }
1, 0 ]}
}
}}
])
Or for that matter, any of the parts you need to construct. This is generally how it is done in the real world, where we would almost never hard-code a data structure like this.

Categories

Resources