【问题标题】:How natural sorting a varchar column with Doctrine使用 Doctrine 如何自然地对 varchar 列进行排序
【发布时间】:2020-02-21 20:05:18
【问题描述】:

我有一个表,其中有一列包含名称 (varchar),但有些名称里面有数字,并且顺序不是预期的。

我有类似的东西:

Courlis
D11 Espadon
D13 Balbuzard
D1 empacher
D2

但我希望:

Courlis
D1 empacher
D2
D11 Espadon
D13 Balbuzard

我发现了很多关于它的提示,但它总是关于仅存储为字符串的数字排序:添加一个 0 以将其转换为数字,检查字符串的长度以将 1 放在 10 之前,等等......但是在我的情况下它不能工作。

我不能使用 SQL 查询,因为我以需要 QueryBuilder 的 Symfony 应用程序的形式使用它。

【问题讨论】:

  • 这个订单有什么“疯狂”的地方?
  • 这只是一种说话方式,只是它不是我们在订购商品时所期望的。我们期望顺序字母,然后是数字,顺序如下:1、2、3、4、5、6、7、8、9、10、11、12。不是:1、10、11、12、2 , 3, 4, 5, 6, 7, 8, 9
  • 这个问题的边界重复。 stackoverflow.com/questions/5967500/…
  • 嗯,我不太明白python问题的答案。它与 Doctrine Query Builder 并没有真正的关系。目前,我发现的唯一工作是使用 ORDER BY name::bytea ASC; 的纯 SQL 请求,但它在 Doctrine 中不起作用。
  • 你可以在 PostgreSQL 从 v10 开始使用 ICU 排序规则。

标签: postgresql symfony doctrine doctrine-query natural-sort


【解决方案1】:

这是一种在 PostgreSQL 中使用 ICU 排序规则的方法(从 v10 开始可用):

CREATE COLLATION en_natural (
   LOCALE = 'en-US-u-kn-true',
   PROVIDER = 'icu'
);

CREATE TABLE atable (textcol text COLLATE en_natural);

COPY atable FROM STDIN;
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself, or an EOF signal.
>> Courlis
>> D11 Espadon
>> D13 Balbuzard
>> D1 empacher
>> D2
>> \.

test=# SELECT textcol FROM atable ORDER BY textcol;

    textcol    
---------------
 Courlis
 D1 empacher
 D2
 D11 Espadon
 D13 Balbuzard
(5 rows)

【讨论】:

  • 谢谢,它运作良好。我只是在寻找一种使用 Doctrine 解决方案的方法。
【解决方案2】:

感谢 Laurentz Albe 的回答,以及在 Symfony 应用程序中的逐步说明:

  1. 创建用于创建自定义排序规则的迁移文件
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
 * Auto-generated Migration: Please modify to your needs!
 */
final class Version20200221215657 extends AbstractMigration
{
    public function getDescription() : string
    {
        return '';
    }

    public function up(Schema $schema) : void
    {
        // this up() migration is auto-generated, please modify it to your needs
        $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');

        $this->addSql('CREATE COLLATION fr_natural (provider = "icu", locale = "fr-u-kn-true");');
    }

    public function down(Schema $schema) : void
    {
        // this down() migration is auto-generated, please modify it to your needs
        $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');

        $this->addSql('DROP COLLATION fr_natural');
    }
}
  1. 创建 Doctrine 自定义函数(Collat​​e DQL)
<?php

namespace App\DQL;

use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Lexer;

class Collate extends FunctionNode
{
    public $expressionToCollate = null;
    public $collation = null;

    public function parse(\Doctrine\ORM\Query\Parser $parser)
    {
        $parser->match(Lexer::T_IDENTIFIER);
        $parser->match(Lexer::T_OPEN_PARENTHESIS);

        $this->expressionToCollate = $parser->StringPrimary();

        $parser->match(Lexer::T_COMMA);
        $parser->match(Lexer::T_IDENTIFIER);

        $lexer = $parser->getLexer();

        $this->collation = $lexer->token['value'];

        $parser->match(Lexer::T_CLOSE_PARENTHESIS);
    }

    public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
    {
        return sprintf( '%s COLLATE %s', $sqlWalker->walkStringPrimary($this->expressionToCollate), $this->collation );
    }
}
  1. 在 Doctrine 配置中注册新功能
doctrine:
    dbal:
        url: '%env(resolve:DATABASE_URL)%'

        # IMPORTANT: You MUST configure your server version,
        # either here or in the DATABASE_URL env var (see .env file)
        server_version: '12'
    orm:
        auto_generate_proxy_classes: true
        naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
        auto_mapping: true
        mappings:
            App:
                is_bundle: false
                type: annotation
                dir: '%kernel.project_dir%/src/Entity'
                prefix: 'App\Entity'
                alias: App
        dql:
            string_functions:
                collate: App\DQL\Collate

  1. 最后在需要时在查询中使用它
$query = $this->createQueryBuilder('shell')
    ->orderBy('COLLATE(shell.name, fr_natural)', 'ASC')
    ->getQuery();

return $query->getResult();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-28
    相关资源
    最近更新 更多