MongoDB find() in multidimensional array and $unset [duplicate] - php

Here is array structure
contact: {
phone: [
{
number: "+1786543589455",
place: "New Jersey",
createdAt: ""
}
{
number: "+1986543589455",
place: "Houston",
createdAt: ""
}
]
}
Here I only know the mongo id(_id) and phone number(+1786543589455) and I need to remove that whole corresponding array element from document. i.e zero indexed element in phone array is matched with phone number and need to remove the corresponding array element.
contact: {
phone: [
{
number: "+1986543589455",
place: "Houston",
createdAt: ""
}
]
}
I tried with following update method
collection.update(
{ _id: id, 'contact.phone': '+1786543589455' },
{ $unset: { 'contact.phone.$.number': '+1786543589455'} }
);
But it removes number: +1786543589455 from inner array object, not zero indexed element in phone array. Tried with pull also without a success.
How to remove the array element in mongodb?

Try the following query:
collection.update(
{ _id: id },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
It will find document with the given _id and remove the phone +1786543589455 from its contact.phone array.
You can use $unset to unset the value in the array (set it to null), but not to remove it completely.

You can simply use $pull to remove a sub-document.
The $pull operator removes from an existing array all instances of a value or values that match a specified condition.
Collection.update({
_id: parentDocumentId
}, {
$pull: {
subDocument: {
_id: SubDocumentId
}
}
});
This will find your parent document against given ID and then will remove the element from subDocument which matched the given criteria.
Read more about pull here.

In Mongoose:
from the document:
To remove a document from a subdocument array we may pass an object
with a matching _id.
contact.phone.pull({ _id: itemId }) // remove
contact.phone.pull(itemId); // this also works
See Leonid Beschastny's answer for the correct answer.

To remove all array elements irrespective of any given id, use this:
collection.update(
{ },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);

To remove all matching array elements from a specific document:
collection.update(
{ _id: id },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
To remove all matching array elements from all documents:
collection.updateMany(
{ },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);

Given the following document in the profiles collection:
{ _id: 1, votes: [ 3, 5, 6, 7, 7, 8 ] }
The following operation will remove all items from the votes array that are greater than or equal to ($gte) 6:
db.profiles.update( { _id: 1 }, { $pull: { votes: { $gte: 6 } } } )
After the update operation, the document only has values less than 6:
{ _id: 1, votes: [ 3, 5 ] }
If you multiple items the same value, you should use $pullAll instead of $pull.
In the question having a multiple contact numbers the same use this:
collection.update(
{ _id: id },
{ $pullAll: { 'contact.phone': { number: '+1786543589455' } } }
);
it will delete every item that matches that number. in contact phone
Try reading the manual.

Related

How to get a value of a certain array parameter and put it in another array?

I have the following encoded JSON array
{
"canonList": [{
"deviceId": "Device123",
"deviceModel": "Model123",
"mapList": [{
"alarmStatus": true,
"disabledEndDate": "2020-01-28T15:06:19",
"lastUpdateDate": "2020-01-02T15:06:19",
"ruleDesc": "this is a test description"
}, {
"alarmStatus": true,
"disabledEndDate": "2020-01-28T15:06:19",
"lastUpdateDate": "2020-01-02T15:06:19",
"ruleDesc": "this is a test description 3"
}, {
"alarmStatus": true,
"disabledEndDate": "2020-01-28T15:06:19",
"lastUpdateDate": "2020-01-02T15:06:19",
"ruleDesc": "this is a test description 2"
}]
}, {
"deviceId": "Device1234",
"deviceModel": "Model1234",
"mapList": {
"alarmStatus": true,
"disabledEndDate": "2020-01-28T15:06:19",
"lastUpdateDate": "2020-01-02T15:06:19",
"ruleDesc": "this is a test description 5"
}
}],
"resultCode": 0,
"transactionId": "retrieve_1580400944"
}
I am trying to create an array of just all the values of ruleDesc but I am only getting a null value. The index of the value is dynamic. One thing certain is I need the value inside ruleDesc ...
I've used
$arrayName['canonList']['mapList']['ruleDesc']
but it's only getting the value of the first array.
Any idea?
You need to extract all the mapList entries first, which you can do with array_column. Then you need to check if the maplist value has a ruleDesc key, in which case you add that to your output; otherwise you merge all the ruleDesc from the mapList into the output:
$ruleDesc = array();
foreach (array_column($arrayName['canonList'], 'mapList') as $mList) {
if (isset($mList['ruleDesc'])) {
$ruleDesc[] = $mList['ruleDesc'];
}
else {
$ruleDesc = array_merge($ruleDesc, array_column($mList, 'ruleDesc'));
}
}
print_r($ruleDesc);
Output:
Array
(
[0] => this is a test description
[1] => this is a test description 3
[2] => this is a test description 2
[3] => this is a test description 5
)
Demo on 3v4l.org
As I see you have 2 inner arrays inside your JSON object. The first one is canonList and the second one is mapList so you have to iterate over both of them and add needed values into the result array like this:
$ruleDescs = [];
foreach ($arrayName['cannonList'] as $cannon) {
foreach ($cannon['mapList'] as $map) {
$ruleDescs[] = $map['ruleDesc'];
}
}
print_r($ruleDesc);

How to update an array element in a MongoDB document

I've got a problem updating an array element in MongoDB. This is the structure of a document:
{
"_id" : ObjectId("57e2645e11c979157400046e"),
"site" : "BLABLA",
"timestamp_hour" : 1473343200,
"values" : [
{
"1473343200" : 66
},
{
"1473344100" : 230
},
{
"1473345000" : 479
},
{
"1473345900" : 139
}
]
}
Now I want to update the element with key "1473345900". How can I do this? I've tried:
db.COLLECTIONNAME.update({"values.1473345900": {$exists:true}}, {$set: {"values.$": 0}})
But after that the document looks like:
{
"_id" : ObjectId("57e2645e11c979157400046e"),
"site" : "BLABLA",
"timestamp_hour" : 1473343200,
"values" : [
{
"1473343200" : 66
},
{
"1473344100" : 230
},
{
"1473345000" : 479
},
0
]
}
What I'm doing wrong? I only want to update the value of 1473345900 to any value... I don't want to update the complete element...
Thanks a lot!!!
You need to add an additional query in your update that matches the array element you want to update. A typical query would involve checking for the element's value not equal to the one being updated.
The following example update shows this where the $ positional operator identifies the correct index position of the hash key array element { "1473345900": 139 }. If you try to run the update operation without the $ positional operator:
db.COLLECTIONNAME.update(
{ "values.1473345900": { "$exists": true } },
{ "$set": { "values.1473345900": 0 } }
)
mongo will treat the timestamp 1473345900 as the index position and thus you will get the error
can't backfill array to larger than 1500000 elements
Thus the correct way should be:
var val = 32;
db.COLLECTIONNAME.update(
{ "values.1473345900": { "$ne": val, "$exists": true } },
{ "$set": { "values.$.1473345900": val } }
)

MongoDB save and update array element

I have this current value that I've saved
db.mycol.save({name:'elbren',lastname:'antonio',subjects:{}})
result:
"_id" : ObjectId("576db356332a8fc87bf769be"),
"name" : "elbren",
"lastname" : "antonio",
"subjects" : {
}
What I need is this result:
"_id" : ObjectId("576db356332a8fc87bf769be"),
"name" : "elbren",
"lastname" : "antonio",
"subjects" :
{
{
code: "sub1",
Description: "desc1",
Unit: 3
},
code: "sub2",
Description: "desc2",
Unit: 3
}
What is the best thing to do on this which is I want to add subjects into that _id?
To create the document so that you get the desirable result, just write it directly:
db.mycol.save({name:'elbren',lastname:'antonio',subjects:{
{
code: "sub1",
Description: "desc1",
Unit: 3
},
code: "sub2",
Description: "desc2",
Unit: 3
}})
If you want to change ("fix") the existing document, you have to use update:
db.mycol.update(
{ name : "elbren", lastname : "antonio" },
{ $set: { subjects:{
{
code: "sub1",
Description: "desc1",
Unit: 3
},
code: "sub2",
Description: "desc2",
Unit: 3
}
}}
);
Here { name : "elbren", lastname : "antonio" } is a query that matches the document you want, and the second part is the update parameter.
Use "$push" operator to add array to current object.
// in php e.g:
['$push'=>['fild_value'=>array('mail=>'me#mail.com','tag'=>'personal')]]
More result this:
{
"_id": 12345678,
"fild_value": [
{
"mail":"me#mail.com",
"tag":"personal"
},
{
"mail":"job#mail.com",
"tag":"worker"
}
]
}
MongoDB provide array operators ($push/$pull,$addToSet,$elemMatch,etc...)

MongoDB Ordering by average combined numbers or nested sub arrays

Having some issues working out the best way to do this in MongoDB, arguably its a relation data set so I will probably be slated. Still its a challenge to see if its possible.
I currently need to order by a Logistics Managers' daily average miles across the vans in their department and also in a separate list a combined weekly average.
Mr First setup in the database was as follows
{
"_id" : ObjectId("555cf04fa3ed8cc2347b23d7"),
"name" : "My Manager 1",
"vans" : [
{
"name" : "van1",
"miles" : NumberLong(56)
},
{
"name" : "van2",
"miles" : NumberLong(34)
}
]
}
But I can't see how to order by a nested array value without knowing the parent array keys (these will be standard 0-x)
So my next choice was to scrap that idea just have the name in the first collection and the vans in the second collection with Id of the manager.
So removing vans from the above example and adding this collection (vans)
{
"_id" : ObjectId("555cf04fa3ed8cc2347b23d9"),
"name" : "van1",
"miles" : NumberLong(56),
"manager_id" : "555cf04fa3ed8cc2347b23d7"
}
But because I need show the results by manager, how do I order in a query (if possible) the average miles in this collection where id=x and then display the manager by his id.
Thanks for your help
If the Manager is going to have limited number of Vans, then your first approach is better, as you do not have to make two separate calls/queries to the database to collect your information.
Then comes the question how to calculate the average milage per Manager, where the Aggregation Framework will help you a lot. Here is a query that will get you the desired data:
db.manager.aggregate([
{$unwind: "$vans"},
{$group:
{_id:
{
_id: "$_id",
name: "$name"
},
avg_milage: {$avg: "$vans.miles"}
}
},
{$sort: {"avg_milage": -1}},
{$project:
{_id: "$_id._id",
name: "$_id.name",
avg_milage: "$avg_milage"
}
}
])
The first $unwind step simply unwraps the vans array, and creates a separate documents for each element of the array.
Then the $group stage gets all documents with the same (_id, name) pair, and in the avg_milage field, counts the average value of miles field out of those documents.
The $sort stage is obvious, it just sorts the documents in the descending order, using the new avg_milage field as the sort key.
And finally, the last $project step just cleans up the documents by making appropriate projections, just for beauty :)
A similar thing is needed for your second desired result:
db.manager.aggregate([
{$unwind: "$vans"},
{$group:
{_id:
{
_id: "$_id",
name: "$name"
},
total_milage: {$sum: "$vans.miles"}
}
},
{$sort: {"total_milage": -1}},
{$project:
{_id: "$_id._id",
name: "$_id.name",
weekly_milage: {
$multiply: [
"$total_milage",
7
]
}
}
}
])
This will produce the list of Managers with their weekly milage, sorted in descending order. So you can $limit the result, and get the Manager with the highest milage for instance.
And in pretty much similar way, you can grab info for your vans:
db.manager.aggregate([
{$unwind: "$vans"},
{$group:
{_id: "$vans.name",
total_milage: {$sum: "$vans.miles"}
}
},
{$sort: {"total_milage": -1}},
{$project:
{van_name: "$_id",
weekly_milage: {
$multiply: [
"$total_milage",
7
]
}
}
}
])
First, do you require average miles for a single day, average miles over a given time period, or average miles over the life of the manager? I would consider adding a timestamp field. Yes, _id has a timestamp, but this only reflects the time the document was created, not necessarily the time of the initial day's log.
Considerations for the first data model:
Does each document represent one day, or one manager?
How many "vans" do you expect to have in the array? Does this list grow over time? Do you need to consider the 16MB max doc size in a year or two from now?
Considerations for the second data model:
Can you store the manager's name as the "manager_id" field? Can this be used as a possible unique ID for a secondary meta lookup? Doing so would limit the necessity of a secondary manager meta-data lookup just to get their name.
As #n9code has pointed out, the aggregation framework is the answer in both cases.
For the first data model, assuming each document represents one day and you want to retrieve an average for a given day or a range of days:
db.collection.aggregate([
{ $match: {
name: 'My Manager 1',
timestamp: { $gte: ISODate(...), $lt: ISODate(...) }
} },
{ $unwind: '$vans' },
{ $group: {
_id: {
_id: '$_id',
name: '$name',
timestamp: '$timestamp'
},
avg_mileage: {
$avg: '$miles'
}
} },
{ $sort: {
avg_mileage: -1
} },
{ $project: {
_id: '$_id._id',
name: '$_id.name',
timestamp: '$_id.timestamp',
avg_mileage: 1
} }
]);
If, for the first data model, each document represents a manager and the "vans" array grows daily, this particular data model is not ideal for two reasons:
"vans" array may grow beyond max document size... eventually, although that would be a lot of data
It is more difficult and memory intensive to limit a certain date range since timestamp at this point would be nested within an item of "vans" and not in the root of the document
For the sake of completeness, here is the query:
/*
Assuming data model is:
{
_id: ...,
name: ...,
vans: [
{ name: ..., miles: ..., timestamp: ... }
]
}
*/
db.collection.aggregate([
{ $match: {
name: 'My Manager 1'
} },
{ $unwind: '$vans' },
{ $match: {
'vans.timestamp': { $gte: ISODate(...), $lt: ISODate(...) }
} },
{ $group: {
_id: {
_id: '$_id',
name: '$name'
},
avg_mileage: {
$avg: '$miles'
}
} },
{ $sort: {
avg_mileage: -1
} },
{ $project: {
_id: '$_id._id',
name: '$_id.name',
avg_mileage: 1
} }
]);
For the second data model, aggregation is more straightforward. I'm assuming the inclusion of a timestamp:
db.collection.aggregate([
{ $match: {
manager_id: ObjectId('555cf04fa3ed8cc2347b23d7')
timestamp: { $gte: ISODate(...), $lt: ISODate(...) }
} },
{ $group: {
_id: '$manager_id'
},
avg_mileage: {
$avg: '$miles'
}
names: {
$addToSet: '$name'
}
} },
{ $sort: {
avg_mileage: -1
} },
{ $project: {
manager_id: '$_id',
avg_mileage: 1
names: 1
} }
]);
I have added an array of names (vehicles?) used during the average computation.
Relevant documentation:
$match, $unwind, $group, $sort, $project - Pipeline Aggregation Stages
$avg, $addToSet - Group Accumulator Operators
Date types
ObjectId.getTimestamp

How to conditionally find items by Tag in mongodb

I am new to NoSQL and MongoDB and I am a little puzzled on what type of queries I can do and how to do them. my knowledge is limited to simpler queries
I would like to make what I think its a complicated query within MongoDB instead of using PHP to sort it but I do not know if it is possible or how to do it.
I have a tag field within my collection that is an array. {tag: ["blue","red","yellow","green","violet"]}.
First level problem: Let says I want to find all birds that have the tag blue & yellow & green, where blue is a must have tag and any other colours are optional.
Second level problem: Then I would like to order the query so that the birds that have all the queried colours appear first.
Is it possible to create this query in mongoDB? and if it is How could I do it?
You can use aggregation framework. So for the next dataset:
{ "_id":ObjectId(...), "bird":1, "tags":["blue","red","yellow","green","violet"]}
{ "_id":ObjectId(...), "bird":2, "tags":["red","yellow","green","violet"] }
{ "_id":ObjectId(...), "bird":3, "tags":["blue","yellow","violet"] }
{ "_id":ObjectId(...), "bird":4, "tags":["blue","yellow","red","violet"] }
{ "_id":ObjectId(...), "bird":5, "tags":["blue"] }
we can apply next query:
colors = ["blue","red","yellow","green"];
db.birds.aggregate(
{ $match: {tags: 'blue'} },
{ $project: {_id:0, bird:1, tags:1} },
{ $unwind: '$tags' },
{ $match: {tags: {$in: colors}} },
{ $group: {_id:'$bird', score: {$sum:1}} },
{ $sort: {score:-1} },
{ $project: {bird:'$_id', score:1, _id:0} }
)
and will get result like this:
{
"result" : [
{ "score" : 4, "bird" : 1 },
{ "score" : 3, "bird" : 4 },
{ "score" : 2, "bird" : 3 },
{ "score" : 1, "bird" : 5 }
],
"ok" : 1
}
Most of this you will have to do in your application. In order to find all documents where a bird has the tag "blue", you can do this:
db.collection.find( { tag: "blue" } );
Which colours are optional doesn't matter, as you have to find by the required tag anyway.
After finding them, you need to do a sort. But sorting like you want (by their 3 colours) is not something you can do in MongoDB, and something you will have to do in PHP instead.

Categories