22

假设我有一组项目:

  • 项目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 有关。

4

2 回答 2

62

在 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将要排除的数据放入其中,可能在其上创建一个索引,然后在临时表上使用上述方法之一而不是 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
  • VALUES基于JOIN排除:20.727
  • VALUES基于子查询排除:20.495
  • 基于表JOIN,前列表中没有索引:25.183
  • 基于子查询表,前列表上没有索引:23.985

... 使基于 CTE 的方法比列表快三千多倍,比AND列表快 130 倍NOT IN

代码在这里:https ://gist.github.com/ringerc/5755247 (请保护你的眼睛,你们谁关注这个链接)。

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

笔记:

  • IN生成的列表SELECT 'IN (' || string_agg(item::text, ',' ORDER BY item) || ')' from exclude;
  • ANDSELECT string_agg(item::text, ' AND item <> ') from exclude;用)生成的列表
  • 在重复运行中,基于子查询和连接的表排除几乎相同。
  • 对计划的检查表明 Pg 转化NOT IN<> ALL

所以......你可以看到两者和列表之间确实存在巨大的差距,而不是进行正确的连接。令我惊讶的是,使用列表的 CTE 执行速度有多快……解析列表几乎不需要任何时间,在大多数测试中执行的速度与表格方法相同或略快。INANDVALUESVALUES

如果 PostgreSQL 能够自动识别一个荒谬的长IN子句或类似AND条件的链并切换到更智能的方法,例如进行散列连接或隐式将其转换为 CTE 节点,那就太好了。现在它不知道该怎么做。

也可以看看:

于 2013-06-11T06:51:47.723 回答
12

我不同意@Jayram 最初接受的答案。

尤其重要的是,该链接适用于 SQL Server,并且与许多其他文章和答案相矛盾。此外,示例表上没有索引。

通常,对于子查询 SQL 结构

  • <>(or !=) 是标量比较
  • NOT IN是一个左反半连接关系算子

简单来说

  • NOT IN成为一种可以使用索引的 JOIN 形式(PostgreSQL 除外!)
  • !=通常是非 SARGable 并且可能不使用索引

这在 dba.se 上进行了讨论:“与索引相关的 NOT 逻辑的使用”。对于 PostgreSQL,那么这篇explainextended 文章解释了内部更多(但不幸的是,不适用于带有 NOT IN 的常量列表)。

无论哪种方式,对于常量列表,我通常都会使用它NOT IN<>因为它更容易阅读并且因为@CraigRinger 解释了什么。

对于子查询,NOT EXISTS是要走的路

于 2013-06-11T07:25:09.550 回答