Expression $ne takes exactly 2 arguments. 1 were passed in - javascript

I have this to query in db :
const hasUsedVideo = await db.SessionCard.findOne({ movie: Card.movie, unit: Card.unit, video: Card.video }, { _id: { $ne: Card._id } }, { _id: 1 } ).lean();
But I get this error:
MongoError: Expression $ne takes exactly 2 arguments. 1 were passed
in.
I don't know how to fix this?

#nimrod serok provides the right guidance in the comments, which I'll copy below for completeness. This answer is just going to outline why you are receiving that error.
The solution to the problem is to include the predicate on _id in the rest of the object that comprises the first argument for the findOne() operation. So instead of:
const hasUsedVideo = await db.SessionCard.findOne(
{ movie: Card.movie, unit: Card.unit, video: Card.video },
{ _id: { $ne: Card._id } },
{ _id: 1 }
).lean();
It should be:
const hasUsedVideo = await db.SessionCard.findOne(
{ movie: Card.movie, unit: Card.unit, video: Card.video, _id: { $ne: Card._id } },
{ _id: 1 }
).lean();
As currently written in the question, the separate second argument (of { _id: { $ne: Card._id } }) is being parsed as the projection argument (instead of the intended projection of { _id: 1 }. The projection argument accepts aggregation expressions and syntax as of version 4.4. This is relevant to the error that you were receiving because there are two different $ne operators in MongoDB:
One is the standard query operator which has the syntax of { field: { $ne: value } }.
The other is an aggregation expression with a syntax of { $ne: [ <expression1>, <expression2> ] }.
This is why your usage of _id: { $ne: Card._id } was triggering the error about $ne requiring 2 arguments, because the aggregation variant of the operation does require that.
Mongoplayground will complain about the syntax in the question stating that a maximum of 2 arguments are expected for find() (in the shell implementation). By contrast, the syntax provided by #nimrod serok executes successfully.

Related

Mongodb+mongoose update documents with date from other field = cast to date error

okay so i have some documents that are missing some data, im trying to update them like this;
await MyModel.updateMany(
{ imported: null },
{ $set: {
"imported": "$post.date"
}}
)
but i get cast to date error.
so i decided to work with a single document to figure out why that is.
so first lets see what inside post.date
console.log(
await MyModel.find({ _id: '5c2ce0fa527ad758bdb29506' })
.select('post.date imported')
)
[
{
post: { date: 2018-11-29T18:02:25.000Z },
_id: new ObjectId("5c2ce0fa527ad758bdb29506")
}
]
okay, so i though what happens if i copy the date and hardcode it, like so:
await MyModel.updateMany(
{ _id: "5c2ce0fa527ad758bdb29506" },
{ $set: {
"imported": "2018-11-29T18:02:25.000Z"
}}
)
that works just fine, but when i try to use the value from $post.date, like this:
await MyModel.updateMany(
{ _id: "5c2ce0fa527ad758bdb29506" },
{ $set: {
"imported": "$post.date"
}}
)
it results in
Cast to date failed for value "$post.date" (type string) at path "imported"
i have tried to use $dateFromString and $toDate but with no luck, but i feel like mongoose is trying to validate the string value "$post.date" and not interpret it as mongo would even through the mongoose schema has no validators defined.
so i tried adding { runValidators: false }, but it feels like it is ignored
await MyModel.updateMany(
{ _id: "5c2ce0fa527ad758bdb29506" },
{ $set: {
"imported": "$post.date"
}},
{ runValidators: false }
)
as the error is the same.
im running mongoose v. 6.2.0 and mongodb v. 4.2.18
any inputs are much appreciated
You can use this
db.collection.update({
imported: null
},
[ //aggregate update
{
"$set": {
"imported": "$post.date"
}
}
])
Supported from Mongo 4.2+

Mongoose: only one unique boolean key should be true

I have two schema collections:
Campaigns:{
_id: "someGeneratedID"
//...a lot of key value pairs.
//then i have teams which is an, array of teams from the Team schema.
teams:teams: [{ type: mongoose.Schema.Types.ObjectId, ref: "Team" }],
}
Teams:{
campaignId: { type: mongoose.Schema.Types.ObjectId, ref: "Campaign" },
isDefault: { type: Boolean, default: false },
}
Now I would like that when I add teams to the collection, it should throw an error if there are more than 2 isDefault:true per campaignId.
so the following shouldn't be allowed:
teams:[
{
campaignId:1,
teamName:"John Doe"
isDefault:true,
}
{
campaignId:1,
teamName:"Jane Doe"
isDefault:true
}
]
I found this answer on SO:
teamSchema.index(
{ isDefault: 1 },
{
unique: true,
partialFilterExpression: { isDefault: true },
}
But couldn't manage to also check for the campaignId.
Thanks in advance.
ps: can you also provide an explanation for what's happening in the index method?
I think that the simplest way to approach this is via Mongoose's middleware pre('save'). This method will give you a way to check all the campaigns listed in the collection in order to check if any of the items is already set as default.
teamSchema.pre("save", async function (next) {
try {
if ((this.isNew || this.isModified("isDefault") && this.isDefault) {
const previousDefault = await mongoose.models["Team"].findOne({ isDefault: true, campaignId: this.campaignId });
if (previousDefault) {
throw new Error('There is already default team for this campaign');
}
}
next();
} catch (error) {
throw error;
}
});
This way, if any team, either new or already existing, is set as default for a given campaign, before its record is saved, the whole collection will be searched for any entry with isDefault already set to true. If at least one item is found, we will throw an error. If not, next() guarantees the save() method will go on.

TypeError: Invalid `field` argument. Must be string or function at CustomQuery.Query.distinct

I am trying to make a query with mongoose's distinct, I read the documentation at https://mongoosejs.com/docs/api.html#model_Model.distinct
the distinct query works fine if I did not use the conditions field works fine if I do it like this await Country.distinct('en.country'), but
if I do something like await Country.distinct('en.country', { 'en.country': { $ne: 'some country name' } }); this would give me the error of TypeError: Invalid field argument. Must be string or function at CustomQuery.Query.distinct
What am I missing here?
Thanks in advance for any suggestions / help.
Use aggregation pipeline -
Start by filtering out the country name documents not matching with some country name
Group by en.country key and count the number of its occurrences.
Filter only those countries that have occurrences as 1.
Project the result back
await Country.aggregate([
{
$match: {
'en.country': {
$ne: 'some country name'
}
}
},
{
$group: {
_id: '$en.country',
count: {
$sum: 1
},
countryName: {
$first: '$en.country'
}
}
},
{
$match: {
count: {
$eq: 1
}
}
},
{
$project: {
_id: 0,
country: '$countryName'
}
}
]).exec();

Mongo conditional aggregation within set operation

I am trying to perform a conditional update of MongoDB record with the following transaction.
db.collection(SESSIONS_COLLECTION)
.updateOne({_id: ObjectId(id)},
{
$set: {
end: {
$cond: {
if: { $gt: ["$end", latestActionDate] }, then: "$end", else: latestActionDate
}
}
},
$push: {
actions: {
$each: data.map(action => ({
...action,
time: new Date(action.time)
}))
}
}
}
);
But all my efforts are crushed with.
MongoError: The dollar ($) prefixed field '$cond' in 'end.$cond' is not valid for storage.
It is possible that such operations are simply not allowed, but I prefer to think that I'm missing something here.
Issue is with you're mixing up normal update operation with aggregation operators, to use aggregation operators in update you need to wrap your update part in [] to say it's actually an aggregation pipeline.
Starting MongoDB version 4.2 you can update-with-an-aggregation-pipeline :
Code :
let actionArray = data.map((action) => ({
...action,
time: new Date(action.time),
}));
db.collection(SESSIONS_COLLECTION).updateOne({ _id: ObjectId(id) }, [
{
$set: {
end: {
$cond: [{ $gt: ["$end", latestActionDate] }, "$end", latestActionDate]
}
}
},
/** (fail safe stage) this second stage is optional but needed in general as `$push` will add a new array
* if that field doesn't exists earlier to update operation
* but `$concatArray` doesn't, So this helps to add `[]` prior to below step */
{
$set: { actions: { $ifNull: [ "$actions", [] ] } }
},
{
$set: {
actions: { $concatArrays: ["$actions", actionArray] }
}
}
]);
Note :
As we're using aggregation pipeline here, you need to use aggregation operators and usage of few query/update operators doesn't work.

Filter to not Include Matched Values in Result Arrays

I have a query where I first want to match find the list of matched users and then filter the matches out from the array of external users that was passed in so that I am left with users Id's that have not been matched yet.
Here is a the match Schema:
const mongoose = require('mongoose'); // only match two users at a time.
const Schema = mongoose.Schema;
const MatchSchema = new Schema({
participants: [{
type: String, ref: 'user'
}],
blocked: {
type: Boolean,
default: false
}
});
Here is the query with explanations:
db.getCollection('match').aggregate([
{
'$match': {
'$and': [
{ participants: "599f14855e9fcf95d0fe11a7" }, // the current user.
{ participants: {'$in': [ "598461fcda5afa9e0d2a8a64","598461fcda5afa9e0d111111", "599f14855e9fcf95d0fe5555"] } } // array of external users that I want to check if the current user is matched with.
]
}
},
{
'$project': {
'participants': 1
}
},
This returns the following result:
{
"_id" : ObjectId("59c0d76e66dd407f5efe7112"),
"participants" : [
"599f14855e9fcf95d0fe11a7",
"599f14855e9fcf95d0fe5555"
]
},
{
"_id" : ObjectId("59c0d76e66dd407f5efe75ac"),
"participants" : [
"598461fcda5afa9e0d2a8a64",
"599f14855e9fcf95d0fe11a7"
]
}
what I want to do next it merge the participants array form both results into one array.
Then I want to take away the those matching items from the array of external users so that I am left with user id's that have not been matched yet.
Any help would be much appreciated!
If you don't want those results in the array, then $filter them out.
Either by building the conditions with $or ( aggregation logical version ):
var currentUser = "599f14855e9fcf95d0fe11a7",
matchingUsers = [
"598461fcda5afa9e0d2a8a64",
"598461fcda5afa9e0d111111",
"599f14855e9fcf95d0fe5555"
],
combined = [currentUser, ...matchingUsers];
db.getCollection('match').aggregate([
{ '$match': {
'participants': { '$eq': currentUser, '$in': matchingUsers }
}},
{ '$project': {
'participants': {
'$filter': {
'input': '$participants',
'as': 'p',
'cond': {
'$not': {
'$or': combined.map(c =>({ '$eq': [ c, '$$p' ] }))
}
}
}
}
}}
])
Or use $in ( again the aggregation version ) if you have MongoDB 3.4 which supports it:
db.getCollection('match').aggregate([
{ '$match': {
'participants': { '$eq': currentUser, '$in': matchingUsers }
}},
{ '$project': {
'participants': {
'$filter': {
'input': '$participants',
'as': 'p',
'cond': {
'$not': {
'$in': ['$p', combined ]
}
}
}
}
}}
])
It really does not matter. It's just the difference of using JavaScript to build the expression before the pipeline is sent or letting a supported pipeline operator do the array comparison where it is actually supported.
Note you can also write the $match a bit more efficiently by using an "implicit" form of $and, as is shown.
Also note you have a problem in your schema definition ( but not related to this particular query ). You cannot use a "ref" to another collection as String in one collection where it is going to be ObjectId ( the default for _id, and presumed of the hex values obtained ) in the other. This mismatch means .populate() or $lookup functions cannot work. So you really should correct the types.
Unrelated to this. But something you need to fix as a priority.

Categories

Resources