【问题标题】:Binding multiple rows and columns for WHERE IN clause in PostgreSQL在 PostgreSQL 中为 WHERE IN 子句绑定多行和多列
【发布时间】:2012-03-20 17:02:04
【问题描述】:

所以我想准备一个类似的查询:

SELECT id FROM users WHERE (branch, cid) IN $1;

然后将一组可变长度的行(如(('a','b'),('c','d')))绑定到它。

换句话说,类似于:

pg_prepare($users, 'users_query', 'SELECT id FROM users WHERE (branch, cid) IN $1');
$result = pg_execute($users, 'users_query', array("(('a','b'),('c','d'))");

我需要将两者分开的原因是因为我想准备一次,然后以尽可能少的开销运行多次。

【问题讨论】:

  • @dbenhur 它确实有效。我使用的是 PostgreSQL 9.1.3,如果你愿意,我可以粘贴输出吗?
  • 你有关于 users(branch,cid) 的索引吗?如果可以,请为您的实际查询发布解释分析计划。
  • 是的,主键是 (branch, cid)。解释如下: Bitmap Heap Scan on users (cost=8.52..12.54 rows=1 width=8) Recheck Cond: (((branch = 'a'::text) AND (cid = 'b'::text)) OR ((branch = 'c'::text) AND (cid = 'd'::text))) -> BitmapOr (cost=8.52..8.52 rows=1 width=0) -> Bitmap Index Scan on users_pkey ( cost=0.00..4.26 rows=1 width=0) Index Cond: ((branch = 'a'::text) AND (cid = 'b'::text)) -> 位图索引扫描 users_pkey (cost=0.00 ..4.26 rows=1 width=0) Index Cond: ((branch = 'c'::text) AND (cid = 'd'::text)) (7 rows)
  • 您应该将计划编辑到您的问题中,以便您可以更好地格式化它。这个计划有什么问题?它看起来很便宜。此外,请使用 EXPLAIN ANALYZE 而非 EXPLAIN,以便查看实际时间和行数。
  • @Alec:如果您只有两行,查询优化器可能会总是选择顺序扫描。想一想。然后添加更多行。

标签: php sql postgresql


【解决方案1】:

您仅使用两条记录进行顺序扫描这一事实毫无意义。对于这么小的集合,索引永远不会比顺序扫描快。我构建了一个类似于您的小型示例表,并用一百万行填充它,以下查询样式始终如一地产生良好的计划和快速的执行:

prepare s4 as
select id from users
join (select * from (values ($1,$2),($3,$4)) as v(branch, cid)) as p
using (branch, cid);

explain analyze execute s4('b11','c11','b1234','c1234');
                                                    QUERY PLAN                                                    
------------------------------------------------------------------------------------------------------------------
 Nested Loop  (cost=0.00..16.65 rows=1 width=4) (actual time=0.199..0.234 rows=2 loops=1)
   ->  Values Scan on "*VALUES*"  (cost=0.00..0.03 rows=2 width=64) (actual time=0.002..0.003 rows=2 loops=1)
   ->  Index Scan using u_i on users  (cost=0.00..8.30 rows=1 width=16) (actual time=0.111..0.112 rows=1 loops=2)
         Index Cond: ((users.branch = "*VALUES*".column1) AND (users.cid = "*VALUES*".column2))
 Total runtime: 0.425 ms

看来您真正的问题是如何将动态确定数量的值对绑定到您的 sql。我的 PHP 生锈得很厉害,阅读在线文档让我想起了我多么讨厌它,但我认为下面会做你想做的事,使用基于值的数量动态创建的值对占位符的数量来构造上述形式的 sql你想绑定。我没有方便的 php 执行环境,所以我什至没有检查它是否在语法上正确,但是您应该能够理解并解决我的示例中的任何琐碎错误。

$values = array(
  'a', 'b',
  'c', 'd',
  // etc...
);

$value_placeholders = "";
$sep = "";
for ($i=1; $i <= $count($values); $i+=2) {
  $value_placeholders = $value_placeholders . sprintf("($%u,$%u),", $i, $i+1) . $sep
  $sep = ",";
}

$sql =
  'select id from users ' .
  'join (select * from (values ' . $value_placeholders . ') as v(branch, cid)) as p' .
  'using (branch, cid)';

$result = pg_query_params($dbconn, $sql, $values);

如果你真的只需要一个准备好的语句(对于一个懒得实际尝试对真实数据集而不是两条记录的查询的人,我们将完全避免谈论premature optimization ),我想我有一个答案:

create index u_i2 on users ((branch||cid));
prepare sa as select id from users where branch||cid in (select unnest($1::text[]));
explain analyze execute sa(ARRAY['b1c1','b1234c1234']);
                                                      QUERY PLAN                                                      
----------------------------------------------------------------------------------------------------------------------
 Nested Loop  (cost=12.17..645.78 rows=50000 width=4) (actual time=0.169..0.188 rows=2 loops=1)
   ->  HashAggregate  (cost=0.02..0.03 rows=1 width=32) (actual time=0.018..0.019 rows=2 loops=1)
         ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.010..0.011 rows=2 loops=1)
   ->  Bitmap Heap Scan on users  (cost=12.14..638.25 rows=500 width=16) (actual time=0.082..0.082 rows=1 loops=2)
         Recheck Cond: ((users.branch || users.cid) = (unnest($1)))
         ->  Bitmap Index Scan on u_i2  (cost=0.00..12.02 rows=500 width=0) (actual time=0.078..0.078 rows=1 loops=2)
               Index Cond: ((users.branch || users.cid) = (unnest($1)))
 Total runtime: 0.275 ms

注意:: 我无法找到对行对的索引访问。但是如果你在两个字段的连接上创建一个函数索引,然后提供一个这样的连接的绑定数组,你会得到一个很好的快速嵌套循环索引扫描。

【讨论】:

  • 问题是值的数量不是恒定的。正如我在上面的 cmets 中所说,我可以使用 WHERE branch = ANY($1){'a','c'} 之类的参数执行 WHERE branch IN 等效项。问题是我不知道如何使用多个列来处理 WHERE (branch, cid) IN 等效项。
  • @Alec 我给了你代码,它根据值数组的大小来调整占位符的大小。你还需要什么?你试过我发布的php吗?我假设 php 是你的执行环境,因为你用它标记了问题。
  • 是的,PHP 是环境,但这不是我想要的。我正在寻找 pg_prepare 带有占位符的语句,然后 pg_execute 使用不同的值对和不同数量的值对。查看更新的问题。
  • 对于这么简单的查询,计划步骤非常快,因此您并没有从准备好的语句中获得太多收益。
  • @Alec 使用计划好的单占位符准备好的语句解决方案更新了答案
猜你喜欢
  • 2019-06-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-17
相关资源
最近更新 更多