如何在DQL中按NULL排序?

时间:2012-09-29 10:43:59

标签: symfony doctrine-orm dql

我正在使用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')

但这给了我一个语法错误。

学说文档也没有给我答案。有办法吗?

6 个答案:

答案 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)

答案 5 :(得分:1)

默认情况下,MySQL仍会对NULL值进行排序;它只是将它放在结果集的开头(如果它已经排序ASC),并且最后如果它已经排序DESC。在这里,您希望对ASC进行排序,但希望NULL值位于底部。

不幸的是,尽管功能如此强大,但Doctrine并不会在此提供太多支持,因为功能支持有限,而且大部分仅限于SELECTWHERE和{{ 1}}条款。如果以下任何一个关于QueryBuilder的确是如此,那么你实际上根本不会遇到任何问题:

  • HAVING已接受select()
  • ISNULL()orderBy()支持addOrderBy()
  • 该课程支持ISNULL() s的概念(有了这个,你可以运行两个查询:UNIONcodeIata的一个,而不是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,所以它可能只适用于相对较小的数据集。

无论如何,希望这能让你顺利上路!