Как естественно сортировать колонну varchar с Doctrine - PullRequest
0 голосов
/ 21 февраля 2020

У меня есть таблица со столбцом, который содержит имена (varchar), но у некоторых имен есть числа внутри, и порядок не является ожидаемым.

У меня есть что-то вроде:

Courlis
D11 Espadon
D13 Balbuzard
D1 empacher
D2

Но я ожидаю:

Courlis
D1 empacher
D2
D11 Espadon
D13 Balbuzard

Я нашел много советов по этому поводу, но это всегда при заказе номеров, хранящихся только в виде строки: добавьте 0, чтобы преобразовать его в числа, проверьте длину строки в место 1 перед 10, et c ... Но в моем случае это не может работать.

Я не могу использовать запрос SQL, потому что я использую его в форме Symfony приложение, которому нужен QueryBuilder.

Ответы [ 2 ]

2 голосов
/ 22 февраля 2020

Вот способ сделать это с сопоставлениями ICU в PostgreSQL (доступно начиная с 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)
0 голосов
/ 22 февраля 2020

Спасибо Лоренцу Альбе за ваш ответ, шаг за шагом в 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');
    }
}
Создание пользовательской функции Doctrine (Collate 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 );
    }
}
Зарегистрировать новую функцию в конфигурации 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

И, наконец, используйте его в запросе, когда это необходимо
$query = $this->createQueryBuilder('shell')
    ->orderBy('COLLATE(shell.name, fr_natural)', 'ASC')
    ->getQuery();

return $query->getResult();
...