Как мне сделать таблицы объединения DBIx:: Class с помощью других операторов, чем `=`?
Резюме
У меня есть таблица предметов, которые попадают парами. Я хотел бы присоединиться к нему, чтобы я мог получить обе стороны пары в одном запросе. Это действительный SQL (я думаю), SQLite-движок действительно его принимает, но мне трудно получить DBIx:: Class, чтобы укусить пулю.
Минимальный пример
package Schema::Half;
use parent 'DBIx::Class';
__PACKAGE__->load_components('Core');
__PACKAGE__->table('half');
__PACKAGE__->add_columns(
whole_id => { data_type => 'INTEGER' },
half_id => { data_type => 'CHAR' },
data => { data_type => 'TEXT' },
);
__PACKAGE__->has_one(dual => 'Schema::Half', {
'foreign.whole_id' => 'self.whole_id',
'foreign.half_id' => 'self.half_id',
# previous line results in a '='
# I'd like a '<>'
});
package Schema;
use parent 'DBIx::Class::Schema';
__PACKAGE__->register_class( 'Half', 'Schema::Half' );
package main;
unlink 'join.db';
my $s = Schema->connect('dbi:SQLite:join.db');
$s->deploy;
my $h = $s->resultset('Half');
$h->populate([
[qw/whole_id half_id data /],
[qw/1 L Bonnie/],
[qw/1 R Clyde /],
[qw/2 L Tom /],
[qw/2 R Jerry /],
[qw/3 L Batman/],
[qw/3 R Robin /],
]);
$h->search({ 'me.whole_id' => 42 }, { join => 'dual' })->first;
Последняя строка генерирует следующий SQL:
SELECT me.whole_id, me.half_id, me.data
FROM half me
JOIN half dual ON ( dual.half_id = me.half_id AND dual.whole_id = me.whole_id )
WHERE ( me.whole_id = ? )
Я пытаюсь использовать синтаксис соединения DBIx:: Class, чтобы получить оператор <>
между dual.half_id
и me.half_id
, но пока этого не удалось.
Вещи, которые я пробовал
Документация указывает на синтаксис SQL:: Abstract-like.
Я попытался написать отношение has_one
как таковое:
__PACKAGE__->has_one(dual => 'Schema::Half', {
'foreign.whole_id' => 'self.whole_id',
'foreign.half_id' => { '<>' => 'self.half_id' },
});
# Invalid rel cond val HASH(0x959cc28)
Прямой SQL за строкойref также не делает этого:
__PACKAGE__->has_one(dual => 'Schema::Half', {
'foreign.whole_id' => 'self.whole_id',
'foreign.half_id' => \'<> self.half_id',
});
# Invalid rel cond val SCALAR(0x96c10b8)
Обходные пути и почему они недостаточны для меня
Я мог бы получить правильный SQL, который будет сгенерирован с помощью сложного вызова search()
, и не будет определено отношение. Это довольно уродливое, с (слишком) сильно жестко запрограммированным SQL. Он должен подражать нефакторизуемому способу для каждого конкретного случая, когда пересечение происходит.
Я мог бы решить проблему, добавив столбец other_half_id
и присоединившись к =
. Это явно избыточные данные.
Я даже попытался уклониться от указанной избыточности, добавив ее через выделенное представление (CREATE VIEW AS SELECT *, opposite_of(side) AS dual FROM half...
). Вместо схемы базы данных это код, получивший избыточность и уродство, более, чем обходное решение search()
. В конце концов, я не был достаточно храбр, чтобы заставить его работать.
Желаемый SQL
Вот такой SQL, который я ищу. Обратите внимание, что это всего лишь пример: я действительно хочу, чтобы это было сделано через отношения, поэтому я могу использовать его в качестве Half
Accessult Access также в дополнение к предложению search()
join
.
sqlite> SELECT *
FROM half l
JOIN half r ON l.whole_id=r.whole_id AND l.half_id<>r.half_id
WHERE l.half_id='L';
1|L|Bonnie|1|R|Clyde
2|L|Tom|2|R|Jerry
3|L|Batman|3|R|Robin
Боковые заметки
Я действительно присоединяюсь к себе в моем полном расширенном случае, но я уверен, что это не проблема. Я сохранил его таким образом для уменьшенного случая, потому что он также помогает поддерживать небольшой размер кода.
Я сохраняю путь соединения/отношения вместо сложного search()
, потому что у меня есть несколько применений для ассоциации, и я не нашел выражения поиска "один размер подходит всем".
Позднее обновление
Отвечая на мой вопрос через два года, это была недостающая функциональность, которая с тех пор была реализована.
Ответы
Ответ 1
Для тех, кто по-прежнему заинтересован в этом, он, наконец, был реализован с 0.08192 или ранее. (Сейчас я на 0.08192)
Один правильный синтаксис:
__PACKAGE__->has_one(dual => 'Schema::Half', sub {
my $args = shift;
my ($foreign,$self) = @$args{qw(foreign_alias self_alias)};
return {
"$foreign.whole_id" => { -ident => "$self.whole_id" },
"$foreign.half_id" => { '<>' => { -ident => "$self.half_id" } },
}
});
Трекбэк: DBIx:: Class Расширенные отношения в блоге fREW Schmidt, где я должен был сначала прочитать об этом.
Ответ 2
Я думаю, что вы могли бы сделать это, создав новый тип отношений, расширяющий DBIx::Class::Relationship::Base
, но он не выглядит невероятно хорошо документированным. Вы рассмотрели возможность добавления метода удобства в набор результатов для Half
, который выполняет ->search({}, { join => ... }
, и возвращает набор результатов от этого к вам? Это не интроспективно, как отношения, но кроме того, что он работает очень хорошо. Он использует возможности DBIC для привязки запросов к вашим преимуществам.
Ответ 3
JB, обратите внимание, что вместо:
SELECT *
FROM half l
JOIN half r ON l.whole_id=r.whole_id AND l.half_id<>r.half_id
WHERE l.half_id='L';
Вы можете написать тот же запрос, используя:
SELECT *
FROM half l
JOIN half r ON l.whole_id=r.whole_id
WHERE l.half_id<>r.half_id AND l.half_id='L';
Который вернет те же данные и определенно проще выразить с помощью DBIx:: Class.
Конечно, это не отвечает на вопрос "Как мне сделать таблицы соединений DBIx:: Class с помощью других операторов, чем =
?", но приведенный вами пример не оправдывает такую потребность.
Ответ 4
Вы пробовали:
__PACKAGE__->has_one(dual => 'Schema::Half', {
'foreign.whole_id' => 'self.whole_id',
'foreign.half_id' => {'<>' => 'self.half_id'},
});
Я считаю, что критерии соответствия в определении отношения используются для поиска.
Ответ 5
'foreign.half_id' => \'<> self.half_id'
Ответ 6
Вот как это сделать:
...
field => 1, # =
otherfield => { '>' => 2 }, # >
...