我正在使用Symfony2框架和使用Doctrine ORM构建应用程序。我有一张航空公司的表格,其中缺少一些IATA代码。我正在输出一个按此IATA代码排序的列表,但是我得到了一个不良结果,即带有空IATA代码的记录在顶部排序。
在MySQL中,使用ORDER BY ISNULL(code_iata), code_iata
这很简单,但我对DQL的等效内容一无所知。我试过了
$er->createQueryBuilder('airline')->orderBy('ISNULL(airline.codeIata), airline.codeIata', 'ASC')
但这给了我一个语法错误。
学说文档也没有给我答案。有办法吗?
答案 0 :(得分:13)
您可以在DQL中使用以下技巧来最后命令NULL值
$em->createQuery("SELECT c, -c.weight AS HIDDEN inverseWeight FROM Entity\Car c ORDER BY inverseWeight DESC");
HIDDEN
关键字(自Doctrine 2.2开始提供)将导致省略结果集中的inverseWeight
字段,从而防止出现不受欢迎的mixed results。
(排序字段值已反转,因此订单也必须反转,这就是查询使用DESC
订单而不是ASC
的原因。)
积分属于this answer。
答案 1 :(得分:8)
最不引人注目的通用解决方案是将CASE
表达式与HIDDEN
关键字结合使用。
SELECT e,
CASE WHEN e.field IS NULL THEN 1 ELSE 0 END HIDDEN _isFieldNull
FROM FooBundle:Entity e
ORDER BY _isFieldNull ASC
适用于数字和其他字段类型,不需要扩展Doctrine。
答案 2 :(得分:5)
这是一个自定义助行器可以准确获得所需内容的示例。我从它的github问题中接受了Doctrine:
https://github.com/doctrine/doctrine2/pull/100
但是MySQL中的代码并不适用于我。我已将其修改为在MySQL中工作,但我还没有对其他引擎进行测试。
将以下walker类放在YourNS\Doctrine\Waler\
目录中;
<?php
namespace YourNS\Doctrine\Walker;
use Doctrine\ORM\Query\SqlWalker;
class SortableNullsWalker extends SqlWalker
{
const NULLS_FIRST = 'NULLS FIRST';
const NULLS_LAST = 'NULLS LAST';
public function walkOrderByClause($orderByClause)
{
$sql = parent::walkOrderByClause($orderByClause);
if ($nullFields = $this->getQuery()->getHint('SortableNullsWalker.fields'))
{
if (is_array($nullFields))
{
$platform = $this->getConnection()->getDatabasePlatform()->getName();
switch ($platform)
{
case 'mysql':
// for mysql the nulls last is represented with - before the field name
foreach ($nullFields as $field => $sorting)
{
/**
* NULLs are considered lower than any non-NULL value,
* except if a – (minus) character is added before
* the column name and ASC is changed to DESC, or DESC to ASC;
* this minus-before-column-name feature seems undocumented.
*/
if ('NULLS LAST' === $sorting)
{
$sql = preg_replace_callback('/ORDER BY (.+)'.'('.$field.') (ASC|DESC)/i', function($matches) {
if ($matches[3] === 'ASC') {
$order = 'DESC';
} elseif ($matches[3] === 'DESC') {
$order = 'ASC';
}
return ('ORDER BY -'.$matches[1].$matches[2].' '.$order);
}, $sql);
}
}
break;
case 'oracle':
case 'postgresql':
foreach ($nullFields as $field => $sorting)
{
$sql = preg_replace('/(\.' . $field . ') (ASC|DESC)?\s*/i', "$1 $2 " . $sorting, $sql);
}
break;
default:
// I don't know for other supported platforms.
break;
}
}
}
return $sql;
}
}
然后:
use YourNS\Doctrine\Walker\SortableNullsWalker;
use Doctrine\ORM\Query;
[...]
$qb = $em->getRepository('YourNS:YourEntity')->createQueryBuilder('e');
$qb
->orderBy('e.orderField')
;
$entities = $qb->getQuery()
->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, '\YourNS\Doctrine\Walker\SortableNullsWalker')
->setHint('SortableNullsWalker.fields', array(
'sortOrder' => SortableNullsWalker::NULLS_LAST
))
->getResult();
答案 3 :(得分:3)
如果你想在SQL中执行类似于“NULLS LAST”的操作(在我的例子中使用PostgreSQL):
ORDER BY freq DESC NULLS LAST
您可以将COALESCE功能与Doctrine Query Builder一起使用 (HIDDEN将隐藏查询结果集中的“freq”字段。)
$qb = $this->createQueryBuilder('d')
->addSelect('COALESCE(d.freq, 0) AS HIDDEN freq')
->orderBy('freq', 'DESC')
->setMaxResults(20);
答案 4 :(得分:2)
DQL不包含纯SQL的所有功能。幸运的是,您可以定义自定义DQL方法来完成此任务。
部分资源:
http://punkave.com/window/2012/07/24/for-the-php-crowd-adding-custom-functions-to-doctrine-2-dql
http://docs.doctrine-project.org/en/2.1/cookbook/dql-user-defined-functions.html
http://symfony.com/doc/2.0/cookbook/doctrine/custom_dql_functions.html
答案 5 :(得分:1)
默认情况下,MySQL仍会对NULL
值进行排序;它只是将它放在结果集的开头(如果它已经排序ASC
),并且最后如果它已经排序DESC
。在这里,您希望对ASC
进行排序,但希望NULL
值位于底部。
不幸的是,尽管功能如此强大,但Doctrine并不会在此提供太多支持,因为功能支持有限,而且大部分仅限于SELECT
,WHERE
和{{ 1}}条款。如果以下任何一个关于QueryBuilder的确是如此,那么你实际上根本不会遇到任何问题:
HAVING
已接受select()
ISNULL()
或orderBy()
支持addOrderBy()
ISNULL()
s的概念(有了这个,你可以运行两个查询:UNION
为codeIata
的一个,而不是NULL
的一个查询可以独立排序)如上所述,您可以使用ArtWorkAD已经提到的用户定义的函数,或者您可以使用两个不同的Doctrine查询复制最后一个点:
$airlinesWithCode = $er->createQueryBuilder("airline")
->where("airline.iataCode IS NULL")
->getQuery()
->getResult();
$airlinesWithoutCode = $er->createQueryBuilder("airline")
->where("airline.iataCode IS NOT NULL")
->getQuery()
->getResult();
然后,您可以将它们组合成一个数组,或者在模板中单独处理它们。
另一个想法是让DQL在一个数据集中返回所有内容,让PHP完成繁重的工作。类似的东西:
$airlines = $er->findAll();
$sortedAirlines = array();
// Add non-NULL values to the end if the sorted array
foreach ($airlines as $airline)
if ($airline->getCodeIata())
$sortedAirlines[] = $airline;
// Add NULL values to the end of the sorted array
foreach ($airlines as $airline)
if (!$airline->getCodeIata())
$sortedAirlines[] = $airline;
这两个方面的缺点是你将无法在MySQL中执行LIMIT
,所以它可能只适用于相对较小的数据集。
无论如何,希望这能让你顺利上路!