postgresql - Postgres 准备了具有不同字段的语句

标签 postgresql prepared-statement field

我正在寻找将数据库访问抽象为 postgres 的方法。在我的示例中,我将在 nodejs 中使用一个假设的 Twitter 克隆,但最后是关于 postgres 如何处理准备好的语句的问题,因此语言和库并不重要:

假设我希望能够通过用户名访问用户的所有推文列表:

name: "tweets by username"
text: "SELECT (SELECT * FROM tweets WHERE tweets.user_id = users.user_id) FROM users WHERE users.username = $1"
values: [username]

这工作正常,但它似乎效率低下,无论是在实践方面还是代码质量方面都必须创建另一个函数来处理通过电子邮件而不是用户名获取推文:

name: "tweets by email"
text: "SELECT (SELECT * FROM tweets WHERE tweets.user_id = users.user_id) FROM users WHERE users.email = $1"
values: [email]

是否可以将字段作为参数包含到准备好的语句中?

name: "tweets by user"
text: "SELECT (SELECT * FROM tweets WHERE tweets.user_id = users.user_id) FROM users WHERE users.$1 = $2"
values: [field, value]

虽然在通过 user_id 访问 tweets 的极端情况下这确实可能有点低效,但这是我愿意提高代码质量的交易,并希望通过减少查询模板的数量来整体提高效率到 1 而不是 3+。

最佳答案

@Clodoaldo 的回答是正确的,因为它允许您想要的功能并且应该返回正确的结果。不幸的是,它的执行速度相当慢。

我建立了一个包含推文和用户的实验数据库。填充 10K 个用户,每个用户有 100 条推文(100 万条推文记录)。我索引了 PKs u.id、t.id、FK t.user_id 和谓词字段 u.username、u.email。

create table t(id serial PRIMARY KEY, data integer, user_id bignit);
create index t1 t(user_id);
create table u(id serial PRIMARY KEY, name text, email text);
create index u1 on u(name);
create index u2 on u(email);
insert into u(name,email) select i::text, i::text from generate_series(1,10000) i;
insert into t(data,user_id) select i, (i/100)::bigint from generate_series(1,1000000) i;
analyze table t;
analyze table u;

使用一个字段作为谓词的简单查询非常快:

prepare qn as select t.* from t join u on t.user_id = u.id where u.name = $1;

explain analyze execute qn('1111');
 Nested Loop  (cost=0.00..19.81 rows=1 width=16) (actual time=0.030..0.057 rows=100 loops=1)
   ->  Index Scan using u1 on u  (cost=0.00..8.46 rows=1 width=4) (actual time=0.020..0.020 rows=1 loops=1)
         Index Cond: (name = $1)
   ->  Index Scan using t1 on t  (cost=0.00..10.10 rows=100 width=16) (actual time=0.007..0.023 rows=100 loops=1)
         Index Cond: (t.user_id = u.id)
 Total runtime: 0.093 ms

@Clodoaldo 提出的在 where 中使用 case 的查询需要将近 30 秒:

prepare qen as select t.* from t join u on t.user_id = u.id
  where case $2 when 'e' then u.email = $1 when 'n' then u.name = $1 end;

explain analyze execute qen('1111','n');
 Merge Join  (cost=25.61..38402.69 rows=500000 width=16) (actual time=27.771..26345.439 rows=100 loops=1)
   Merge Cond: (t.user_id = u.id)
   ->  Index Scan using t1 on t  (cost=0.00..30457.35 rows=1000000 width=16) (actual time=0.023..17.741 rows=111200 loops=1)
   ->  Index Scan using u_pkey on u  (cost=0.00..42257.36 rows=500000 width=4) (actual time=0.325..26317.384 rows=1 loops=1)
         Filter: CASE $2 WHEN 'e'::text THEN (u.email = $1) WHEN 'n'::text THEN (u.name = $1) ELSE NULL::boolean END
 Total runtime: 26345.535 ms

观察该计划,我认为使用联合子选择然后过滤其结果以获得适合参数化谓词选择的 id 将允许计划者为每个谓词使用特定索引。事实证明我是对的:

prepare qen2 as 
select t.*
from t 
join (
 SELECT id from 
  (
  SELECT 'n' as fld, id from u where u.name = $1
  UNION ALL
  SELECT 'e' as fld, id from u where u.email = $1
  ) poly
 where poly.fld = $2
) uu
on t.user_id = uu.id;

explain analyze execute qen2('1111','n');
 Nested Loop  (cost=0.00..28.31 rows=100 width=16) (actual time=0.058..0.120 rows=100 loops=1)
   ->  Subquery Scan poly  (cost=0.00..16.96 rows=1 width=4) (actual time=0.041..0.073 rows=1 loops=1)
         Filter: (poly.fld = $2)
         ->  Append  (cost=0.00..16.94 rows=2 width=4) (actual time=0.038..0.070 rows=2 loops=1)
               ->  Subquery Scan "*SELECT* 1"  (cost=0.00..8.47 rows=1 width=4) (actual time=0.038..0.038 rows=1 loops=1)
                     ->  Index Scan using u1 on u  (cost=0.00..8.46 rows=1 width=4) (actual time=0.038..0.038 rows=1 loops=1)
                           Index Cond: (name = $1)
               ->  Subquery Scan "*SELECT* 2"  (cost=0.00..8.47 rows=1 width=4) (actual time=0.031..0.032 rows=1 loops=1)
                     ->  Index Scan using u2 on u  (cost=0.00..8.46 rows=1 width=4) (actual time=0.030..0.031 rows=1 loops=1)
                           Index Cond: (email = $1)
   ->  Index Scan using t1 on t  (cost=0.00..10.10 rows=100 width=16) (actual time=0.015..0.028 rows=100 loops=1)
         Index Cond: (t.user_id = poly.id)
 Total runtime: 0.170 ms

关于postgresql - Postgres 准备了具有不同字段的语句,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9754372/

相关文章:

mysql - 使用条件按两列排序?

sql - 如何使用 Prisma JS 检查文本列是否为空

java - JDBCPreparedStatement 中 "null primitives"的解决方法?

field - 如何在 SAGE 中获取大字段的 int 表示

java - 这个字符串构造函数的实现如何工作(java)?

Django Query 每天将两行中的值聚合为单个结果

sql - postgresql - 使用命令导出选择查询结果

php - 存在撇号时使用 json_encode 破坏 JSON 对象

python - sqlite/python - 不带引号的命名参数?

java - 创建或更新时的时间戳 JSON 字段