How to do atomic operations like $push $pull $set etc. in Ecto MongoDB - mongodb

I'm using mongodb_ecto and I want to know how can I do operations like $push or $pull on a deeply nested field? At the moment I write back the whole document which sometimes causes false data to be in the DB due to a race-condition.

Ok, I kind of figured it out. Do not use Ecto for this. In some cases you need the MongoDB positional operator and this can only be done directly via the Mongo-Adapter. Now for some usage examples:
I have a doucment with a list of options. Options have an ID, a label and list of userIDs who voted for this option.
BTW to generate an ObjectID (which is needed for talking directly to the MongoDB-Adapter) use this:
id = "584a5b9419d51d724d146e3f" # string form
value = (for <<hex::16 <- id>>, into: <<>>, do: <<String.to_integer(<<hex::16>>, 16)::8>>)
object_id = %BSON.ObjectId{value: value}
And now for some examples:
# update label of option
Mongo.update_one(Repo.Pool, "polls",
%{"_id" => document_id, "options.id" => option_id}, # query
%{"$set" => %{"data.options.$.label" => new_label}} # update
)
# add new option to poll
Mongo.update_one(Repo.Pool, "polls",
%{"_id" => document_id},
%{"$addToSet" => %{"options" => %{label: label, id: random_new_id, votes: []}}}
)
# add user_id to option
Mongo.update_one(Repo.Pool, "polls",
%{"_id" => document_id, "options.id" => option_id},
%{"$addToSet" => %{"options.$.votes" => user_id}}
)
# remove user_id form option
Mongo.update_one(Repo.Pool, "polls",
%{"_id" => document_id, "options.id" => option_id},
%{"$pull" => %{"data.options.$.votes" => user_id}}
)

Related

Insert query using foreach

I'm trying to generate a statement sheet. For that, I try to insert data into Mysql using foreach. It works great. But I try to escape the insert query if any duplicate row found in database. I do not want to escape the whole process, but it should escape if a duplicate row found and continue with the next insert query.
$gps = $this->Webmastermodel->acquireStatementDetail();
foreach($gps as $statement){
$insertdata = array(
'product_invoice_id' => $statement['InvoiceNo'],
'PUC' => $statement['productPUC'],
'productQTY' => $statement['productQty'],
'product_sub_total' => $statement['productSubTotal'],
'commissionGain_on_SubTotal' => $statement['commisionChargedOnProductSubTotal']
);
$this->db->insert('partner_business', $insertdata);
}
I would use insert_batch if i were you:
$insertdata = array();
foreach($gps as $statement)
{
$insertdata[] = array(
'product_invoice_id' => $statement['InvoiceNo'],
'PUC' => $statement['productPUC'],
'productQTY' => $statement['productQty'],
'product_sub_total' => $statement['productSubTotal'],
'commissionGain_on_SubTotal' => $statement['commisionChargedOnProductSubTotal']
);
}
$this->db->insert_batch('partner_business', $insertdata);
But since your question is about skipping if a row already exits, you can modify your query and use replace instead:
$this->db->replace('partner_business', $insertdata);
Keeping in mind that its all about providing the right primary_key in your $data array, if there is and existing id it will update its data else it will insert a new row.

Find query with and operator in PHP

Hi i am working on backend of web application & want to find the documents from mongodb database that contain key active_status with value set to both 1 & 2. With mongodb PHP i am confused of how to find with both parameters in single query.
My query was this:
$mongoDb = MongoDbConnector::getCollection("endusers");
$endUserData = $mongoDb->find(array('active_status' => 1, '$and' => array('active_status' => 2)));
I have to fetch the users whose active_status should be 1 & 2. The above query doesnt seems to work. What is the right one for that?
Thanks on advance for quick response.
You have $and the wrong way around. Both arguments need to be included:
$endUserData = $mongoDb->find(array(
'$and' => array(
array( 'active_status' => 1 )
array( 'active_status' => 2 )
)
));
And since that would only make sense when looking for both elements within an array element, then you should instead use $all, which is shorter syntax:
$endUserData = $mongoDb->find(array(
'active_status' => array( '$all' => array(1,2) )
));
I should add that unless you intend to match a document like this:
{ "active_status" => [1,2] }
The you do not in fact want $and at all, but rather you want $or or better yet $in for multiple possible values on the same field:
$endUserData = $mongoDb->find(array(
'active_status' => array( '$in' => array(1,2) )
));
This matches documents like this:
{ "active_status": 1 },
{ "active_status": 2 }

How to nest .or() and .and() conditions in Mongoid

I have a 'search' function where I want to pass in an arbitrary 'filter' condition and have matches returned
The following matches any name/email where the filter string is a match:
#people = Person.all
#people = #people.or(
{'name.first_name' => /#{filter}/i},
{'name.last_name' => /#{filter}/i},
{'email' => /#{filter}/i }
)
The following correctly does the same on the 'tags' array on the Person record:
#people = Person.all
#people = #people.any_in('tags' => [/#{filter}/i])
Can anyone tell me how to combine the two queries, so that a Person is matched if the filter text is found in the name, email or any of the tags?
It turns out there is a method I was missing here ... found indirectly via https://github.com/mongoid/mongoid/issues/2845
Given these two queryables:
a=Person.where({'name.first_name'=> /a/i})
b=Person.where({'name.first_name'=> /j/i})
You can combine them using .selector
Person.or(a.selector, b.selector).to_a
=> selector={"$or"=>[{"name.first_name"=>/a/i}, {"name.first_name"=>/j/i}]}
or
Person.and(a.selector, b.selector).to_a
=> selector={"$and"=>[{"name.first_name"=>/a/i}, {"name.first_name"=>/j/i}]}
You don't need to use any_in at all. If say:
:array_field => regex
then MongoDB will automatically check each element of array_field against regex for you, you don't have care about the arrayness at all. That means that you can toss the :tags check in with the other conditions:
regex = /#{filter}/i
#people = Person.where(
:$or => [
{ 'name.first_name' => regex },
{ 'name.last_name' => regex },
{ 'email' => regex },
{ 'tags' => regex }
]
)
I also pull the regex out of the query into a variable to make it clear that you're using the same one for each check and I switched to where as that's more common (at least in my experience).

How to keep orders in MongoDB?

In my MongoDB document I have object like this
[_id] => MongoId Object (
[$id] => 52a46b44aabacb5c218b4567
)
[results] => Array (
[http://google.com] => Array (
[position] => 1
[data] => 42672
)
[http://bing.com] => Array (
[position] => 2
[data] => 9423
)
[http://yandex.com] => Array (
[position] => 3
[data] => 5513
)
)
I would like to change data parameter in "bing.com" from 9423 to for instance 300. Moreover, I have to keep order of the sites. It have to looks like this
[_id] => MongoId Object (
[$id] => 52a46b44aabacb5c218b4567
)
[results] => Array (
[http://google.com] => Array (
[position] => 1
[data] => 42672
)
[http://bing.com] => Array (
[position] => 2
[data] => 300
)
[http://yandex.com] => Array (
[position] => 3
[data] => 5513
)
)
Is this achievable in Mongo?
The reordering of fields issue has been fixed as of MongoDB v2.5.2 (2.6 release). Having said that one way you can avoid the issue completely is having results as an array instead of a (sub)document. Also note you should not use "." as part of the key name either.
With 2.4, with the following you will see there is reodering in the case of _id=1 (subdocument) but not in the case of _id=2 (array).
$document = array("_id" => 1, "results" => array('http://google.com' => array('position' => 1, 'data' => 42672),
'http://bing.com' => array('position' => 2, 'data' => 9423),
'http://yandex.com' => array('position' => 3, 'data' => 5513)));
$coll->insert($document);
$document = array("_id" => 2, "results" => array(array('site' => 'http://google.com', 'data' => 42672),
array('site' => 'http://bing.com', 'data' => 9423),
array('site' => 'http://yandex.com', 'data' => 5513)));
$coll->insert($document);
$coll->update(array("_id" => 1), array('$set'=>array("results.http://bing.com.data"=>300)));
$coll->update(array("_id" => 2, 'results.site' => 'http://bing.com'), array('$set'=>array('results.$.data'=>300)));
I've included examples below using the mongo shell for clarity, but the PHP equivalent should be straightforward to work out.
I notice you originally modelled your list of sites as an embedded document, however the order of fields within an embedded document is currently not guaranteed to be preserved so you should instead use an array.
Additionally, you cannot use field names with embedded dots (.) in MongoDB so you should not plan to store urls as field names (see: Field name restrictions).
In order to find an element in an array you need to search by a value (not a field name) so your schema should look more like:
{
_id: ObjectId("52a46b44aabacb5c218b4567"),
results: [
{
site: 'http://google.com',
position: 1,
data: 42762
},
{
site: 'http://bing.com',
position: 2,
data: 9423
},
{
site: 'http://yandex.com',
position: 3,
data: 5513
}
]
}
Assuming the array site elements are unique, you can use the positional operator $ to find and update the matching embedded document in place.
For example, to perform your update of the "bing.com" data value:
db.sites.update(
// Match criteria
{
_id:ObjectId("52a46b44aabacb5c218b4567"),
'results.site':'http://bing.com'
},
// Update
{ $set: {
'results.$.data': 300 }
}
)
In MongoDB 2.4+ you have the option of pushing to a sorted array which could also be a useful approach to maintaining your array in sorted order when you add new entries.
It's worth noting that if you plan to store many (i.e. thousands) of items in an array this can impose a significant performance penalty due to document growth and the complexity of updating large arrays.
I am pretty sure that (as every other DBMS) you can't and should't rely on records orders.
Instead I would advice you to add index (on position, i.e. db.people.ensureIndex( { position: 1 } )) and query your record sorted by that field, i. e.: db.collection.find().sort( { position: 1 } )

How to get affected number of rows using mongodb ,php driver

I have two problem : How can I get affected rows by php mongodb driver ,and how about the last insert id ? thanks .
You can get number of results right from the cursor using count function:
$collection->find()->count();
You can even get number of all records in collection using:
$collection->count();
Using insert method, _id is added to input array automatically.
$a = array('x' => 1);
$collection->insert($a,array('safe'=>true));
var_dump($a);
array(2) {
["x"]=>
int(1)
["_id"]=>
object(MongoId)#4 (0) {
}
}
I don't believe that there is any type of affected_rows() method at your disposal with mongodb. As for the last insert _id You can generate them in your application code and include them in your insert, so there's really no need for any mysql like insert_id() method.
$id = new MongoId();
$collection->insert(array('
'_id' => $id,
'username' => 'username',
'email' => 'johndoe#gmail.com'
'));
Now you can use the object stored in $id however you wish.
Maybe MongoDB::lastError is what you are looking for:
(http://php.net/manual/en/mongodb.lasterror.php)
It calls the getLastError command:
(http://www.mongodb.org/display/DOCS/getLastError+Command)
which returns, among other things:
n - if an update was done, this is the number of documents updated.
For number of affected rows:
$status = $collection->update( $criteria, array( '$set' => $data ) );
$status['n']; // 'n' is the number of affected rows
If you have the output of your action, you can call relative function:
// $m hold mongo library object
$output = $m->myCollection->updateOne([
'_id' => myMongoCreateID('56ce2e90c9c037dba19c3ce1')], [
'$set' => ['column' => 'value']
]);
// get number of modified records
$count = $output->getModifiedCount();
$output is of of type MongoDB\UpdateResult. Relatively check following files to figure out the best function to find inserted, deleted, matched or whatever result you need:
https://github.com/mongodb/mongo-php-library/blob/master/src/InsertManyResult.php
https://github.com/mongodb/mongo-php-library/blob/master/src/DeleteResult.php
https://github.com/mongodb/mongo-php-library/blob/master/src/InsertOneResult.php
https://github.com/mongodb/mongo-php-library/blob/master/src/UpdateResult.php