SQL:当谈到NOT IN而不是EQUAL TO时,哪个更有效,为什么?

时间:2013-06-11 06:06:17

标签: sql postgresql

假设我有一组项目:

  • 项目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件商品怎么办?
  3. 我的问题与PostgreSQL有关。

2 个答案:

答案 0 :(得分:39)

在PostgreSQL中,合理的列表长度通常存在相当小的差异,但IN在概念上更清晰。很长的AND ... <> ...列表和很长的NOT IN列表都表现得非常糟糕,ANDNOT 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列表快三千倍,比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

所以......你可以看到INAND列表之间存在真正的巨大的差距与正确的联接。让我感到惊讶的是,使用VALUES列表进行CTE的速度有多快...解析VALUES列表几乎没有时间,执行相同或略快于大多数测试中的表格方法。

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

另见:

答案 1 :(得分:8)

我对@Jayram原来接受的答案有点不同意。

至少,该链接适用于SQL Server,并且与许多其他文章和答案相矛盾。此外,样本表上没有索引。

通常,对于子查询SQL构造

  • <>(或!=)是标量比较
  • NOT IN左反半连接关系运算符

简单来说

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

这在dba.se上进行了讨论:"The use of NOT logic in relation to indexes"。对于PostgreSQL,那么这个explainextended article解释了内部更多(但不幸的是没有关于NOT IN的常量列表)。

无论哪种方式,对于常量列表,我都会在NOT IN之前使用<>,因为它更易于阅读,因为@CraigRinger解释了这一点。

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