php mongodb full-text search and sort - php

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);
}

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']]]]
);

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],
]);

Database logging in Zend Framework 2: wrong "extra" column name

I want to save log entries to my MySQL database from Zend Framework 2. I am using Zend\Log\Logger with a Zend\Log\Writer\Db writer. By supplying the writer with an array, one can choose which columns to save what data to (e.g. timestamp into a "log_date" column) and which data to save. Here is what I am doing:
$logger = new Zend\Log\Logger();
$mapping = array(
'timestamp' => 'timestamp_column',
'priority' => 'priority_column',
'message' => 'message_column',
'extra' => 'extra_column'
);
$logger->addWriter(new Zend\Log\Writer\Db($dbAdapter, 'table_name', $mapping));
$logger->err('some message', array('some extra information'));
The problem I am facing is that the array of column names and their values contain an incorrect column name for the "extra" column. Based on the array above, it should be inserting the value "some extra information" into the "extra_column" column. The problem is that the Zend\Log\Writer\Db class is using the letter "e" as the name of the extra column. This comes from the first letter of "extra_column" in my array above. For some reason, it is taking the first letter of "extra_column" and using it as the column name instead of the entire value.
I took a look at the source code. The mapEventIntoColumn method is being used to get the column names and values as an array. I copied in the relevant part of the method below.
// Example:
// $event = array('extra' => array(0 => 'some extra information'));
// $columnMap = array('extra' => 'extra_column');
// Return: array('e' => 'some extra information')
// Expected (without looking at the code below): array('extra_column' => 'some extra information')
protected function mapEventIntoColumn(array $event, array $columnMap = null) {
$data = array();
foreach ($event as $name => $value) {
if (is_array($value)) {
foreach ($value as $key => $subvalue) {
if (isset($columnMap[$name][$key])) {
$data[$columnMap[$name][$key]] = $subvalue;
}
}
}
}
return $data;
}
The $event parameter is an array containing the same keys as my $mapping array in my first code snippet and the values for the log message. The $columnMap parameter is the $mapping array from my first code snippet (array values are column names).
What actually seems to happen is that because I am passing in extra information as an array (this is required), the inner foreach loop is executed. Here, $key is 0 (the index) so it is actually doing like this: $columnMap['extra'][0]. This gives the letter "e" (the first letter in "extra_column"), which is used as the column name, where it should be the entire column name instead.
I tried to supply my own key in the extra array when calling the log method, but the same happens. The official documentation shows no examples of usage of the extra parameter. I want to insert information that can help me debug errors into my table, so I would like to use it.
Is this a bug or am I missing something? It seems really strange to me! I hope I explained it well enough - it is quite tricky!
Since Daniel M has not yet posted his comment as an answer, I will refer you to his comment which solved the problem.

Problems with PHP associative array and MySQL data

I recently added a new column to a MySQL database (featured), then added that column into my associative array. Rather than calling the data for the new column, I simply get 'null' in place of each entry, even after specifically requesting the column to be 'not null'.
I thought I may have been calling the data incorrectly, however, my array seems fine:
while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) {
array_push($blogs, array('title' => $row['title'], 'content' => $row['content'], 'featured' => $row['featured']));
}
And this is the response I get:
{"blogs":[{"title":"test-title","content":"test-content","featured":null}]}
I'm guessing this must be a MySQL issue, but I've no idea what it could be.
My guess is that the SQL query executed doesn't select this new column, or uses another name as the one you're expecting:
select title, content from ...
or
select title, content, feature as f from ...
You're using $row['...'] instead of $result['...']

mongo add field to document ($set doesn't)

What I want:
fetch some documents
for every document, set a field:
change it, if it exists
add it, if it doesn't
What I do:
// fresh data
print_r(iterator_to_array($collection->find()->limit(2)));
// query
$docs = $collection->find()->limit(2);
// fetch
foreach ( $docs AS $id => $doc ) {
// update
$collection->update(array('_id' => $doc['_id']), array(
'$set' => array(
'existing_field' => 'x',
'new_field' => 'y',
),
), array('multiple' => false));
}
// verify
print_r(iterator_to_array($collection->find()->limit(2)));
Why doesn't that do anything? The existing field isn't changed and the new field isn't added. Is the condition maybe wrong??
PS. The full code (with a lot of junk in between): http://pastebin.com/CNfCVxex
lines 33 - 37 -- fresh data
lines 63 - 76 -- query, fetch & update
lines 79 - 80 -- verify
Not familiar with the driver you are using here, but from your description you can achieve what you want in a single database hit, no need to fetch/loop/update..
The $set operator will insert or update a field depending on whether it exists or not.
db.Collection.update({}, { $set : { "myfield" : "x" } }, false, true)
The above would set the 'myfield' field in all documents in the collection to 'x', or if it already exists it would change the value to 'x'. Is that what you want to achieve?
I suspect you need to convert the string $doc['_id'] back to a MongoId by using new MongoId. See the example at the bottom of this page: http://www.php.net/manual/en/class.mongoid.php

Categories