9

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

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

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

4

6 回答 6

18

您可以在 DQL 中使用以下技巧对 NULL 值进行最后排序

$em->createQuery("SELECT c, -c.weight AS HIDDEN inverseWeight FROM Entity\Car c ORDER BY inverseWeight DESC");

关键字(自HIDDENDoctrine 2.2 起可用)将导致从结果集中省略该inverseWeight字段,从而防止不希望的混合结果

(排序字段值是倒置的,因此顺序也必须倒置,这就是查询使用DESCorder,而不是ASC.)

学分属于这个答案

于 2014-04-11T08:44:30.277 回答
14

最不显眼的通用解决方案是将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。

于 2016-02-03T17:36:22.187 回答
9

如果您想在 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);
于 2016-03-11T08:35:17.640 回答
7

这是一个自定义步行器的示例,可以准确地获得您想要的东西。我从 Doctrine 的 github 问题中获取了它:

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();
于 2013-01-02T11:45:41.663 回答
3

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

于 2012-09-29T16:53:25.323 回答
0

默认情况下,MySQL 仍然会对一个NULL值进行排序;如果已排序,它将仅将其放在结果集的开头,如果已排序ASC,则将其放在末尾DESC。在这里,您正在寻找 sort ASC,但您希望NULL值位于底部。

不幸的是,尽管 Doctrine 功能强大,但在这里不会提供太多支持,因为功能支持是有限的,而且大部分仅限于SELECT, WHERE, 和HAVING子句。如果 QueryBuilder 符合以下任何一项,那么您实际上根本不会遇到问题:

  • select()公认ISNULL()
  • orderBy()addOrderBy()支持ISNULL()
  • 该类支持UNIONs 的概念(有了这个,您可以运行两个查询:一个是在哪里codeIataNULL一个在哪里不是,你可以独立地对每个查询进行排序)

也就是说,您可以使用 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;

这两者的缺点是您将无法LIMIT在 MySQL 中执行 s,因此它可能仅适用于相对较小的数据集。

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

于 2012-09-29T17:34:17.803 回答