Doctrine orderBy on SUM() field with alias - php

I am trying to do a simple query in doctrine but struggling.
$query->select(array(
'app_title' => 'u.title',
'user_name' => 'u.user_name',
'first_used' => 'MIN(u.creation_time)',
'last_used' => 'MAX(u.stop_time)',
'total_usage' => 'SUM(u.stream_seconds)',
))
->from(self::USAGE_TABLE, 'u')
->orderBy('total_usage', 'DESC');
Obviously I get an error about the column name not being known because Doctrine is using it's own aliases (sclr4).
However, if I try and order by the actual value; SUM(u.stream_seconds), then I get an unexpected bracket in the order by clause, I'm pretty sure SQL doesnt support this.
So, I am simply trying to put data in a table and handle the sorting of the columns. This seems so simple, how do I do it? Any ideas?

You can orderBy the SUM result field by list it in query projection by aliasing result using AS.
If you want to use an aggregate function such as MIN(), MAX(), AVG(), you have to use GROUP BY.
Try simmilar to this, which works perfectly for me (BTW instead of associative array in select method):
$q = $this->em()->createQueryBuilder();
$q->select(['product.id', 'product.title'])
->addSelect('SUM(product.price) AS HIDDEN stat_sum_realised')
->from('ModuleAdmin\Entity\ProductEntity', 'product')
->groupBy('product.id');
$q->orderBy('stat_sum_realised', 'DESC');
Aggregate functions are detailed here (for e.x. for MySQL):
http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html
As of Doctrine ORM 2.3, you can also use the HIDDEN keyword, which will avoid (in this case) stat_sum_realised from getting hydrated into your resultset.

Related

Eloquent whereRaw statement and orWhereRaw is NULL

For a search query I have the following:
DB::whereRaw('column = ?', 'foo')->orWhereRaw('column IS NULL')->get();
Adding the orWhereRaw statement gives me less results than only the whereRaw. Somehow it seems to ignore the first when adding the other. It is included in the SQL statement. Is there another way to compare for a string and null value?
I have also tried the following, as suggested below:
return self::select('id')
->where('current_state', 'unavailable')
->orWhereNull('current_state')
->get();
If I change the order (the whereNull first and the where second) this also gives me different results. It appears as if the inclusive query doesn't function correctly in correspondence with the where clause. If I use to regular where clauses I don't experience any issues.
Running SELECT * FROM events WHERE current_state='unavailable' OR current_state IS NULL; does produce the correct result for me.
Don't use whereRaw to check for null. You can use this instead:
->orWhereNull('column')
The proper way to do the first where, unless you're doing something extra such as a mysql function, is just to pass the column along like this:
where('column', '=', 'foo')
You can actually eliminate the equals, since it defaults to that. So your query would be:
DB::table('table')->where('column', 'foo')->orWhereNull('column')->get();

Cakephp3 case mysql statement is not creating the correct query

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()

Zend: Select object: How do I replace the selected columns set by from()?

first of all, that's what I'm trying to do:
In one of my classes in the library I want to count the total amount of rows of a search result. The class uses a select object set by the appendant model of the search result. My problem is now, this select() has already set the requested columns by from(), but to simply count the rows I just want to select the id, because the website has to to be performant. I can't simply change the values of the object, because I'm using it in the library and the variables are protected. Unfortunately, Zend has no function for the mySql count command and I don't want to use static mySql code, because it could be, that we switch our database system in the future.
Now here's my question:
Is there any possibility by Zend_Select how I could change the selected columns?
Try this:
$select->reset(Zend_Db_Select::COLUMNS)
->from('thetable', 'COUNT(*)');
replacing the 'thetable' with the correct table name.
This is from a project and isn't tested, but one of these should work.
$select->from(array("table_name" => "table_name"), array("my_col" => "COUNT(id)"));
OR
$select->from(array("table_name"), array("my_col" => "COUNT(id)"));
This is the same as
SELECT COUNT(id) as my_col FROM table_name
Hope that helps
Jake
This one didn't work for me (I needed to select only from one joined table):
$select->reset(Zend_Db_Select::COLUMNS)
->from('thetable', 'COUNT(*)');
Maybe because I had some joins. But nevertheless, here's the solution: to use reset() and then columns():
$select->setIntegrityCheck(false)
->from(['t1' => 'table1'])
->join(['t2' => 't2'], 't1.id = t2.t1_id')
->reset(Zend_Db_Select::COLUMNS)
->columns('t1.*');
Just FYI, the version of Zend Framework is 1.12
To use a mysql command in a select, you need to use Zend_Db_Expr:
$select = $this->select()
->from('myTable', new Zend_Db_Expr('COUNT(id) as count'));
echo $select; //SELECT COUNT(id) as count FROM myTable;

Order by multiple columns with Doctrine

I need to order data by two columns (when the rows have different values for column number 1, order by it; otherwise, order by column number 2)
I'm using a QueryBuilder to create the query.
If I call the orderBy method a second time, it replaces any previously specified orderings.
I can pass two columns as the first parameter:
->orderBy('r.firstColumn, r.secondColumn', 'DESC');
But I cannot pass two ordering directions for the second parameter, so when I execute this query the first column is ordered in an ascending direction and the second one, descending. I would like to use descending for both of them.
Is there a way to do this using QueryBuilder? Do I need to use DQL?
You have to add the order direction right after the column name:
$qb->orderBy('column1 ASC, column2 DESC');
As you have noted, multiple calls to orderBy do not stack, but you can make multiple calls to addOrderBy:
$qb->addOrderBy('column1', 'ASC')
->addOrderBy('column2', 'DESC');
In Doctrine 2.x you can't pass multiple order by using doctrine 'orderBy' or 'addOrderBy' as above examples. Because, it automatically adds the 'ASC' at the end of the last column name when you left the second parameter blank, such as in the 'orderBy' function.
For an example ->orderBy('a.fist_name ASC, a.last_name ASC') will output SQL something like this 'ORDER BY first_name ASC, last_name ASC ASC'. So this is SQL syntax error. Simply because default of the orderBy or addOrderBy is 'ASC'.
To add multiple order by's you need to use 'add' function. And it will be like this.
->add('orderBy','first_name ASC, last_name ASC'). This will give you the correctly formatted SQL.
More info on add() function. https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/query-builder.html#low-level-api
Hope this helps. Cheers!
you can use ->addOrderBy($sort, $order)
Add:Doctrine Querybuilder btw. often uses "special" modifications of the normal methods, see select-addSelect, where-andWhere-orWhere, groupBy-addgroupBy...
You can use orderBy() followed by an addOrderBy() - nesting several orderBy()'s is not possible, but nesting several addOrderBy()'s also works after the initial orderBy().
Example:
$this->createQueryBuilder('entity')
->orderBy('entity.addDate', 'DESC')
->addOrderBy('entity.id', 'DESC')
The orderBy method requires either two strings or an Expr\OrderBy object. If you want to add multiple order declarations, the correct thing is to use addOrderBy method, or instantiate an OrderBy object and populate it accordingly:
# Inside a Repository method:
$myResults = $this->createQueryBuilder('a')
->addOrderBy('a.column1', 'ASC')
->addOrderBy('a.column2', 'ASC')
->addOrderBy('a.column3', 'DESC')
;
# Or, using a OrderBy object:
$orderBy = new OrderBy('a.column1', 'ASC');
$orderBy->add('a.column2', 'ASC');
$orderBy->add('a.column3', 'DESC');
$myResults = $this->createQueryBuilder('a')
->orderBy($orderBy)
;
The comment for orderBy source code notes: Keys are field and values are the order, being either ASC or DESC.. So you can do orderBy->(['field' => Criteria::ASC]).

Kohana orm order asc/desc?

I heed two variables storing the maximum id from a table, and the minimum id from the same table.
the first id is easy to be taken ,using find() and a query like
$first = Model::factory('product')->sale($sale_id)->find();
but how can i retrieve the last id? is there a sorting option in the Kohana 3 ORM?
thanks!
Yes, you can sort resulting rows in ORM with order_by($column, $order). For example, ->order_by('id', 'ASC').
Use QBuilder to get a specific values:
public function get_minmax()
{
return DB::select(array('MAX("id")', 'max_id'),array('MIN("id")', 'min_id'))
->from($this->_table_name)
->execute($this->_db);
}
The problem could actually be that you are setting order_by after find_all. You should put it before. People do tend to put it last.
This way it works.
$smthn = ORM::factory('smthn')
->where('something', '=', something)
->order_by('id', 'desc')
->find_all();
Doing like this, I suppose you'll be :
selecting all lines of your table that correspond to your condition
fetching all those lines from MySQL to PHP
to, finally, only work with one of those lines
Ideally, you should be doing an SQL query that uses the MAX() or the MIN() function -- a bit like this :
select max(your_column) as max_value
from your_table
where ...
Not sure how to do that with Kohana, but this topic on its forum looks interesting.

Categories