Find documents with array that doesn't contains a specific value - javascript

I have the following model:
var PersonSchema = new Schema({
name: String,
groups: [
{type: Schema.Types.ObjectId, ref: 'Group'}
],
});
I am looking for a query that retrieves all the Persons that are not part of a certain Group (i.e the persons' group array doesn't contain the id of the specified group).
I was thinking about something like this, but I'm not sure it is correct:
Person.find({groups: {$nin: [group._id]})

Nothing wrong with what you are basically attempting, but perhaps the only clarification here is the common misconception that you need operators like $nin or $in when querying an array.
Also you really need to do here is a basic inequality match with $ne:
Person.find({ "groups": { "$ne": group._id } })
The "array" operators are not for "array targets" but for providing a "list" of conditions to test in a convenient form.
Person.find({ "groups": { "$nin": [oneId, twoId,threeId] } })
So just use normal operators for single conditions, and save $in and $nin for where you want to test more than one condition against either a single value or a list. So it's just the other way around.
If you do need to pass a "list" of arguments where "none" of those in the provided list match the contents of the array then you reverse the logic with the $not operator and the $all operator:
Person.find({ "groups": { "$not": { "$all": [oneId,twoId,threeId] } } })
So that means that "none of the list" provided are present in the array.

This is a better way to do this in Mongoose v5.11:
Person.find({ occupation: /host/ }).where('groups').nin(['group1', 'group2']);
The code becomes clearer and has more readability.

Related

Find all realted matched docs in an array of string

I have a mongoose schema model that have a field name tags , and it is an array of strings to store some tags in it for each document. I want something for example if I have an array of tags like ["test", "testimonials", "test Doc"] tags in it, when i search for test, it returns all documents with tags that they are testimonials or test doc , it should be work for example like wildcards (test*) .... can anyone help ?
this is the model
tags: {
type: [
{
type: String,
},
],
},
First of all, I'd tweak the Schema if possible. Your schema could be changed to this:
tags: [String]
This also just means an array of strings. You don't need to always need to use/specify the type key unless you're planning to add more fields to the tag schema, but it doesn't look like it from the question.
You can do the following to select all documents with a specific tag. Since I don't know what the name of your model is, I'll just call it "Model".
await Model.find({ tags: "tagName" })
OR
await Model.find({ tags: { $elemMatch: { someKey: someValue } } })
The later is only if you have other mongodb documents inside the array. Since you only have strings in the array, use the first method.

Not understanding mongoose schema array syntax

I'm having trouble wrapping my head around the below syntax in a mongoose schema.
tokens:[{
token:{
type: 'String',
required: true
}
}]
Normally when I want to add objects to arrays I would just push the object. That includes if I want to push an object with an internal object to said array like the example below.
let obj =[]
obj.push({name: 'dirk', age: 24})
obj.push({name: 'chris', age:29, clothes: {shirt: 'black', pants: 'jeans'}, height: '6ft'})
So im confused in mongoose as to why I need this syntax [{}] to use an array?
Ok I'll try to explain this as best I can. In basic JavaScript, an array is just a bucket of "stuff" for lack of better words. What that means is, you could have something like this:
let myList = []
myList.push({name: "John Doe", age: 20})
myList.push({car: {make: "Honda", model: "Accord"}})
myList.push({menu_items: ["item 1", "item 2", "item 3"]})
And JavaScript won't really care right? As far as it's concerned, you haven't done anything wrong because you technically did the correct thing, put some object into a list that can hold whatever you want.
Mongoose, which is an ORM (check out the term if you haven't heard of it before), requires things be a little more strict. Remember that Mongoose is trying to map documents from the Mongo database to this "Object" that should be standard and readable from any point in the code the same what. So in the example in your question listed above:
tokens:[{
token:{
type: 'String',
required: true
}
}]
you are saying to Mongoose that it should expect tokens to contain a list of token objects that have a certain design to them. Now, when you want to add more token objects to the tokens list, you can do something like this:
token = {
// YOUR CORRECT TOKEN DATA
}
tokens.push(token);
If you tried to do something like this instead:
token = {
// RANDOM WRONG DATA
}
tokens.push(token);
Mongoose wouldn't take kindly to it because you are violating the constraints you told Mongoose to keep in affect. If you start throwing any: [{}] into the mix, you are telling Mongoose that any old object will do for that list. This is, in my opinion, very dangerous to do because if you have two types of objects in your list:
var schema1 = new Schema({
name: String,
age: { type: Number, min: 18, max: 65 },
living: Boolean
})
var schema2 = new Schema({
name: String,
age: { type: Number, min: 18, max: 65 },
breed: Boolean
})
and you were working with a list that combined these two objects, if you tried to grab say breed from the schema1 type object, you would either get an error or an odd value that could break your code. So Mongoose strictly types the objects you are pushing to a list, unless you use something like Mixed or any. Here's a link to the documentation that may help explain a bit as well
https://mongoosejs.com/docs/schematypes.html#arrays
If this doesn't make sense or I explained it poorly or answered the wrong question please comment and I'll fix it to explain as best I can.
Happy coding :-)

Using $match twice through mongo aggregation pipeline

I 'm facing an issue while trying to get some results from a mongoDB aggregation pipeline.
Here's what my DB look like:
var dbSchema = mongoose.Schema({
identity: Number,
parametres: {
style: {
fuel: [styleSchema],
gasoline: [styleSchema],
},
},
And here's what the styleSchema looks like:
var styleSchema = new mongoose.Schema({
date: Date,
value: Number,
type: String,
});
I'm trying to extract ALL the objects in 'fuel' and 'gasoline' which are of some kind of 'type'.
I've tried to group both in a unique array with concatArray and then match the 'type' I want by:
db.aggregate([
{$match:
{'identity':3,
}},
{$project: {all: {$concatArrays: ['$parametres.style.fuel','$parametres.style.gasoline']} }},
{$match: {'$all.type': 'example'}},
Before trying to match the second time, I've got a unique array ('all') and I try to match some things on it, but nothing works (I've tried 'all.type' also)...
I've probably misunderstood the way I have to use the 'match' query as I am a beginner, so thanks for your time and your answers,
Arthur
db.aggregate([
{
$match:{identity:3}
},
{
$project: {all: {$concatArrays: ['$parametres.style.fuel','$parametres.style.gasoline']} }
},
{$unwind: "$all"},
{$match: {"all.type": 'example'}},
{$group : {_id: null, all:{$push:"$all"}}}
])
Probably you are trying to do something like this.
In aggregate operation, the $ is used in right side of : to mention field name and in left side to mention operator.
So when you are using "$all.type" in left-hand side MongoDB is treating it as an operator which is not available in the Mongodb operator list.
Another thing is that when you do any query over an array. Mongodb sends back the full array if atleast one of the element matches the condition. So we need to use $unwind operator to deconstruct the array before doing any query.

Searching in embeded comments in mongodb

I'd like to make a simple "chat" where there is a post and answers for them (only 1 deep), I decided to go this way, so a single document would look like this
{
_id: ObjectId(...),
posted: date,
author: "name",
content: "content",
comments: [
{ posted: date,
author: "name2"},
content: '...'
}, ... ]
}
My question is how should I search in the content this way? I'd first need to look for a match in the "parent" content, then the contents in the comments list. How should I do that?
If you can search for a regex within each content, you could use:
{$or : [
{'content':{$regex:'your search regex'}},
{'comments' : { $elemMatch: { 'content':{$regex:'your search regex'}}}]}
Please note that when fetching for results, upon a match to either a parent or a child you will receive the entire mongo document, containing both the parent and the children.
If you want to avoid this (to be sure what you've found), you can possibly run first a regex query on the parent only, and then on the children only, instead of the single $or query.
For more details on $elemMatch take a look at: docs.mongodb.org/manual/reference/operator/query/elemMatch
As was stated in the comments earlier, the basic query to "find" is just a simple matter of using $or here, which also does short circuit to match on the first condition where that returns true. There is only one array element here so no need for $elemMatch, but just use "dot notation" since multiple field matches are not required:
db.messages.find({
"$or": [
{ "content": { "$regex": ".*Makefile.*" } },
{ "comments.content": { "$regex": ".*Makefile.*" } }
]
})
This does actually match the documents that would meet those conditions, and this is what .find() does. However what you seem to be looking for is something a little "funkier" where you want to "discern" between a "parent" result and a "child" result.
That is a little out of the scope for .find() and such manipulation is actually the domain of other operations with MongoDB. Unfortunately as you are looking for "part of a string" to match as your condition, doing a "logical" equivalent of a $regex operation does not exist in something such as the aggregation framework. It would be the best option if it did, but there is no such comparison operator for this, and a logical comparison is what you want. The same would apply to "text" based searches, as there is still a need to discern the parent from the child.
Not the most ideal approach since it does involve JavaScript processing, but the next best option here is mapReduce().
db.messages.mapReduce(
function() {
// Check parent
if ( this.content.match(re) != null )
emit(
{ "_id": this._id, "type": "P", "index": 0 },
{
"posted": this.posted,
"author": this.author,
"content": this.content
}
);
var parent = this._id;
// Check children
this.comments.forEach(function(comment,index) {
if ( comment.content.match(re) != null )
emit(
{ "_id": parent, "type": "C", "index": index },
{
"posted": comment.posted,
"author": comment.author,
"content": comment.content
}
);
});
},
function() {}, // no reduce as all are unique
{
"query": {
"$or": [
{ "content": { "$regex": ".*Makefile.*" } },
{ "comments.content": { "$regex": ".*Makefile.*" } }
]
},
"scope": { "re": /.*Makefile.*/ },
"out": { "inline": 1 }
}
)
Basically the same query to input as this does select the "documents" you want and really just using "scope" here is it makes it a little easier to pass in the regular expression as an argument without re-writing the JavaScript code to include that value each time.
The logic there is simple enough, just to each "de-normalized" element you are testing to see if the regular expression condition was a match for that particular element. The results are returned "de-normalized" and discern between whether the matched element was a parent or a child.
You could take that further and not bother to check the children if the parent was a match just by moving that to else. In the same way you could even just return the "first" child match by some means or another if that was your desire.
Anyhow this should set you on the path to whatever your final code looks like. But this is the basic approach to the only way to are going to get this distinction to be processed on the server, and client side post processing would follow much the same pattern.

Inserting into an array?

I am attempting to insert items at a specific index in an array that may or may not be empty. For example, say I have the following document in my mongoDb collection:
{
title: "abe",
questions: []
}
I would like to do something like set the 5th element in the array to a specific value. I was playing with the mongo db command line and I was able to do the following:
> mytest = []
[ ]
> mytest[4] = 'test'
test
> mytest
[ undefined, undefined, undefined, undefined, "test" ]
Which is basically what I want, but when I attempt to do this from my node.js code I am getting weird results (weird as in, items are not in the correct index). Code below:
Mongoose Schema Definition:
mongoose = require("mongoose")
Schema = mongoose.Schema
surveySchema = new Schema
title: String
questions: [
type: Schema.Types.ObjectId
ref: 'Question'
]
Code doing the update:
surveys.update {id: doc.survey_id},
{
$push: {
questions: {
$each: [doc._id],
$position: doc.sort_order
}
}
},
{upsert:false, multi:true},
callback
The code above is being executed in an asynchronous loop, so it's possible the last item will be inserted before the first item and so on.
Can anyone see any reason why items would not be getting inserted at the correct index? Why does my basic example work but the code does not? Could it have to do with the fact that my schema defines questions as an array of ObjectIds?
Documentation for MongoDB $position:
If the number is greater or equal to the length of the array, the $position modifier has no effect and the operator adds elements to the end of the array.

Categories