假设我有一组项目:

  • 项目1
  • 项目2
  • 项目3
  • 项目4
  • 项目5

查询可以用两种方式构造.首先:

SELECT * 
FROM TABLE 
WHERE ITEM NOT IN ('item1', 'item2', 'item3', 'item4','item5')

或者,它可以写为:

SELECT * 
FROM TABLE 
WHERE ITEM != 'item1' 
  AND ITEM != 'item2' 
  AND ITEM != 'item3' 
  AND ITEM != 'item4' 
  AND ITEM != 'item5'
  1. 哪个效率更高?为什么?
  2. 在哪一点上,一个变得比另一个更有效率?换句话说,如果有500件呢?

我的问题与PostgreSQL有关.

推荐答案

在PostgreSQL中,在合理的列表长度上通常会有相当小的差异,尽管IN在概念上要干净得多.很长的AND ... <> ...个列表和很长的NOT IN个列表的表现都很糟糕,其中AND个列表的表现比NOT IN个列表差得多.

在这两种情况下,如果它们足够长,你甚至可以问这个问题,那么你应该对值列表进行反连接或子查询排除测试.

WITH excluded(item) AS (
    VALUES('item1'), ('item2'), ('item3'), ('item4'),('item5')
)
SELECT * 
FROM thetable t
WHERE NOT EXISTS(SELECT 1 FROM excluded e WHERE t.item = e.item);

或者:

WITH excluded(item) AS (
    VALUES('item1'), ('item2'), ('item3'), ('item4'),('item5')
)
SELECT * 
FROM thetable t
LEFT OUTER JOIN excluded e ON (t.item = e.item)
WHERE e.item IS NULL;

(在现代Pg版本中,两者都将生成相同的查询计划).

如果值列表足够长(数万个项),那么查询解析可能会开始产生巨大的成本.在这一点上,您应该考虑创建一个TEMPORARY表,将COPY个数据排除在其中,可能在其上创建索引,然后在TEMP表上使用上面的方法之一代替CTE.

演示:

CREATE UNLOGGED TABLE exclude_test(id integer primary key);
INSERT INTO exclude_test(id) SELECT generate_series(1,50000);
CREATE TABLE exclude AS SELECT x AS item FROM generate_series(1,40000,4) x;

其中exclude是要忽略的值列表.

然后,我在相同数据上比较以下方法,并以毫秒为单位比较所有结果:

  • NOT IN名单:3424.596
  • AND ...名单:80173.823
  • 基于VALUESJOIN排除:20.727
  • 基于VALUES的子查询排除:20.495
  • 基于表的JOIN,ex list上无索引:25.183
  • 基于子查询表,ex list上没有索引:23.985

... 使基于CTE的方法比AND名单快3000多倍,比NOT IN名单快130倍.

这里的代码是:https://gist.github.com/ringerc/5755247(注意这个链接).

对于这个数据集大小,在排除列表上添加索引没有任何区别.

笔记:

  • IN个列表生成SELECT 'IN (' || string_agg(item::text, ',' ORDER BY item) || ')' from exclude;
  • AND个列表(SELECT string_agg(item::text, ' AND item <> ') from exclude;个)
  • 重复运行时,子查询和基于联接的表排除基本相同.
  • 对该计划的审查表明,Pg将NOT IN转化为<> ALL

所以你可以看到,IN个和AND个列表之间确实存在huge个差距,而不是进行适当的连接.让我惊讶的是,使用VALUES列表的CTE的速度有多快...解析VALUES列表几乎不花时间,在大多数测试中执行相同或slightly faster than的表方法.

如果PostgreSQL能够自动识别一个长得离谱的IN子句或类似AND条件的链,并切换到更智能的方法,比如进行哈希连接或隐式将其转换为CTE node ,那就太好了.现在它不知道该怎么做.

另见:

Postgresql相关问答推荐

Postgs密码重置问题

使用Spring data jpa和CriteriaQuery在jsonb列中搜索操作

PostgreSQL:重新创建主键值以匹配记录计数

如何诊断字符编码问题

Postgres从spark触发post-write

在特定距离内创建点的唯一索引

在 jOOQ 中使用 $$ 引用字符串

如何在 MockDataProvider 中创建自定义 JOOQ 记录?

为什么不能超过 10 个并发连接到 Postgres RDS 数据库

Regexp_replace 行为会改变,如果它用空白字符串或 null 替换字符串数组中的字符串

PostgreSQL SELECT 结果具有不同的 id,它更喜欢来自另一个表的特定值

JPA findBy 字段忽略大小写

需要将整个 postgreSQL 数据库加载到 RAM 中

如何引用使用 ON CONFLICT 中的函数的唯一索引?

postgresql 分组和内部连接

PostGIS - 将多面体转换为单面体

PostgreSQL 中跨多个表的索引

当从 Heroku pg:pull 数据库时提示:role "root" does not exist.

Postgresql滚动删除旧行?

在 OS X 上使用 Postgres.app 时如何将 psql 放在路径上?