Remove embedded document from MongoDB PHP - php

I am trying to build a query that deletes an embedded document from a MongoDB document in PHP. What I have now is:
$collection->update(array("_id" => new MongoId($id)),
array('$unset' => 'BUSCO.short_summary_data'));
I have also tried:
$collection->remove(array("_id" => new MongoId($id)),
array('$unset' => 'BUSCO.short_summary_data'));
No error is thrown, but the embedded document still exists! Could someone help me out?

Your current statement written in JSON looks like this:
{ $unset: 'BUSCO.short_summary_data' }
But according to the documentation:
The $unset operator deletes a particular field. Consider the following
syntax:
{ $unset: { <field1>: "", ... } }
The specified value in the $unset expression (i.e. "") does not impact the operation.
So $unset expects an array with key-value pairs. Try:
$collection->update(array("_id" => new MongoId($id)),
array('$unset' => array('BUSCO.short_summary_data' => '')));

Related

Add new field to a MongoDB document parsing from String to Int using updateMany

In my MongoDB collection, all documents contain a mileage field which currently is a string. Using PHP, I'd like to add a second field which contains the same content, but as an integer value. Questions like How to change the type of a field? contain custom MongoDB code which I don't want to run using PHP, and questions like mongodb php Strings to float values retrieve all documents and loop over them.
Is there any way to use \MongoDB\Operation\UpdateMany for this, as this would put all the work to the database level? I've already tried this for static values (like: add the same string to all documents), but struggle with getting the data to be inserted from the collection itself.
Some further hints:
I'm looking for a pure PHP solution that does not rely on any binary to be called using exec. This should avoid installing more packages than needed on the PHP server
Currently, I have to use MongoDB in v4.0. Yes, that's not the most recent version, but I'm not in the position to perform an upgrade
Try this, please:
01) MongoDB Aggregate reference:
db.collectionName.aggregate(
[
{ "$addFields": {
"intField": { "$toInt": "$stringFieldName" }
}},
{ "$out": "collectionName" }
]
)
02) Possible PHP solution (Using as reference https://www.php.net/manual/en/mongocollection.aggregate.php):
$pipeline = array(
array(
'$addFields' => array(
'integerField' => array('$toInt' => '$mileage')
)
),
array(
'$out' => 'collection'
),
);
$updateResult = $collection->aggregate(pipeline);
You could use $set like this in 4.2 which supports aggregation pipeline in update.
$set stage creates a mileageasint based on the previous with $toInt value
db.collection.updateMany(
<query>,
[{ $set: { "mileageasint":{"$toInt":"$mileage" }}}],
...
)
Php Solution ( Using example from here)
$updateResult = $collection->updateMany(
[],
[['$set' => [ 'mileageasint' => [ '$toInt' => '$mileage']]]]
);

Iterate over Mongo results without running out of memory

I need to find keywords in the name/description/tags, etc. of each document and remove them if they are found. I'm new to Mongo, so I'm following a similar script in the existing codebase. First, get the MongoCursor and only get the fields we'll be checking:
/** #var MongoCursor $products */
$products = $collection->find(
['type' => ['$in' => ['PHONES', 'TABLETS']], 'supplier.is_awful' => ['$exists' => true]],
['details.name' => true, 'details.description' => true]
);
Then, iterate through each document, then check each of the properties for the values we're interested in:
/** #var \Doctrine\ODM\MongoDB\DocumentManager $manager */
$manager = new Manager();
foreach ($products as $product) {
// Find objectionable words in the content and remove these documents
foreach (["suckysucky's", "deuce", "a z z"] as $word) {
if (false !== strpos(mb_strtolower($product['details']['name']), $word)
|| false !== strpos(mb_strtolower($product['details']['description']), $word)) {
$object = $manager->find(\App\Product::class, $product['_id']);
$manager->remove($object);
}
}
}
// Persist to DB
$manager->flush();
The problem is that the database has hundreds of thousands of records, and it looks like iterating over the MongoCursor, the memory usage goes up and up until it runs out:
Now at (0) 20035632
Now at (100) 24446048
Now at (200) 32190312
Now at (300) 36098208
Now at (400) 42433656
Now at (500) 45204376
Now at (600) 50664808
Now at (700) 54916888
Now at (800) 59847312
Now at (900) 65145808
Now at (1000) 70764408
Is there a way for me to iterate over the MongoCursor without running out of memory (I've tried unsetting the various objects at different points, but no luck there)? Alternatively, is this a query that can be run directly in Mongo? I've looked at the docs, and I saw some hope in $text, but it looks like I need to have an index there (I don't), and there can only be one text index per collection.
You don't need fulltext index to find a substring: the right way is using a regex and then return only the "_id" value, something like:
$mongore = new MongoRegex("/suckysucky's|deuce|a z z/i")
$products = $collection->find(
['type' => ['$in' => ['PHONES', 'TABLETS']],
'supplier.is_awful' => ['$exists' => true],
'$or': [['details.name' => $mongore],
['details.description' => $mongore]]]
['_id' => true]
);
I'm not sure about the exact PHP syntax, but the key is an inclusive $or filter with the same mongodb regex on the two fields.

php mongodb full-text search and sort

i nead to make a search with full text index and this code work:
$cursor=$collection->find(array('$text'=>(array('$search'=>$s))),
array("score"=> array('$meta'=>"textScore"))
);
i try to sort the cursor with:
$cursor =$cursor->sort(array("score"=>1));
when i try to read
var_dump($cursor->getNext());
i gave me this error
Uncaught exception 'MongoCursorException' with message 'localhost:27017: Can't canonicalize query: BadValue can't have a non-$meta sort on a $meta projection'
any idea?
You are attempting to sort on a meta field, not a normal fieldname.
The second argument to $collection->find() determines which fields of the document you (do/do not) want to be returned by the query.
This is similar to SELECT *... vs SELECT field1, field2 ... in SQL databases.
Now, in MongoDB 2.6 there is an additional keyword you can use here, $meta. This keyword allows you to "inject" fieldnames into the return document (that otherwise would not actually exist). The value of this injected fieldname would come from some sort of "meta data" of the document or query you are executing.
The $text query operator is an example of an operator that has more information available about the matched document.. Unfortunately, it has no way of telling you about this extra information since doing so would manipulate your document in unexpected way. It does however attach a metadata to the document - and it is up to you to decide if you have need for it or not.
The meta data the $text operator creates uses the keyword "textScore". If you want to include that data, you can do so by assigning it to a field name of your choice:
array("myFieldname" => array('$meta' => 'keyword'))
For example, in the case of $text search (textScore) we can inject the fieldname "score" into our document by passing this array as the 2nd argument to $collection->find():
array("score" => array('$meta' => 'textScore'))
Now we have injected a field called "score" into our return document which has the "textScore" value from the $text search.
But since this is still just meta data of the document, if you want to continue to use this value in any subsequent operations before executing the query, you still have to refer to it as $meta data.
This means, to sort on the field you have to sort on the $meta projection
array('score' => array('$meta' => 'textScore'))
Your full example then becomes:
<?php
$mc = new MongoClient();
$collection = $mc->selectCollection("myDatabase", "myCollection");
$string = "search string";
$cursor = $collection->find(
array('$text' => array('$search' => $string)),
array('score' => array('$meta' => 'textScore'))
);
$cursor = $cursor->sort(
array('score' => array('$meta' => 'textScore'))
);
foreach($cursor as $document) {
var_dump($document);
}

Find with regular expression MongoDB + PHP

I'm trying to do a PHP find over a MongoDB collection using MongoRegex, but I'm not able to make it work. The code is quite easy:
$cursor = $this->collection->find($params)
//$params has this value:
//Array
//(
// [name] => MongoRegex Object
// (
// [regex] => .*victor.*
// [flags] => i
// )
// [login] => MongoRegex Object
// (
// [regex] => .*victor.*
// [flags] => i
// )
//)
This $params array is constructed with this function:
function toRegEx($entryVars=array()){
$regexVars = array();
foreach($entryVars as $var => $value){
$regexVal = html_entity_decode($value);
$regexVars[$var] = new MongoRegex("/.*".$regexVal.".*/i");
}
return $regexVars;
}
For some reason, this query is only returning the values which makes an exact match (i.e. the documents where login or name are exactly "victor"). What I want is that the query returns all the documents where login and/or name contains the word "victor". I'm pretty sure I'm missing something basic, but I'm not being able to find it. Any help is appreciated. Thanks!
I suppose you simply anchored the regexp to the beginning of the subject string (^), try without :
$regexVars[$var] = new MongoRegex("/".$regexVal."/i");
EDIT:
Also, if the print_r dump of the $params array above is accurate, you're probably missing a $or statement somewhere to reflect your conditions. By default, the mongodb query criteria are linked with a "and" logic, so your query will return records matching regexps on both fields only.

MongoDB search $in _id php

Usually when I search for one related ID I do it like this:
$thisSearch = $collection->find(array(
'relatedMongoID' => new MongoId($mongoIDfromSomewhereElse)
));
How would I do it if I wanted to do something like this:
$mongoIdArray = array($mongoIDfromSomewhereElseOne, $mongoIDfromSomewhereElseTwo, $mongoIDfromSomewhereElseThree);
$thisSearch = $collection->find(array(
'relatedMongoID' => array( '$in' => new MongoId(mongoIdArray)
)));
I've tried it with and without the new MongoId(), i've even tried this with no luck.
foreach($mongoIdArray as $seprateIds){
$newMongoString .= new MongoId($seprateIds).', ';
}
$mongoIdArray = explode(',', $newMongoString).'0';
how do I search '$in' "_id" when you need to have the new MongoID() ran on each _id?
Hmm your rtying to do it the SQL way:
foreach($mongoIdArray as $seprateIds){
$newMongoString .= new MongoId($seprateIds).', ';
}
$mongoIdArray = explode(',', $newMongoString).'0';
Instead try:
$_ids = array();
foreach($mongoIdArray as $seprateIds){
$_ids[] = $serprateIds instanceof MongoId ? $seprateIds : new MongoId($seprateIds);
}
$thisSearch = $collection->find(array(
'relatedMongoID' => array( '$in' => $_ids)
));
That should produce a list of ObjectIds that can be used to search that field - relatedMongoID.
This is what I am doing
Basically, as shown in the documentation ( https://docs.mongodb.org/v3.0/reference/operator/query/in/ ) the $in operator for MongoDB in fact takes an array so you need to replicate this structure in PHP since the PHP driver is a 1-1 with the documentation on most fronts (except in some areas where you need to use an additional object, for example: MongoRegex)
Now, all _ids in MongoDB are in fact ObjectIds (unless you changed your structure) so what you need to do to complete this query is make an array of ObjectIds. The ObjectId in PHP is MongoId ( http://php.net/manual/en/class.mongoid.php )
So you need to make an array of MongoIds.
First, I walk through the array (could be done with array_walk) changing the values of each array element to a MongoId with the old value encapsulated in that object:
foreach($mongoIdArray as $seprateIds){
$_ids[] = $serprateIds instanceof MongoId ? $seprateIds : new MongoId($seprateIds);
}
I use a ternary operator here to see if the value is already a MongoId encapsulated value, and if not encapsulate it.
Then I add this new array to the query object to form the $in query array as shown in the main MongoDB documentation:
$thisSearch = $collection->find(array(
'relatedMongoID' => array( '$in' => $_ids)
));
So now when the query is sent to the server it forms a structure similar to:
{relatedMongoId: {$in: [ObjectId(''), ObjectId('')]}}
Which will return results.
Well... I came across the same issue and the solution might not be relevant anymore since the API might have changed. I solved this one with:
$ids = [
new \MongoDB\BSON\ObjectId('5ae0cc7bf3dd2b8bad1f71e2'),
new \MongoDB\BSON\ObjectId('5ae0cc7cf3dd2b8bae5aaf33'),
];
$collection->find([
'_id' => ['$in' => $_ids],
]);

Categories