Mongodb how to update more than one array documents - php

Can somebody tell me please how to update more than one element in array?
I have object like:
{
_id: 1,
name: 'x',
someArray: [
{'a': 1},
{'a': 1, 'b': 2},
{'a': 2}
]
}
I would like to update all elements in someArray where 'a' == 1.
I tried to do it via command
db.collection.update(
{_id: 1, 'somaArray.a': 1},
{$set: {'someArray.$.c': 3}},
{multi: true}
)
but this command updated only one element in someArray. The second one is not updated. Result seems like:
{
_id: 1,
name: 'x',
someArray: [
{'a': 1, 'c': 3},
{'a': 1, 'b': 2},
{'a': 2}
]
}
How to achieve update of all elements which match the condition?
Thank you.

Try as below: (Read)
db.collection.update({_id:1},
{
$set: {
'someArray.$[elem].c': 3
}
},
{ arrayFilters: [{ "elem.a": 1 }], multi: true, "upsert": true }
)
Result response will be as below:
{
"_id" : 1,
"name" : "x",
"someArray" : [
{
"a" : 1,
"c" : 3
},
{
"a" : 1,
"b" : 2,
"c" : 3
},
{
"a" : 2
}
]
}

i think this question is similar to yours
visit (How to Update Multiple Array Elements in mongodb)! and check it out

Related

PHP Collection flatten object only if it contains a key

I want to flatten some items in an array if they contain a key of items so for example the following object:
{
"key1": {
"order": 1,
"name": "Test"
},
"group1": {
"order": 1,
"name": "Test",
"items": {
"key2": {
"order": 1,
"name": "Test"
},
"key3": {
"order": 1,
"name": "Test"
}
}
}
}
Should be flattened to be formatted like follows, so basically removing the items within the group to the root level:
{
"key1": {
"order": 1,
"name": "Test"
},
"key2": {
"order": 1,
"name": "Test"
},
"key3": {
"order": 1,
"name": "Test"
}
}
I have put this object into a Illuminate\Database\Eloquent\Collection so I can use flatMap. flatten doesn't work, as I don't want to completely flatten it.
Assuming you are using Laravel, did you take a resource and collection in consideration? You could also try the Laravel collapse() method which can be found here: collapse method on collections
$collection = collect([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]);
$collapsed = $collection->collapse();
$collapsed->all();
// [1, 2, 3, 4, 5, 6, 7, 8, 9]

Mongodb nested document where clause returns many subdocuments

I have a mongodb document that looks similar to this:
{
"id": 1,
"title": "This is the title",
"body" : "This is the body",
"comments": [
{
"email_address": "mirko.benedetti#somemail.com",
"name": "Mirko",
"surname": "Benedetti",
"language": "it",
"text": "This is a message",
"published": "Y",
"on": "2014-03-22 15:04:04"
},
{
"email_address": "marc.surname#somemail.com",
"name": "Marc",
"surname": "Surname",
"language": "it",
"text": "Another Message",
"published": "N",
"on": "2014-03-23 15:04:05"
}
]
}
And I have a query like this:
$this->db->collection->find(array('id' => $id, 'language' => $lang, 'comments.published' => 'Y'),
array('comments.name' => 1, 'comments.surname' => 1, 'comments.text' => 1, 'comments.on' => 1, '_id' => 0));
My problem is that running that query, mongodb returns both comments, which I don't want, I want only the message with "published": "Y".
I tried for example to run 'comments.published' => 'something' and none comment is selected, which is correct, but if at least one of the comments has
the flag "published" set to 'Y', both comments are showed.
Any help will be welcome.
Look at $elemMatch documentation
db.schools.find( { zipcode: "63109" },
{ students: { $elemMatch: { school: 102 } } } )
You need to be careful while using the elemMatch operator. First thing it has two variants. $elemMatch(projection) & $elemMatch(query)
The elemMatch(projection) variant appears to working because the filter criteria you have only matches to one value in comments array.
The below query will work fine.
find({'_id' : ObjectId("582f2abf9b549b5a765ab380"), comments: { $elemMatch: { language: "it", published : "Y" }}})
Now consider when you have more than 1 matching values (two values with 'Y' published status) in comments arrays, then the above query will not work and will only return the first matching value.
In this scenario, you will need to use $filter, which will filter the comments array based on the filter crtieria passed.
aggregate([{
$match: {
'_id': ObjectId("582f2abf9b549b5a765ab380")
}
}, {
"$project": {
"comments": {
"$filter": {
"input": "$comments",
"as": "result",
"cond": {
$and: [{
$eq: ["$$result.language", "it"]
}, {
$eq: ["$$result.published", "Y"]
}]
}
}
}
}
}, {
$project: {
"comments": {
name: 1,
surname: 1,
text: 1,
on: 1
}
}
}])

MongoDB aggregate sub-array as group _id

I'm having some trouble using the MongoDB aggregation framework to count event types in my database. How do I calculate the sum of the value.count field for each unique 3rd index of the _id.val field?
The basic structure of my data looks like:
{ _id: { evt: "click", val: [ "default", "125", "311", "1" ] }, value: { count: 1 } }
{ _id: { evt: "click", val: [ "default", "154", "321", "2" ] }, value: { count: 2 } }
{ _id: { evt: "click", val: [ "default", "192", "263", "1" ] }, value: { count: 4 } }
The values in the val field denote ["type","x","y","time"], respectively.
I'm trying to extract the 3rd index, or time value of the _id.val key. The output I'm looking to achieve:
1: 5
2: 2
I've been trying to do it via this PHP:
$ops2 = array(
array(
'$match' => $q2
),
array(
'$group' => array(
'_id' => array(
'evt' => '$_id.evt',
'time' => '$_id.val.3'
),
'count' => array('$sum' => '$value.count' )
)
)
);
But it doesn't appear to like the 3 index in the group array
The data you are working with looks like it has come as the output of a mapReduce operation already, since it has that specific "_id" and "value" structure that mapReduce prodcues. As such you may be better off going back to the logic of how that process is implemented and follow the same to just extract and total what you want, or at least change it's output form to this:
{
_id: {
evt: "click",
val: { "type": "default", "x": "125", "y": "311", "time": "1" }
},
value: { count: 1 }
},
{
_id: {
evt: "click",
val: { "type": "default", "x": "154", "y": "321", "time": "2" }
},
value: { count: 2 }
},
{
_id: {
evt: "click",
val: { "type": "default", "x": "192", "y": "263", "time": "1" }
},
value: { count: 4 }
}
As the problem is that the aggregation framework "presently" lacks the ability to address the "indexed" position of an array ( real "non-associative" array and not PHP array ) and would always return null when you try to do so.
Lacking the ability to go back to the original source or mapReduce operation, then you can write a mapReduce operation on this data to get the expected results ( shell representation since it's going to be a JavaScript anyway ):
db.collection.mapReduce(
function() {
emit({ evt: this._id.evt, time: this._id.val[3] }, this.value.count)
},
function(key,values) {
return Array.sum(values)
},
{ out: { inline: 1 } }
)
Which returns typical mapReduce output like this:
{
"_id" : {
"evt" : "click",
"time" : "1"
},
"value" : 5
},
{
"_id" : {
"evt" : "click",
"time" : "2"
},
"value" : 2
}
If you were able to at least transform the current output collection to the form suggested at first above, then you would run with the aggregation framework like this instead ( again common representaion ):
{ "$group": {
"_id": {
"evt": "$_id.evt",
"time": "$_id.val.time"
},
"count": { "$sum": "$value.count" }
}}
Which of course would yield from the altered data:
{ "_id" : { "evt" : "click", "time" : "2" }, "count" : 2 }
{ "_id" : { "evt" : "click", "time" : "1" }, "count" : 5 }
In future releases of MongoDB, there will be a $slice operator which allows the array handling, so with your current structure you could do this instead:
{ "$group": {
"_id": {
"evt": "$_id.evt",
"time": { "$slice": [ "$_id.val", 3,1 ] }
},
"count": { "$sum": "$value.count" }
}}
Which allows picking of the "third" index element from the array, albeit that this will of course still return an "array" as the element like this:
{ "_id" : { "evt" : "click", "time" : [ "2" ] }, "count" : 2 }
{ "_id" : { "evt" : "click", "time" : [ "1" ] }, "count" : 5 }
So right now, if you can change your initial mapReduce output then do it. Either to the form as shown here or just work with modifications to the initial query to get the end result you want here. Modifying to the recommened form will at least allow the .aggregate() command to work as is shown in the second example here.
If not, then mapReduce is still the only way at present for writing, as shown in the "first" example.
At first, I think you may have something wrong in your understanding of Mongo...Because each document in mongo should have its unique _id, to identify itself from others. So I have add a _id to each object, and change your origin "_id" field to "data". Now the structure is:
/* 1 */
{
"_id" : "ubLrDptWvJE7LZqDF",
"data" : {
"evt" : "click",
"val" : [ "default", "125", "311", "1" ]
},
"value" : {
"count" : 1
}
}
/* 2 */
{
"_id" : "C2QCEhvCsp3xG6EKZ",
"data" : {
"evt" : "click",
"val" : [ "default", "154", "321", "2" ]
},
"value" : {
"count" : 2
}
}
/* 3 */
{
"_id" : "bT72z7gMKoyX5JfHL",
"data" : {
"evt" : "click",
"val" : [ "default", "192", "263", "1" ]
},
"value" : {
"count" : 4
}
}
I am not sure how to do this query in PHP, Because I only know a little PHP...... But I could give you an example of using aggregation in Javascript, its code and output are as follows:
Here are some useful link: using mongo in PHP
I wish it can help you solve your problem perfectly :-)

Mongo Nested queries

I need to query based on key's that all equal the same thing, let's say the following is an order, and that order contains three products. I need to query orders, that have products, that have a specific status all equal to true. Each product is stored, with a mongo id as its key, so I don't actually know it's key name, Example: (obviously I've shortened the keys)
{
"_id" : "foo",
"products": {
"123": {
"status": {
"a": false,
"b": true,
"c": true,
},
},
"213": {
"status": {
"a": true,
"b": true,
"c": true,
},
},
"321": {
"status": {
"a": false,
"b": false,
"c": true,
},
}
},
}
Here's what I've tried:
$this->database->$collection->find(
array('_id' => 'foo', 'products.$.status.c' => true)
);
I'd expect the above, to return the complete order in the example, as the status 'c' inside each product is true, if I were to perform the same query, but with 'a' or 'b' as the status query, it wouldn't return it.
I'm not really sure how to do this, obviously the above didn't work, so my question is, how can I match on multiple sub keys of an array of objects that I do not know the key name?
You want an array of documents, not an object with the id as its key. The $ positional operator doesn't work on objects.
{
"_id" : "foo",
"products": [
{
id: "123"
"status": {
"a": false,
"b": true,
"c": true,
},
},
{
id: "213"
"status": {
"a": true,
"b": true,
"c": true,
},
},
{
id: "321"
"status": {
"a": false,
"b": false,
"c": true,
},
}
]
}
Your query needs to look like this:
$this->database->$collection->find(
array('_id' => 'foo', 'products.status.c' => true)
);
This returns the cursor at the first 'product' that matches your query
and on udpate.
$this->database->$collection->find(
array('_id' => 'foo', 'products.status.c' => true),
array('products.$.hello' => 'world')
);
This updates only that sub document. The only time you should use objects is if you know what the keys will be. Like status, or address... but if key is a variable as well, it gets really tricky, really quickly.

PHP MongoDB SUM all columns

Is there anyway in which MongoDB aggregate framework can be used to sum up all the columns or an array of columns rather than just one?
Each row has a 'category id'. I'm hoping to filter by a specific category id and then output a row which is the sum of each of the columns. As I wont be needing all of the columns, it would be better if I could specify columns to increase performance.
I have seen examples of this done with 1 column however not with multiple columns.
Below is example data
{ "type" : "A", "a" : 1, "b" : 2, "c" : 3, "d" : 4 }
{ "type" : "A", "a" : 10, "b" : 20, "c" : 30, "d" : 40 }
{ "type" : "B", "a" : 1, "b" : 2, "c" : 3, "d" : 4 }
{ "type" : "B", "a" : 10, "b" : 20, "c" : 30, "d" : 40 }
The result therefore when the type is B would be
{ "type" : "B", "a" : 11, "b" : 22, "c" : 33, "d" : 44 }
As can be seen, each of the columns have been summed up, and a single row is returned with the sum of each column.
I am using PHP and the latest version of MongoDB with Debian
Thanks
We don't have your data for a clear example, but you seem to be basically looking for the $add operator which "add" a list of supplied values together. So given a sample of documents:
{ "type" : "A", "a" : 1, "b" : 2, "c" : 3, "d" : 4 }
{ "type" : "A", "a" : 10, "b" : 20, "c" : 30, "d" : 40 }
{ "type" : "B", "a" : 1, "b" : 2, "c" : 3, "d" : 4 }
{ "type" : "B", "a" : 10, "b" : 20, "c" : 30, "d" : 40 }
There is this example to "add" all the "columns" together for "a", "b" and "c" only. Also grouping by "type":
db.collection.aggregate([
{ "$group": {
"_id": "$type",
"total": { "$sum": { "$add": [ "$a", "$b", "$c" ] } }
}}
])
You can also use $add with $project if you do not need to $group or are otherwise working with data at a different level.
The result on this data would be:
{ "_id" : "B", "total" : 66 }
{ "_id" : "A", "total" : 66 }
What you are actually asking though is just doing a $sum for each column:
db.collection.aggregate([
{ "$group": {
"_id": "$type",
"a": { "$sum": "$a" },
"b": { "$sum": "$b" },
"c": { "$sum": "$c" },
"d": { "$sum": "$d" }
}}
])
Which produces:
{ "_id" : "B", "a" : 11, "b" : 22, "c" : 33, "d" : 44 }
{ "_id" : "A", "a" : 11, "b" : 22, "c" : 33, "d" : 44 }

Categories