Как мне сделать таблицы соединения DBIx :: Class, используя операторы, отличные от `=`? - PullRequest
14 голосов
/ 11 июня 2009

Резюме

У меня есть таблица предметов, которые идут парами. Я хотел бы присоединиться к нему самостоятельно, чтобы получить обе стороны пары в одном запросе. Это действительный 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 :: Абстрактный синтаксис.

Я пытался написать отношение 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 за stringref также не делает это:

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

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

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

Ответы [ 6 ]

11 голосов
/ 05 августа 2011

Для тех, кто все еще интересуется этим, он, наконец, был реализован с 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" } },
  }
});

Trackback: DBIx :: Класс расширенных отношений в блоге fREW Schmidt , где я впервые прочитал об этом.

3 голосов
/ 08 августа 2009

Я думаю, что вы могли бы сделать это, создав новый тип отношений, расширяющих DBIx::Class::Relationship::Base, но это не кажется невероятно хорошо документированным. Рассматривали ли вы возможность простого добавления вспомогательного метода в набор результатов для Half, который выполняет ->search({}, { join => ... } и возвращает вам набор результатов из этого? Это не интроспективно, как отношения, но кроме того, что это работает в значительной степени также. Он использует способность DBIC объединять запросы в ваших интересах.

1 голос
/ 11 июня 2009

Вы пробовали:

__PACKAGE__->has_one(dual => 'Schema::Half', {
'foreign.whole_id' => 'self.whole_id',
'foreign.half_id' => {'<>' => 'self.half_id'},
});

Я считаю, что критерии поиска в определении взаимосвязи те же, что и при поиске.

1 голос
/ 11 июня 2009

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 с использованием операторов, отличных от =?», Но приведенный вами пример не оправдывает такой необходимости.

0 голосов
/ 18 ноября 2009

Вот как это сделать:

...
field => 1,                  # =
otherfield => { '>' => 2 },  # >
...
0 голосов
/ 12 июня 2009

'foreign.half_id' => \'<> self.half_id'

...