Подтвердить что ты не робот

Как мне сделать таблицы объединения 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(), потому что у меня есть несколько применений для ассоциации, и я не нашел выражения поиска "один размер подходит всем".

Позднее обновление

Отвечая на мой вопрос через два года, это была недостающая функциональность, которая с тех пор была реализована.

4b9b3361

Ответ 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 },  # >
...