What is the solution to "Too many positional (i.e. '$') elements found in path"? - mongodb

I have searched over the web for this issue, the conclusion where I have reached is that it is a bug in the latest version of mongo as told here and here
The problem is that the structure of the object is as following:
{
"_id": 2,
"status": 0,
"details": [{
"id": 2,
"category": "A",
"obj": {
"apple": 5,
"banana": 2,
"cherry": 10
},
"members": [{
"id": 3,
"category": "A",
"obj": {
"apple": 5,
"banana": 2,
"cherry": 10
}
},
{
"id": 4,
"category": "A",
"obj": {
"apple": 5,
"banana": 2,
"cherry": 10
}
}
]
}]
}
which I am using everywhere in my project which is almost ready. But now the requirement is to update the obj inside the members object. For which I tried
cond := bson.M{ "$and": []bson.M{ bson.M{"details":bson.M{ "$elemMatch": bson.M{ "category": "A"} } }, bson.M{"status":0} } }
query := bson.M{ "$set": bson.M{ "details.$.members.$.obj.guava":15 } }
_, err := models.DbUpdateAll(Collection, cond, query)
But it is not working. Do I need to change the whole structure of the document by maintaining the array in different collection and passing reference to this object? It will be really a bad idea as for now where the project stands.... Isn't there any easy solution?

Related

MongoDB field must be an array

Currently I have a collection with the following documents:
[
{
"_id": ObjectId("628e6bd640643f97d6517c75"),
"company": "bau",
"current_version": 0,
"form_name": "don't know",
"history": [],
"id": "23421123-24a9-4a45-a12f-27a330152ax3",
"is_active": True,
"user_id": "999",
},
{
"_id": ObjectId("628eaffe4b8ae2ccdeb9305c"),
"company": "vrau",
"current_version": 0,
"form_name": "exemplo",
"history": [
{
"content": [
{
"field_id": 0,
"label": "insira um texto",
"placeholder": "qualquer texto",
"type": "text",
}
],
"layout": [
{"field_id": 0, "h": 10, "type": "text", "w": 100, "x": 0, "y": 0}
],
"responses": [
{
"client_id": 100,
"response_date": "2020-01-02",
"values": [{"field_id": 0, "value": "um texto"}],
},
{
"client_id": 2,
"response_date": "2020-01-01",
"values": [{"field_id": 0, "value": "roi"}],
},
],
"version": 0,
}
],
"id": "33b66684-24a9-4a45-a12f-27a330152ac8",
"is_active": True,
"user_id": "1",
},
]
I want to change the response fromthe client_id = '2' by I'm receiving the following error:
pymongo.errors.WriteError: The field 'history.0.responses.1' must be an array but is of type object in document {_id: ObjectId('628eaffe4b8ae2ccdeb9305c')}, full error: {'index': 0, 'code': 2, 'errmsg': "The field 'history.0.responses.1' must be an array but is of type object in document {_id: ObjectId('628eaffe4b8ae2ccdeb9305c')}"}
I don't know what I'm doing wrong and this error doesnt make sense to me cuz reponses is an array.
my current query:
collection.update_many(
{"id": "33b66684-24a9-4a45-a12f-27a330152ac8", "history.version": 0},
{
"$push": {
"history.$[h].responses.$[r]": {
"client_id": 2,
"response_date": "2020-01-01",
"values": [{"field_id": 0, "value": "roi"}],
}
}
},
array_filters=[{"h.version": 0}, {"r.client_id": "2"}],
)
Is there another to do it?
It is because you are also performing filter on r, which already resolves to object level in responses array.
You can simply abandon the r arrayFilter if you simply want to push to responses array.
collection.update_many(
{"id": "33b66684-24a9-4a45-a12f-27a330152ac8", "history.version": 0},
{
"$push": {
"history.$[h].responses": {
"client_id": 2,
"response_date": "2020-01-01",
"values": [{"field_id": 0, "value": "roi"}],
}
}
},
array_filters=[{"h.version": 0}],
)
Here is the Mongo playground for your reference. (in native js syntax)
You should use $set instead of $push if you want to update the entry instead of adding an entry. In your given example, the client_id is int while your arrayFilter is string. It could cause problem if it is not intended.
collection.update_many(
{"id": "33b66684-24a9-4a45-a12f-27a330152ac8", "history.version": 0},
{
"$set": {
"history.$[h].responses.$[r]": {
"client_id": 2,
"response_date": "2020-01-01",
"values": [{"field_id": 0, "value": "roi"}],
}
}
},
array_filters=[{"h.version": 0}, {"r.client_id": 2}],
)
Here is the Mongo playground for your reference. (in native js syntax)

Grouping multiple documents with nested array of objects in MongoDB

I'm having documents that are having this structures
x = {
"scalar": 1,
"array": [
{"key": 1, "value": 2},
{"key": 2, "value": 3},
],
"array2": [
{"key": 1, "value": 2},
{"key": 2, "value": 3},
],
}
and
y = {
"scalar": 2,
"array": [
{"key": 1, "value": 3},
{"key": 3, "value": 0},
],
"array2": [
{"key": 1, "value": 3},
{"key": 3, "value": 0},
],
}
The end results I'm trying to find is this
{
"scalar": 3, # SUM of scalar
"array": [
{"key": 1, "value": 5}, # SUM by key = 1
{"key": 2, "value": 3},
{"key": 3, "value": 0},
],
"array2": [
{"key": 1, "value": 5}, # SUM by key = 1
{"key": 2, "value": 3},
{"key": 3, "value": 0},
],
}
I've tried to use double $unwind and then do push by. I'm thinking of using $reduce to get the final results
Query
one way to do it, is by facet, you want 3 groupings and facet can do that , like break into 3 seperate parts, to not mix the unwinds, i think this is the most simple way to do it
Test code here
db.collection.aggregate([
{
"$facet": {
"scalar": [
{
"$project": {
"scalar": 1
}
},
{
"$group": {
"_id": null,
"sum": {
"$sum": "$scalar"
}
}
},
{
"$unset": [
"_id"
]
}
],
"array": [
{
"$project": {
"array": 1
}
},
{
"$unwind": {
"path": "$array"
}
},
{
"$group": {
"_id": "$array.key",
"sum": {
"$sum": "$array.value"
}
}
},
{
"$project": {
"_id": 0,
"key": "$_id",
"value": "$sum"
}
}
],
"array2": [
{
"$project": {
"array2": 1
}
},
{
"$unwind": {
"path": "$array2"
}
},
{
"$group": {
"_id": "$array2.key",
"sum": {
"$sum": "$array2.value"
}
}
},
{
"$project": {
"_id": 0,
"key": "$_id",
"value": "$sum"
}
}
]
}
},
{
"$set": {
"scalar": {
"$arrayElemAt": [
"$scalar.sum",
0
]
}
}
}
])
Other alternative is to unwind both arrays, but then unwinds and groups will be mixed, making things complicated i think.
Also $reduce cant be used for grouping in MongoDB i think, because we can't construct dynamic paths.
If group-reduce and have this data (key=key value=value)
{"1" : 5 , "2" : 3}
And we see {"key" 1, "value" : 5} how we can check if the above data contains the 1 as key? We cant construct dynamic paths, like $$this.1 . Only way it to convert it to an array and back to object that will be so slow.

MongoDB Aggregation lookup how often Document is mentioned in other Collection

I need to know how often a Document from Collection A is mentioned in Collection B. I am currently doing this with a $lookup aggregation and the size of the resulting array, but I guess that there is a much nicer way to do that?
Example:
Collection A
{ "_id": 1, "name": "User 1" }
{ "_id": 2, "name": "User 2" }
Collection B
{ "_id": 1, "user": 1, ... }
{ "_id": 2, "user": 1, ... }
{ "_id": 3, "user": 2, ... }
Desired result:
{ "_id": 1, "name": "User 1", "mentions": 2 }
{ "_id": 2, "name": "User 2", "mentions": 1 }

mongodb: delete object within an array, nested within two objects

I am trying to delete an object within an array. But that array is nested within two objects. How do I do this?
For example: let's say I want to delete the transaction with
_id: 58c3154a19f82c0ddc53f0de
How would I do this??
{
"_id": {
"$oid": "58bad6cf93ab9703da331e25"
},
"username": "david.lam#transfast.com",
"password": "sha1$fc05ad7d$1$1cc86a287642516f947fda520ae8ddd42e983e23",
"firstName": "David",
"lastName": "David",
"transactions": {
"2017": {
"3": [
{
"where": "Duane Reade",
"what": "asdf",
"category": 6,
"amount": "34",
"_id": {
"$oid": "58c300ef1602f90c7166cbfb"
},
"date": {
"day": 10,
"month": 3,
"year": 2017
}
},
{
"where": "Amazon",
"what": "asdf",
"category": 2,
"amount": "100",
"_id": {
"$oid": "58c3154a19f82c0ddc53f0de"
},
"date": {
"day": 10,
"month": 3,
"year": 2017
}
}
]
}
}
}
To do so you need to use the $pull operator. Address array elements with the dot notation. The following query does the job:
db.user1.update({},
{$pull: {'transactions.2017.3': {_id: ObjectId("58c3154a19f82c0ddc53f0de")}}
});

Finding documents that are valid on a specific date

I have some data stored in a mongodb collection similar to:
{"_id": 1, "category": "food", "name": "chips", "price": 1.50, "effectiveDate": ISODate("2013-03-01T07:00:00Z")}
{"_id": 2, "category": "food", "name": "chips", "price": 1.75, "effectiveDate": ISODate("2013-03-05T07:00:00Z")}
{"_id": 3, "category": "food", "name": "chips", "price": 1.90, "effectiveDate": ISODate("2013-03-10T07:00:00Z")}
{"_id": 4, "category": "beverage", "name": "pop", "price": 2.00, "effectiveDate": ISODate("2013-03-01T07:00:00Z")}
{"_id": 5, "category": "beverage", "name": "pop", "price": 2.25, "effectiveDate": ISODate("2013-03-05T07:00:00Z")}
{"_id": 6, "category": "beverage", "name": "pop", "price": 1.80, "effectiveDate": ISODate("2013-03-10T07:00:00Z")}
In mongodb, how would I go about writing a query that would return the documents that were active on a specific date, grouped by the category?
If I specified March 6, 2013 I'd expect to see the results:
{"_id": 2, "category": "food", "name": "chips", "price": 1.75, "effectiveDate": ISODate("2013-03-05T07:00:00Z")}
{"_id": 5, "category": "beverage", "name": "pop", "price": 2.25, "effectiveDate": ISODate("2013-03-05T07:00:00Z")}
I am new to mongo and have been trying to do this using group, aggregate and mapreduce but have been just spinning in circles.
To give you a real good answer, I need more details of your code and what you are trying to do. But if I understand right, I think you can solve that using only the aggregation framework. You should know that the aggregation framework uses the pipeline concept, in other words, the result of each step is used as the entry to the following.
My query:
db.yourcollection.aggregate([
/* First exclude everything that is superior to a given date */
{$match:{effectiveDate:{$lte:new Date(2013,2,6)}}},
/* Sort the rest by date, descending */
{$sort:{effectiveDate:-1}},
/* Group by name+category and grab only the first result
(the newest below that date) */
{$group:{_id:{name:'$name',category:'$category'}, effectiveDate:{$first:"$effectiveDate"},price:{$first:"$price"}}},
/* You said you want the results grouped by category.
This last $group does that and returns all matching products inside an array
It also removes the duplicates */
{$group:{_id:'$_id.category',products:{$addToSet:{name:"$_id.name",price:"$price",effectiveDate:"$effectiveDate"}}}}
])
The output is like this:
{
"result": [
{
"_id": "food",
"products": [
{
"name" : "chips",
"price" : 1.75,
"effectiveDate" : ISODate("2013-03-05T07:00:00Z")
}
]
},
{
"_id" : "beverage",
"products": [
{
"name" : "pop",
"price" : 2.25,
"effectiveDate" : ISODate("2013-03-05T07:00:00Z")
}
]
}
],
"ok":1
}
You can change the final output modifing the last $group or using a $project