Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to generate SQL function calls with the CakePHP query builder?

I have a fullname column for authors and would like to extract the surname into another column. I do that with the following raw SQL:

SELECT name,
SUBSTRING_INDEX(`name`, ' ', -1) AS `surname`
FROM qr.authors;

Output:

enter image description here

Under "Using SQL Functions" the Cookbook says:

In addition to the above functions, the func() method can be used to create any generic SQL function such as year, date_format, convert, etc.

But how can I create this SUBSTRING_INDEX function through the func() method so that I can use it with the CakePHP query builder?

like image 997
ndru Avatar asked Jun 15 '15 13:06

ndru


People also ask

How can I get data from database in cakephp?

To view records of database, we first need to get hold of a table using the TableRegistry class. We can fetch the instance out of registry using get() method. The get() method will take the name of the database table as argument. Now, this new instance is used to find records from database using find() method.

How can I print query in cakephp 2?

Add below code in app_model. php file which is located at root/cake/libs/model. Add below line in your model where you want print query. $last_query = $ this ->ModelName->getLastQuery();

How can I order in cakephp?

To apply ordering, you can use the order method: $query = $articles->find() ->order(['title' => 'ASC', 'id' => 'ASC']); When calling order() multiple times on a query, multiple clauses will be appended.


1 Answers

The functions builder comes with predefined methods/functions

The FunctionsBuilder class ships with a bunch of ready-made methods/functions for you to use, like sum(), count(), concat(), dateDiff(), now(), etc. You can find a complete list of the supported functions and examples on how to use them in the Cookbook and the API docs.

Arbitrary functions can be built by just calling them

The FunctionsBuilder class uses the magic method __call handler to build arbitrary SQL function expressions, so in case there is no ready made method for your function, you can just "call" your SQL function:

$query = $this->SomeTable->find();

$func = $query->func()->substring_index([
    'name' => 'identifier',
    ' ',
    -1 => 'literal'
]);
$query->select([/* ... */, 'surname' => $func]);

This should be mostly rather self explanatory, the magic method name is the SQL function name, and the passed array holds the arguments that should be passed to the function, where in this case the first and last argument are defined to be treated as identifier respectively as a literal, and thus both being inserted into the query directly, ie not as bound parameter that would be escaped!

The identifier one will additionally be subject to possible automatic identifier quoting, ie name would be transformed to for example `name`, "name", or [name] depending on the database driver in use. The second argument could be made a literal too (by passing for example '" "'), I've just not set it as one for example purposes. Not doing so will cause the value to be bound/casted as a string.

The resulting compiled SQL will look something like this:

substring_index(name, :c0, -1)

and will finally be executed as

substring_index(name, ' ', -1) 

Handling non-hard-coded data, for example user input

When working with data that isn't hard-coded, ie dynamic, or otherwise subject to possible change, make sure that you define the proper types for casting/escaping in the second argument if necessary, like integer, datetime, etc. In order to get this working properly, you'll have to use an identifier expression for the column name value, as otherwise the second argument would be ignored when using the 'xyz' => 'identifier' syntax:

$func = $query->func()->substring_index(
    [
        new \Cake\Database\Expression\IdentifierExpression('title'),
        ' ',
        $userInput,
    ],
    [
        null,     // no typecasting for the first argument
        'string', // second argument will be bound/casted as string
        'integer' // third argument will be bound/casted as integer
    ]
);

The types will be matched via the numeric indices, and the first one is going to be ignored, since it is an expression, hence passing just null.

You could even use raw expressions

In your case, where you are passing safe, hard-coded values that don't need to be inserted into the query as bound parameters, and SUBSTRING_INDEX isn't a function that is covered by any of the dialects that ship with CakePHP, you could even use raw queries instead - you'll however loose the ability to transform the expression in custom dialects, and automatic identifier quoting will also not apply any more, so only do this if you know what you are doing!

$query->newExpr('SUBSTRING_INDEX(`name`, "", -1)')

See also

  • Cookbook > Database Access & ORM > Query Builder > Using SQL functions
  • API > \Cake\Database\Query::newExpr()
like image 159
ndm Avatar answered Sep 21 '22 09:09

ndm