I want to search for a partial first and last name match - for example in sql
f_name LIKE J% OR l_name LIKE S%
would match John Smith or John Henry or Harry Smith .
I am assuming I may need to use the "$or" operator,
I have this so far that I believe is doing the LIKE % part properly, but I believe it is doing an "AND" search (meaning it searches for f_name LIKE J% AND l_name LIKE S% so it would only match John Smith):
$name1="J";
$name2="S";
$cursor = $this->clientCollection->find(array('f_name' => new MongoRegex('/^'.$name1.'/i'), 'l_name' => new MongoRegex('/^'.$name2.'/i') ));
Note: This will match containing as in %J%
MongoRegex('/'.$name1.'/i')
This will match starts with (notice the added ^) as in J%
MongoRegex('/^'.$name1.'/i')
$or takes an array of clauses, so you basically just need to wrap another array around your current query:
array('$or' => array(
array('f_name' => new MongoRegex('/'.$name1.'/i')),
array('l_name' => new MongoRegex('/'.$name2.'/i'))
));
Edit: the previous example missed an inner set of array() calls.
The original, wrong, example that I posted looked like this:
array('$or' => array(
'f_name' => new MongoRegex('/'.$name1.'/i'),
'l_name' => new MongoRegex('/'.$name2.'/i')
));
This is a valid query, but not a useful one. Essentially, the f_name and l_name query parts are still ANDed together, so the $or part is useless (it's only getting passed one query, so it's the same as just running that query by itself).
As for the alternative you mentioned in your comment, that one doesn't work because the outermost array in a query has to be an associative array. The confusion arises because Mongo's query syntax is JSON-like and uses a mixture of objects and arrays, but both of those structures are represented in PHP by arrays. The PHP Mongo driver basically converts PHP associative arrays to JSON objects ({ ... }), and "normal" PHP arrays to JSON arrays ([ ... ]).
The practical upshot is that "normal" PHP arrays are generally only valid when inside an associative array, like when specifying multiple values for a field. The following example from the PHP Mongo manual shows a valid usage of a "normal" array in a query:
$cursor = $collection->find(array("awards" => array('$in' => array("gold", "copper"))));
Related
I am updating an old internal library to use PHP's PDO extension. In the old code, there's a function where the first parameter was a line of SQL and then the second parameter was an optional column to use as the key. I need to keep this same function signature so I don't break any code.
I'd like to use PDO's FETCH_UNIQUE fetch mode, but it would require me to parse every line of SQL that is passed in and add the key column to the beginning of the select. I'm worried that there are edge cases that I wouldn't think of that would break the call.
Is there another way in PDO to designate which column to use as the key when using PDO::FETCH_UNIQUE other than to add it directly to the column list in the SQL call?
Edit:
Here's some example code and more details:
$db = new Database();
$query = "first_name, last_name, contacts_id from contacts where first_name='George'";
$uniqueKeyField = "contacts_id";
$contact = $db->getRows($query, $uniqueKeyField);
Which would yield:
array (
[34] => array (
"first_name" => "George",
"last_name" => "Smith",
),
[452] => array (
"first_name" => "George",
"last_name" => "Johnson",
)
)
The problem is, I cannot always guarantee that the unique keyField will be first in the list like PDO requires because that was not a requirement of our API before.
After going through the manual, my guess is this is not possible to do with PDO::FETCH_UNIQUE and we will either have to use PDO::FETCH_ASSOC and loop through the results like we used to and build our own array or change every call to the above function in existing code to rearrange the fields so the unique keyfield is first. I was hoping someone knew of a way you could just pass the unique keyfield as an argument somewhere, but my guess is PDO faces the same problem we do - they have to have the keyfield in the query somewhere to pull out and put as the id in the returned array and they don't want to have to try to parse and add it to existing queries, thus the requirement to have it as the first field.
I'm trying to create a query that returns the sum of a column using a case (it has logged time and the format in either minutes or hours, if it's in hours, multiply by 60 to convert to minutes). I'm very close, however the query is not populating the ELSE part of the CASE.
The finder method is:
public function findWithTotalTime(Query $query, array $options)
{
$conversionCase = $query->newExpr()
->addCase(
$query->newExpr()->add(['Times.time' => 'hours']),
['Times.time*60', 'Times.time'],
['integer', 'integer']
);
return $query->join([
'table' => 'times',
'alias' => 'Times',
'type' => 'LEFT',
'conditions' => 'Times.category_id = Categories.id'
])->select([
'Categories.name',
'total' => $query->func()->sum($conversionCase)
])->group('Categories.name');
}
The resulting query is:
SELECT Categories.name AS `Categories__name`, (SUM((CASE WHEN
Times.time = :c0 THEN :c1 END))) AS `total` FROM categories Categories
LEFT JOIN times Times ON Times.category_id = Categories.id GROUP BY
Categories.name
It's missing the ELSE statement before the CASE end, which according to the API docs:
...the last $value is used as the ELSE value...
https://api.cakephp.org/3.3/class-Cake.Database.Expression.QueryExpression.html
I know there might be a better way to do this, but at this point I'd like to at least know how to do CASE statements properly using the built in QueryBuilder.
Both arguments must be arrays
Looks like there are some documenation issues in the Cookbook, and the API could maybe be a little more clear on that subject too. Both, the $conditions argument as well as the $values argument must be arrays in order for this to work.
Enforcing types ends up with casting values
Also you're passing the SQL expression wrong, including the wrong types, defining the types as integer will cause the data passed in $values to be casted to these types, which means that you will be left with 0s.
The syntax that you're using is useful when dealing with user input, which needs to be passed safely. In your case however you want to pass hardcoded identifiers, so what you have to do is to use the key => value syntax to pass the values as literals or identifiers. That would look something like:
'Times.time' => 'identifier'
However, unfortunately there seems to be a bug (or at least an undocumented limitation) which causes the else part to not recognize this syntax properly, so for now you'd have to use the manual way, that is by passing proper expression objects, which btw, you may should have done for the Times.time*60 anyways, as it would otherwise break in case automatic identifier quoting is being applied/required.
tl;dr, Example time
Here's a complete example with all forementioned techniques:
use Cake\Database\Expression\IdentifierExpression;
// ...
$conversionCase = $query
->newExpr()
->addCase(
[
$query->newExpr()->add(['Times.time' => 'hours'])
],
[
$query
->newExpr(new IdentifierExpression('Times.time'))
->add('60')
->tieWith('*'), // setConjunction() as of 3.4.0
new IdentifierExpression('Times.time')
],
);
If you were for sure that you'd never ever make use of automatic identifier quoting, then you could just pass the multiplication fragment as:
'Times.time * 60' => 'literal'
or:
$query->newExpr('Times.time * 60')
See also
Cookbook > Database Access & ORM > Query Builder > Case statements
Cookbook > Database Access & ORM > Query Builder > Using SQL Functions
API > \Cake\Database\Expression\QueryExpression::add()
API > \Cake\Database\Expression\QueryExpression::tieWith()
On performing the below operation,
$this->mongo_db->order_by(array('first_name' => 'ASC'))->get('users');
the records starting with A-Z come before the records starting with a-z.
I want them to be sorted alphabetically irrespective of their cases.
e.g. AaBbcCDd....Zz
This is because MongoDB, for one, does not have case insensitive indexes ( https://jira.mongodb.org/browse/SERVER-90 ) and for two (the reason why it doesn't) is because it does not have collations yet ( https://jira.mongodb.org/browse/SERVER-1920 ).
Derick Rethans, one of the PHP driver maintainers, recently wrote a blog post with a possible solution to natural language sorting, http://derickrethans.nl/mongodb-collation.html but it still requires a separate field
The problem is because the ascii values of A-Z < a-z.
You need to use the $toLower function in the aggregate pipeline as below,
Project an extra field for each record to hold the first_name in
lowercase.
Sort based on the projected field in ascending order.
Project the required fields.
The Code as acceptable in the mongo shell:
db.collection.aggregate([
{$project:{"users":1,"first_name":{$toLower:"$first_name"}}},
{$sort:{"first_name":1}},
{$project:{"users":1,"_id":0}}
],{allowDiskUse:true})
If your business permits to store the first_name field value in either lowercase or uppercase, you can avoid this problem. And more importantly you can index the first_name field and query, in the case, that the first_field is stored.
As requested, please find below the code in PHP:
$proj1 = array("users" => 1,'first_name' => array ('$toLower' => '$first_name'))
$sort = array("first_name" => 1)
$proj2 = array("users" => 1,"_id" => 0)
$options = array("allowDiskUse" => true)
$collection -> aggregate(array($proj1,$sort,$proj2),$options)
Say I have the following string (forget that it is a MySQL statement):
SELECT * FROM users WHERE name = 'fred bloggs' AND age=21 AND address='Mountain View, CA 94043' LIMIT 1
I need a way to extract the field name and value in the WHERE clause so I have an array like:
Array
(
[name] => fred bloggs
[age] => 21
[address] => Mountain View, CA 94043
)
Remember this is a dynamic MySQL string so I can't hard-code name, age or address.
The problems I can foresee are:
Finding the field names, the function would have to know all valid operators so to match each field name (see array to use below)
Spaces are not guaranteed (e.g. age=21)
Finding the values within ' and ' but not breaking when a ' is within the string
Finding values not within ' and ' e.g. numbers or other field names (would need to be treated seperately)
Does anyone have any ideas how to do this either with regex or string functions?
Here are the operators if required:
$operators = array('+', '-', '*', '/', '^', '=', '<>', '!=', '<', '<=', '>', '>=', 'like', 'clike', 'slike', 'not', 'is', 'in', 'between', 'and', 'or');
You can try with this kind of tool :
http://code.google.com/p/php-sql-parser/
You will get a tree (an array) describing the query, this will help you to build the array you want.
You should create an expresssion parser for that, it cannot be achieved with regex . E.g., your resulting array would contain an expression tree with all node types you specified (OR, AND, IN, BETWEEN, etc.)
There's an example on the web: here
There are probably a few ways to approach this:
1) Look for metadata from the query. I know you can get info about the resultset but Im not sure about the original query. Its in there somewhere but might need to be teased out.
2) Use a prepared statement to build your query from pieces. Pass in the various where conditions in an array. Its kind of backwards (IE starting with the answer you want and then building the query) though.
I've got to sort a list of Reservations (they're coupled to an event by defining a belongsTo association) by the last name of the person who registered the ticket.
I do this in cakePHP:
$reservations = Set::sort($eventinfo['Reservation'],'{n}.last_name','asc');
This works, but some users input their data in all lowercase, which makes the sorting wrong:
Alfa, Ziggy, aardvark, zorro
Where it should be:
aardvark, Alfa, Ziggy, zorro
How can I fix this? I could loop over the array and make every string start with an uppercase letter using ucword(), but that looks a bit ugly. Isn't there an easy way to alter the sort algorithm so it ignores case?
I would be normalising the surnames before storing them - either all lowercase, all UPPERCASE or all Capitalised.
Is there a reason you're not performing the sorting in SQL?
I assume there's a find operation shortly before you call Set::sort, which I imagine looks something like:
$reservations = $this->Event->find('first',array(
'conditions' => array('Event.id' => $my_event_id),
'contain' => array('Reservation')
));
You can instruct Cake's ORM to sort the contained Reservations with the same syntax as a standard find operation, like so:
$reservations = $this->Event->find('first',array(
'conditions' => array('Event.id' => $my_event_id),
'contain' => array(
'Reservation' => array('order'=>'Reservation.last_name')
)
));
It looks like in the code Set::sort is using array_multisort under the hood http://php.net/manual/en/function.array-multisort.php so it is not going to give you case insensitive sorting. You could look at the code for Set::sort and make your own subclass of Set that uses something like natcasesort http://www.php.net/manual/en/function.natcasesort.php or use mysql to do the sorting first, which by default in mysql would be case insensitive sorting