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

cod*_*ama 12 sql postgresql

假设我有一组项目:

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

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

SELECT * 
FROM TABLE 
WHERE ITEM NOT IN ('item1', 'item2', 'item3', 'item4','item5')
Run Code Online (Sandbox Code Playgroud)

或者,它可以写成:

SELECT * 
FROM TABLE 
WHERE ITEM != 'item1' 
  AND ITEM != 'item2' 
  AND ITEM != 'item3' 
  AND ITEM != 'item4' 
  AND ITEM != 'item5'
Run Code Online (Sandbox Code Playgroud)
  1. 哪个更有效,为什么?
  2. 一个人变得比另一个人更有效率?换句话说,如果有500件商品怎么办?

我的问题与PostgreSQL有关.

Cra*_*ger 43

在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);
Run Code Online (Sandbox Code Playgroud)

要么:

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;
Run Code Online (Sandbox Code Playgroud)

(在现代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;
Run Code Online (Sandbox Code Playgroud)

哪里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;
  • AND生成的列表SELECT string_agg(item::text, ' AND item <> ') from exclude;)
  • 在重复运行中,子查询和基于联接的表排除大致相同.
  • 对计划的检查表明,Pg转化NOT IN<> ALL

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

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

也可以看看:

  • @BurhanKhalid使用链式`AND ... <> ...`也会让解析器和规划器变得艰难.最近有一些关于查询计划的邮件列表报告,对于包含数万个这样的条款的查询需要*分钟*,这些条款是由一些可怕的ORM生成的. (6认同)
  • @BurhanKhalid查询规划器必须将巨大的"AND"不等式列表合并到一个"NOT IN"列表中,以获得一个微弱的理智结果,它本身需要时间,并且对绝大多数非疯狂的查询进行规划较慢.这是"不要这样做"的事情之一......如果你用*50,000简单的"OR"条款*生成一个查询,就像我最近看到的一样,你做错了*.计划时间的几分钟是可怕的,但是处理怪异的角落案件的常见情况也是如此. (3认同)

gbn*_*gbn 9

我对@Jayram最初接受的答案有点不同意.

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

通常,对于子查询SQL构造

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

简单来说

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

This was discussed on dba.se: "The use of NOT logic in relation to indexes". For PostgreSQL, then this explainextended article explains the internal more (but not for a list of constants with NOT IN unfortunately).

Either way, for a list of constants, I'd use NOT IN before <> generally because it's easier to read and because of what @CraigRinger explained.

For a subquery, NOT EXISTS is the way to go