DBD :: SQLite, как передать массив в запрос через заполнитель? - PullRequest
0 голосов
/ 09 апреля 2011

Давайте иметь таблицу:

sqlite> create table foo (foo int, bar int);
sqlite> insert into foo (foo, bar) values (1,1);
sqlite> insert into foo (foo, bar) values (1,2);
sqlite> insert into foo (foo, bar) values (1,3);

Затем ВЫБЕРИТЕ некоторые данные:

sqlite> select * from foo where foo = 1 and bar in (1,2,3);
1|1
1|2
1|3

Работает все в порядке.Сейчас я пытаюсь использовать DBD :: SQLite 1.29:

my $sth = $dbh->prepare('select * from foo where foo = $1 and bar in ($2)');
$sth->execute(1,[1,2,3]);

И это дает мне нулевые результаты.Трассировка DBI показывает, что 2-й заполнитель привязан к массиву, но без оценки.Если я join массив значений в строке и передать его, нет результата.Если я сглаживаю массив, я получаю предсказуемую ошибку «вызывается с N заполнителями вместо 2».

Я вроде как в растерянности.Что еще можно попробовать?

Upd: Хорошо, вот один добросовестный пример, взятый из реального приложения.

Во-первых, установка: у меня есть несколькоТаблицы заполнены статистическими данными, количество столбцов варьируется от 10 до 700+.Запросы, о которых я говорю, выбирают подмножество этих данных для целей отчетности.Разные отчеты учитывают разные аспекты и, следовательно, запускают разные запросы, по одному или более на запрос.Есть более 200 отчетов, то есть 200-300 запросов.Этот подход был разработан для Postgres, и теперь мне нужно уменьшить его и заставить работать с SQLite.Учитывая, что все это хорошо работает с Postgres, я не могу оправдать просмотр всех запросов и их переписывание.Плохо для обслуживания.Я могу и действительно использовать корректировки запросов на месте, такие как замена = ANY () на IN (), это второстепенные аспекты.

Итак, вот мой пример: 2 запроса выполнялись подряд для одного отчета:

SELECT SPLIT, syn(SPLIT),
(SELECT COUNT(*) FROM cagent WHERE ACD = $1 AND SPLIT = $2 AND
LOC_ID = ANY ($3) AND LOGID IS NOT NULL AND WORKMODE = 40),
(SELECT COUNT(*) FROM cagent WHERE ACD = $1 AND SPLIT = $2 AND
LOC_ID = ANY ($3) AND LOGID IS NOT NULL AND WORKMODE = 30),
(SELECT COUNT(*) FROM cagent WHERE ACD = $1 AND SPLIT = $2 AND
LOC_ID = ANY ($3) AND LOGID IS NOT NULL AND WORKMODE = 50),
(SELECT COUNT(*) FROM cagent WHERE ACD = $1 AND SPLIT = $2 AND
LOC_ID = ANY ($3) AND LOGID IS NOT NULL AND WORKMODE = 220),
(SELECT COUNT(*) FROM cagent WHERE ACD = $1 AND SPLIT = $2 AND
LOC_ID = ANY ($3) AND LOGID IS NOT NULL),
(SELECT COUNT(*) FROM cagent WHERE ACD = $1 AND SPLIT = $2 AND
LOC_ID = ANY ($3) AND LOGID IS NOT NULL AND WORKMODE = 20),
(SELECT COUNT(*) FROM cagent WHERE ACD = $1 AND SPLIT = $2 AND
LOC_ID = ANY ($3) AND LOGID IS NOT NULL AND WORKMODE = 80)
FROM csplit WHERE ACD = $1 AND SPLIT = $2

SELECT syn(LOGID), syn(LOC_ID), LOGID, EXTENSION, syn(ROLE), PERCENT,
syn(AUXREASON), syn(AWORKMODE), syn(DIRECTION), WORKSKILL, syn(WORKSKLEVEL),
AGTIME FROM cagent WHERE ACD = $1 AND SPLIT = $2 AND LOC_ID = ANY ($3) AND
LOGID IS NOT NULL

Это не самый сложный пример, так как может быть любое количество входных параметров, используемых и повторно используемых в разных местах в запросе;замена их на общие ? заполнители не является тривиальной задачей.Код, который выполняет запросы к Postgres, выглядит так (после очистки ввода и др.):

sub run_select {
  my ($class, $dbh, $sql, @bind_values) = @_;

  my $sth;
  eval {
    $sth = $dbh->prepare_cached($sql);
    $sth->execute(@bind_values);
  };
  $@ and die "Error executing query: $@";

  my %types;
  {
    my $dbt = $dbh->type_info_all;
    @types{ map { $_->[1] } @$dbt[1..$#$dbt] } =
        map { $_->[0] } @$dbt[1..$#$dbt];
  };

  my @result;

  while (my $row = $sth->fetchrow_arrayref) {
    my $i = 0;
    push @result, [ map { [ $types{${$sth->{TYPE}}[$i++]}, $_ ] } @$row ];
  };

  return \@result;
};

Я могу переписать запросы и ввести значения напрямую;Внедрение SQL не представляет большой угрозы, потому что все входные данные не обрабатываются через шаблоны регулярных выражений задолго до того, как они могут попасть в движок SQL.Я не хочу переписывать запросы динамически по двум причинам: а) это может потенциально привести к проблемам с цитированием значения и б) это как бы убивает всю причину, что стоит за prepare_cached.Движок SQL не может кэшировать повторно используемое подготовленное утверждение, если оно меняется каждый раз.

Теперь, как я уже сказал, приведенный выше код хорошо работает с Postgres.Поскольку сам движок SQLite, очевидно, имеет возможность работать с наборами данных, я подумал, что это недостаток в реализации DBD :: SQLite.Таким образом, реальный вопрос звучит так: есть ли способ передать набор данных в заполнитель с помощью DBD :: SQLite?Не обязательно массив, хотя это было бы наиболее логичным.

Ответы [ 2 ]

3 голосов
/ 09 апреля 2011

Попробуйте это:

my $sth = $dbh->prepare("select * from foo where foo = ? and bar in (?,?,?)";
$sth->execute(1,1,2,3);

Вы можете использовать оператор повторения x для генерации необходимого числа ? s:

my $sql = sprintf "select ... and bar in (%s)", join ",", ('?')x@values; 
1 голос
/ 10 апреля 2011

Использование SQL :: Abstract , например:

use strict;
use warnings;
use SQL::Abstract;

my $sqla = SQL::Abstract->new;
my %where  = (
    foo => 1,
    bar => { -in => [1,2,3] }
);

my ($sql, @params) = 
    $sqla->select('foo', '*', \%where);

my $sth = $dbh->prepare($sql);
$sth->execute(@params);
...