SQL不等于和空值

9

我们想要编写这个查询:

select * from table 
where col1 != 'blah' and col2 = 'something'

我们希望查询包括那些col1为空(且col2='something')的行。目前,查询无法返回col1为空的行。以下查询是最好且最快的方法吗?
select * from table 
where (col1 != 'blah' or col1 is null) and col2 = 'something'

或者,如果需要,我们可以将所有col1的null值更新为空字符串。这样做是否更好?然后我们的第一个查询就能正常工作了。


更新:关于使用NVL函数:我在另一个帖子上读到,从性能角度考虑,这并不是一个好选择。


1
NVL:这完全取决于您查询的数据量。如果性能成为问题,您仍然可以选择在函数上创建索引。 - Josh Bush
大多数的10万行都有col2和col1的值(其中约20%的col1行为null)。我们向用户展示所有这些信息,并且他们可以筛选结果集。 - Marcus Leon
我们在查询1000万条以上的记录时使用nvl,并且没有性能问题。就像Thilo上面所说的,您可以在nvl被评估之前使用其他索引来缩小范围。 - Josh Bush
就此而言,符合标准的 SQL 表达式应为 WHERE NULLIF(col1, 'blah') IS NOT NULL ...。这很可能会产生与 NVL 方法相同的性能影响。更好的方法是使用 IS DISTINCT FROM,但正如 @BillKarwin 所指出的那样,Oracle 不支持该方法。 - pilcrow
使用 NVL(col1,'X')!='blah'(col1!='blah' OR col1 IS NULL) 进行查询将没有显着的性能差异。 - Jeffrey Kemp
显示剩余2条评论
9个回答

16

在Oracle中,空字符串和NULL没有区别。

这明显违反了SQL标准,但是就是这样……

除此之外,你不能使用“普通”的运算符来比较NULL(或非NULL):“col1 = null”不起作用,“col1 =''”也不起作用,“col1!= null”也不起作用,你必须使用“is null”。

因此,不,你不能通过其他方式使其工作,只能使用“col1 is null”或类似于此的变体(例如使用nvl)。


1
对@Thilo的回答点赞。不幸的是,Oracle不支持SQL-99谓词“IS DISTINCT FROM”,这也是一种解决方案。 - Bill Karwin

3

虽然不是最易读的 - Oracle有一个LNNVL函数,它本质上是not()函数,但是针对null值反转了行为。这意味着在lnnvl内部与null进行比较的任何内容都将返回true(我不知道这可能会产生什么性能影响)。

要在单个语句中实现您想要的功能:

select * from table where lnnvl(col1 = 'blah') and col2 = 'something'

请注意,这仅适用于将可空值与您可以确保为非空值的值进行比较。否则,您需要像Thilo建议的那样使用类似于操作符。
lnnvl(nvl(col1, -1) = nvl(col2, -1))

我无言以对。有时我觉得 Oracle 在恶搞大家。不过这是一个很好的函数,谢谢。只是感到很遗憾自己需要用到它。 - Alan Hensley

3
我认为你发表的解决方案是最佳选项之一。
关于性能,我认为在这种情况下并没有太大的差异。如果子句已经有一个!=比较,通常优化器不会在该列中使用索引,因为选择性不够,所以更具有区分性的过滤器将是“and”条件的另一侧。
如果你问我,我不会将空字符串用作null,但可能只是个人喜好。

2

这取决于您的数据,但大多数优化器会先查看 col2,因为 = 比 != 更容易索引。

否则,有各种方法可以加速此查询。 最好使用 (col1 != 'blah' or col1 is null),但某些数据库允许对函数进行索引。 因此,您可以对 coalesce(col1, 0) 进行索引并获得良好的性能。

实际上,这取决于您的数据和表格。


0
如果您想加快此类查询的速度,并且使用的是 Oracle 10g 或更高版本,请使用基于函数的索引将这些 NULL 转换为值:
CREATE INDEX query_specific_index ON table (col2, NVL(col1,'***NULL***'));

select * from table 
where NVL(col1,'***NULL***') != 'blah' and col2 = 'something';

在这种情况下,数据库很可能会使用索引(当然,受CBO的决定影响,受行计数和统计数据准确性的影响)。查询必须使用索引中给定的确切表达式 - 在本例中是 "NVL(col1,'***NULL***')"

当然,选择一个对col1中的任何数据都不会产生冲突的'***NULL***'值!


0
在Oracle中使用nvl函数。
select * from table where nvl(col1,'notblah') <> 'blah'

0

这个选项怎么样?如果你的值从不为空,我认为它可能有效。

where not (value = column)

这将导致以下真值表用于评估 where 子句。
                    col1
              | 'bla' |  null |
              -----------------
      | 'bla' |   F   |   T   |
value -------------------------
      |  null |   T   |  *T   | 

*这是唯一一个“错误”的,但没关系,因为我们的值永远不会为空

更新

好的,我刚刚尝试了我的想法,但失败了。我将在此处保留答案,以节省其他人尝试相同事情的时间。以下是我的结果:

select 'x', 'x' from dual where not ('x' = 'x');
0 rows
select 'x', 'y' from dual where not ('x' = 'y');
1 row
select 'x', 'null' from dual where not ('x' = null);
0 rows
select 'null', 'null' from dual where not (null = null);
0 rows

更新 2

如果您的值从不为空(与上面的真值表匹配),则此解决方案有效。

where ('blah' != col1 or col1 is null)

这里是测试:

select 'x', 'x' from dual where ('x' != 'x' or 'x' is null);
0 rows
select 'x', 'y' from dual where ('x' != 'y' or 'y' is null);
1 row
select 'x', 'null' from dual where ('x' != null or null is null);
1 row
select 'null', 'null' from dual where (null != null or null is null);
1 row

你为什么要寻找只在值为空时才起作用的解决方案呢?问题的整个重点在于 OP 需要 包括空值;如果您不想包括它们,那么原始查询就可以工作。 or'blah'为null 定义上是错误的,因此根本没有为查询添加任何内容。我不明白你从哪里来,或者为什么你真的要重新访问这个问题。 - Alex Poole
我本意是说 或者 col1 为空。我已经修复了它。我试图通过删除 为空 检查来简化查询来解决问题,但没有成功,所以我将我的测试留在这里供将来参考,以防其他人有相同的想法。 - Alex
你最终回到了原帖的起点,所以仍然不确定这是否对问题有所帮助。 - Alex Poole

-1

针对Oracle

select * from table where nvl(col1, 'value') != 'blah' and col2 = 'something'

针对 SqlServer

select * from table where IsNull(col1, '') <> 'blah' and col2 = 'something'

1
你不能在Oracle中使用''作为替换值,它仍然会将其视为空值。 - Josh Bush

-3

我认为将NULL值更改为""字符串,您的增量将是最小的。但是如果'blah'不为null,则应包括NULL值。

编辑:我想我很惊讶为什么在这里被投票否决。如果'blah'不为null或空字符串,则永远不会有影响,因为您已经检查了COL1是否不等于'blah',这不是一个NULL或空字符串。


1
在Oracle中,将null与任何东西进行比较都会返回null,例如,“SELECT 1 FROM dual WHERE NULL <> '1'”不会返回行。 - Josh Bush
Oracle 不认识空字符串 - 它将其视为 null 值。 - Gary Myers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接