Not valid for storage - javascript

I get the error:
The dollar ($) prefixed field '$size' in 'analytics.visits.amounts..$size' is not valid for storage.
return Manager.updateMany({},
{
$push: {
"analytics.visits.amounts": {$size: "$ips"},
"analytics.visits.dates": new Date()
}
}
).exec()
What I try to do is I have an array called ips and I have another array amounts and I try to push the size of ips into the amounts array
The structure of a single document is:
{
offices: [{
ips: []
}],
analytics: {
visits: {
amounts: [],
dates: []
}
}
}

The normal update syntax can not use document variable values, meaning the $ips is what's throwing you the error.
For Mongo v4.2+ they introduced pipelined updates which allows you to use document values in an update, like so:
db.collection.updateMany(
{},
[
{
$set: {
"analytics.visits.amounts": {
$concatArrays: [
"$analytics.visits.amounts",
[
{
$reduce: {
input: "$offices",
initialValue: 0,
in: {
$sum: [
{
$size: "$$this.ips"
},
"$$value"
]
}
}
}
]
]
},
"analytics.visits.dates": {
$concatArrays: [
"$analytics.visits.dates",
[
new Date()
]
]
},
}
}
])
Mongo Playground
If you're using an older Mongo version then you will have to read the documents into memory in order to fetch the ips values to use in the update.

Related

How to filter an array of objects to remove elements based on condition in MongoDB aggregate?

I have a collection of documents ChatRooms in MongoDB that has this (simplified) structure:
{
_id: ObjectId('4654'),
messages: [
{
user: ObjectId('1234'),
sentAt: ISODate('2022-03-01T00:00:00.000Z')
},
{
user: ObjectId('1234'),
sentAt: ISODate('2022-03-02T00:00:00.000Z')
},
{
user: ObjectId('8888'),
sentAt: ISODate('2022-03-03T00:00:00.000Z')
},
]
}
What I'm trying to achieve is to filter the messages array inside the aggregate pipeline in order to get an array where the userId is presend just once. The result I'm looking for is (or something similar but the array shouldn't have two elements with the same user id):
{
_id: ObjectId('4654'),
messages: [
{
user: ObjectId('1234'),
sentAt: ISODate('2022-03-01T00:00:00.000Z')
},
{
user: ObjectId('8888'),
sentAt: ISODate('2022-03-03T00:00:00.000Z')
},
]
}
Is such a thing possible even?
Any help would be much appreciated.
You can do this in several different ways, here is an example of how to achieve this using the $reduce operator:
db.collection.aggregate([
{
$addFields: {
messages: {
$reduce: {
input: "$messages",
initialValue: [],
in: {
$cond: [
{
$in: [
"$$this.user",
"$$value.user"
]
},
"$$value",
{
"$concatArrays": [
"$$value",
[
"$$this"
]
]
}
]
}
}
}
}
}
])
Mongo Playground

Reduce mongodb aggregation with condition

I have an array of objects call "extra" with different properties: some objects have "plus" and some haven't.
I want to create inside this "extra" array, 2 different arrays one called "cheap" with all the object that don't have the "plus" property and one called "exp" with only the objects with the "plus" property.
I think I can use the $reduce method in mongodb aggregate with $concatArrays and check with $cond if the property plus exists or not.
Something like that:
Data example:
{
extra: [
{
description: "laces",
type: "exterior",
plus: '200'
},
{
description: "sole",
type: "interior"
},
{
description: "logo",
type: "exterior"
},
{
description: "stud",
type: "exterior",
plus: '450'
}
],
}
{
$project: {
extra: {
$reduce: {
input: ['$extra'],
initialValue: {cheap: [], exp: []},
$cond: {
if: {$eq: ['$$this.plus', null]},
then: {
in: {
cheap: {
$concatArrays: ['$$value.cheap', '$$this'],
},
},
},
else: {
in: {
exp: {
$concatArrays: ['$$value.exp', '$$this'],
},
},
},
},
},
},
},
}
It doesn't work...I tried many ways or writing the $cond part without luck.
I can't figure it out.
Thank you all.
K.
Apart from some minor syntax issues you've had another problem is your understand of the $ne operator.
In this case you expect a missing value to be equal to null, this is not how Mongo works. so for a document:
{ name: "my name" }
The aggregation query:
{ $cond: { $eq: ["$missingField", null] } }
Will not give true as you expect as missing is not equal to null. I took the liberty to fix the syntax issues you've had, this working pipeline is the way to go:
db.collection.aggregate([
{
$project: {
extra: {
$reduce: {
input: "$extra",
initialValue: {
cheap: [],
exp: []
},
in: {
cheap: {
"$concatArrays": [
"$$value.cheap",
{
$cond: [
"$$this.plus",
[],
[
"$$this"
],
]
}
]
},
exp: {
"$concatArrays": [
"$$value.exp",
{
$cond: [
"$$this.plus",
[
"$$this"
],
[]
]
}
]
}
}
},
},
},
}
])
Mongo Playground
One thing to note is that $cond evaluates the plus field, meaning if the field does exist with a null value or a 0 value then it will consider this document matched for the cheap array. This is something to consider and change in case these are possible.

Query access variable outside query based on condition in NodeJS and MongoDB

I have a schema like below:
[
{
"_id": 1,
"showResult": true,
"subject": "History",
},
{
"_id": 2,
"showResult": false,
"subject": "Math",
}
]
and an object in JS like below:
result = {
"History": 22,
"Math": 18
}
I am using aggregate to process query, in between i need to find score based on subject field in the document if showResult field is true i.e to access result variable inside query as map result[$subject]
My query:
db.collection.aggregate([
{
"$project": {
_id: 1,
"score":{$cond: { if: { $eq: [ "$showResult", true ] }, then: subjectObj[$subject], else: null }}
}
}
])
can this be done in MongoDB, i want result like below:
{
_id: 1,
score: 22
}
I think query is little costly than JS code, but i am adding the query if it will help you as per your question,
$match showResult is true
$project to show required fields, $reduce to iterate loop of result after converting from object to array using $objectToArray, check condition if subject match then return matching score
let result = {
"History": 22,
"Math": 18
};
db.collection.aggregate([
{ $match: { showResult: true } },
{
$project: {
_id: 1,
score: {
$reduce: {
input: { $objectToArray: result },
initialValue: 0,
in: {
$cond: [{ $eq: ["$$this.k", "$subject"] }, "$$this.v", "$$value"]
}
}
}
}
}
])
Playground

updateMany() on array

In my application i have a collection called Blog and i run this query every 24h
await Blog.updateMany({}, [
{
$set: {
viewed: {
$add: [{ $size: "$visitorIps" }, "$viewed"]
},
visitorIps: []
}
}
]);
My problem is that i have a second collection called Users.
Inside of Users i have an array called posts and here are all posts from that user saved.
{
_id: 234klj2รถ34,
user: "Max",
posts: [
{
_id: 5dgewef323523,
name: "My first blogpost",
content: "...",
viewed: 0,
visitorIps: ["192.168.23.12"]
}
]
}
Now i need the same query on my second collection for each array. How do i do it? I tried something like this but it doesnt worked:
await User.updateMany({}, [
{
$set: {
"posts.$[].viewed: {
$add: [{ $size: "posts.$[].visitorIps" }, "posts.$[].viewed"]
},
"posts.$[].visitorIps": []
}
}
]);
But thats completely wrong. Could somebody help me here out?
You can try using $map,
your logic and code remain same for viewed and visitorIps
$mergeObjects will merge current cursor fields and viewed and visitorIps that we have calculated
await User.updateMany({},
[{
$set: {
posts: {
$map: {
input: "$posts",
as: "post",
in: {
$mergeObjects: [
"$$post",
{
"viewed": {
$add: [{ $size: "$$post.visitorIps" }, "$$post.viewed"]
},
"visitorIps": []
}
]
}
}
}
}
}]
)

MongoDB aggregation lookup additional field from localField to result

I have the following collections in MongoDB (mongoplayground) [characters,guilds]
I want to make a $lookup, that will add rank field to the resulting document, like that:
"members_t": [
{
"_id": ObjectId("5a934e000102030405000000"),
"level": 20,
"name": "test1",
"rank": 1
},
{
"_id": ObjectId("5a934e000102030405000001"),
"level": 40,
"name": "test2",
"rank": 2
}
]
old $lookup syntax can't help me with that, but the following query with the new syntax returns me an empty array in tested field (even without $addFields stage):
{
$lookup: {
from: "characters",
let: {
members_name: "$members.name",
rank: "$members.rank"
},
pipeline: [
{
$match: {
name: "$$members_name"
}
}
],
as: "tested"
}
}
So, is there any option to add an additional field after $lookup stage or not?
(Mongo -v 4.2.3, so the problem is not related with new syntax support)
You were almost close to solving it. Since the members are an array, you need to pass it through $lookup and then conditionally join it per each character with $reduce (You can do this with $filter as well, but then we need aditional stages).
Note: Explanation why we need to use $expr inside $lookup pipeline.
Try this one:
db.guilds.aggregate([
{
$lookup: {
from: "characters",
let: {
members: "$members"
},
pipeline: [
{
$match: {
$expr: {
$in: [
"$name",
"$$members.name"
]
}
}
},
{
$addFields: {
rank: {
$reduce: {
input: "$$members",
initialValue: null,
in: {
$cond: [
{
$eq: [
"$$this.name",
"$name"
]
},
"$$this.rank",
"$$value"
]
}
}
}
}
}
],
as: "members_t"
}
}
])
MongoPlayground

Categories