Как заставить DBIx :: Class игнорировать регистр в ORDER BY с SQLite?

Обычно сортировка SQLite учитывает регистр. Все заглавные буквы идут перед маленькими. Но можно указать SQLite в предложении ORDER BY игнорировать это, сделав это:

... ORDER BY foo COLLATE NOCASE ASC

Но как это сделать с помощью DBIx :: Class?

Рассмотрим следующий пример, который развертывает базу данных SQLite в памяти с таблицей foo и одним столбцом bar. Для подключения используется параметр quote_names. Он заполняет значения z Z b B a A, а затем возвращает их, используя all в ResultSet. Я буду использовать эту настройку во всех следующих примерах. Вам понадобится DBIx :: Class и DBD :: SQLite, чтобы запустить это.

use strict;
use warnings;

package Foo::Schema::Result::Foo;
use base 'DBIx::Class::Core';
__PACKAGE__->table("foo");
__PACKAGE__->add_columns( "bar", { data_type => "text" }, );

package Foo::Schema;
use base 'DBIx::Class::Schema';

__PACKAGE__->register_class( 'Foo' => 'Foo::Schema::Result::Foo' );

package main;
my $schema = Foo::Schema->connect(
    {
        dsn         => 'dbi:SQLite:dbname=:memory:',
        quote_names => 1,
    }
);
$schema->deploy;

$schema->resultset('Foo')->create( { bar => $_ } ) for qw(z Z b B a A);
my @all = $schema->resultset('Foo')->search(
    {},
    {
        order_by => { -asc => 'me.bar' },
    },
)->all;

# example code starts here

print join q{ }, map { $_->bar } @all;

Вывод отсортирован с учетом регистра.

A B Z a b z

Теперь, конечно, я мог бы отсортировать его с помощью Perl и сделать его нечувствительным к регистру, вот так.

print join q{ }, sort { lc $a cmp lc $b } map { $_->bar } @all;

Теперь я получаю

A a B b Z z

Но я также могу использовать COLLATE NOCASE, если я запрашиваю напрямую с помощью базового дескриптора DBI.

$schema->storage->dbh_do(
    sub {
        my ( $storage, $dbh, @args ) = @_;
        my $res = $dbh->selectall_arrayref(
            "SELECT * FROM foo ORDER BY bar COLLATE NOCASE ASC"
        );
        print "$_->[0] " for @$res;
    }

Это дает нам

a A b B z Z  

Я хочу, чтобы DBIC использовал COLLATE NOCASE, но без выполнения SQL. Я не хочу делать дорогостоящие преобразования строк в ORDER BY или делать их позже в Perl.

Как мне указать DBIx :: Class использовать COLLATE NOCASE при заказе с помощью SQLite?


Следующее не работает:

order_by => { '-collate nocase asc' => 'me.bar' },

И это работает, только если quote_names не включен.

order_by => { -asc => 'me.bar COLLATE NOCASE' },

Он выдаст этот запрос и сообщение об ошибке с приведенным выше примером кода.

ВЫБЕРИТЕ "меня". "Bar" ОТ "foo" "me" ЗАКАЗАТЬ "me". "Bar COLLATE NOCASE" ASC: DBIx :: Class :: Storage :: DBI :: _ prepare_sth (): DBI Exception: DBD :: Ошибка SQLite :: db prepare_cached: такого столбца нет: me.bar COLLATE NOCASE [для оператора "SELECT" me "." Bar "FROM" foo "" me "ORDER BY" me "." Bar COLLATE NOCASE "ASC"]

Или я мог бы сделать это, преобразовав в upper или lower в предложении ORDER BY с помощью DBIC.

my @all = $schema->resultset('Foo')->search(
    {},
    {
        order_by => { -asc => 'lower(me.bar)' },
    },
)->all;

print join q{ }, map { $_->bar } @all;

Это дает

a A b B z Z

без quote_names, что похоже, но наоборот. (Это меня не касается), но также выдает ошибку при включении quote_names.

ВЫБЕРИТЕ "меня". "Bar" ОТ "foo" "me" ORDER BY "lower (me". "Bar)" ASC: DBIx :: Class :: Storage :: DBI :: _ prepare_sth (): DBI Exception: DBD: : SQLite :: db prepare_cached не удалось: такого столбца нет: lower (me.bar) [для утверждения "SELECT" me "." Bar "FROM" foo "" me "ORDER BY" lower (me "." Bar) "ASC "]


person simbabque    schedule 03.01.2017    source источник
comment
AFAIK, единственный способ сделать это - использовать буквальный SQL, что означает передачу скалярной ссылки: order_by => \'me.bar COLLATE NOCASE ASC'. FWIW, это тоже работает: order_by => { -asc => \'me.bar COLLATE NOCASE' }.   -  person Matt Jacob    schedule 03.01.2017
comment
Хотите написать ответ @Matt? Литеральный SQL - это нормально, даже если он связывает нас с SQLite. В этом случае мне все равно.   -  person simbabque    schedule 03.01.2017
comment
Я не решался опубликовать ответ, потому что вы сказали, что не хотите использовать какой-либо SQL, и потому что я не был уверен (и до сих пор не уверен), есть ли лучший способ сделать это.   -  person Matt Jacob    schedule 03.01.2017
comment
@matt, похоже, ты прочитал мой пост лучше, чем я. Вы правы, я сказал это, но имел в виду пример с использованием dbh. ;) Если есть лучший ответ, мы его найдем. Но это хорошее начало. Спасибо.   -  person simbabque    schedule 03.01.2017


Ответы (1)


Если вам удобно использовать очень небольшой объем SQL, вы можете передать скалярную ссылку для обозначения буквального SQL, и DBIC не будет с ней связываться:

order_by => \'me.bar COLLATE NOCASE ASC'

Или с минимальным количеством буквального SQL:

order_by => { -asc => \'me.bar COLLATE NOCASE' }

Обратите внимание, что этот синтаксис технически не рекомендуется, но я не знаю любого другого способа достичь того, что вам нужно:

Старый синтаксис scalarref (то есть order_by => \ 'year DESC') по-прежнему поддерживается, хотя вам настоятельно рекомендуется использовать синтаксис hashref, как описано выше.

person Matt Jacob    schedule 03.01.2017
comment
Стоит отметить, что синтаксис scalarref order_by => \SCALAR не приветствуется, но он подразумевает, что синтаксис scalarref-in-a-hashref order_by => { -asc => \SCALAR } по-прежнему отлично сочетается - person Altreus; 09.01.2017