10

我在 Rails3 应用程序中遇到了意外的 postgres 查询问题。

我以为我会通过 stackoverflow 运行它,看看互联网的大脑要说什么:)

这个结果是预期的行为(为什么?!)还是这是一个错误?

鉴于我的 Postgres 9.1.4 数据库中有一个表 Orders:

id        state
=====     ======
1                     <-- nil (default value)
2         'success'
3         'failure'

当我运行查询时:

Order.where('orders.state != ?', 'success').map { |order| order.id }
Order Load (3.8ms)  SELECT "orders".* FROM "orders" WHERE (orders.state != 'success')

=> [3]

我期待结果 [1, 3]。显然有 2 行满足 (!= 'success')。

为什么 nil != 'success' 在这里不正确?!= 是否只是忽略 NULL 值?应该是?

注意:我使用以下查询产生了所需的结果:

Order.where('orders.state IS NULL OR orders.state != ?', 'success').map { |order| order.id }
Order Load (2.3ms) SELECT "orders".* FROM "orders" WHERE (orders.state IS NULL OR orders.state != 'success')

=> [1, 3]

任何意见将不胜感激。

4

2 回答 2

12

PostgreSQL 中有一个IS DISTINCT FROM比较运算符:

当任一输入为空时,普通比较运算符产生空值(表示“未知”),而不是真或假。例如,7 = NULL产生 null 和7 <> NULL. 当此行为不合适时,请使用以下IS [ NOT ] DISTINCT FROM构造:

expression IS DISTINCT FROM expression
expression IS NOT DISTINCT FROM expression

对于非空输入,IS DISTINCT FROM与运算符相同<>。但是,如果两个输入都为 null,则返回 false,如果只有一个输入为 null,则返回 true。类似地,对于非空输入,它IS NOT DISTINCT FROM与 相同,但是当两个输入都为空时它返回真,当只有一个输入为空时它返回假。=因此,这些构造实际上就像 null 是一个正常的数据值一样,而不是“未知”。

例如,给定您的示例数据:

=> select * from orders where state is distinct from 'success';
 id |  state  
----+---------
  1 | 
  3 | failure
(2 rows)

所以你可以这样说:

Order.where('orders.state is distinct from ?', 'success').pluck(:id)

请注意,我也切换到pluck而不是您的map(&:id),它将将此 SQL 发送到数据库:

select id from orders where orders.state is distinct from 'success'

而不是select orders.* ...使用客户端过滤器来提取ids.

于 2013-05-10T17:10:45.570 回答
1

这是 SQL 标准的一部分,其中涉及至少一个 NULL 的两个值之间的比较既不返回真也不返回假,而是未知。

来自http://www-cs-students.stanford.edu/~wlam/compsci/sqlnulls

  • 涉及 NULL 的两个值之间的布尔比较既不返回 true 也不返回 false,但在 SQL 的三值逻辑中是未知的。[3] 例如,NULL 不等于 NULL 或 NULL 不等于 NULL 都是真的。测试一个值是否为 NULL 需要一个表达式,例如 IS NULL 或 IS NOT NULL。
  • SQL 查询仅选择 WHERE 表达式计算结果为 true 的值,并选择 HAVING 子句计算结果为 true 的组。

一种解决方案是使用 COALESCE:

Order.where('COALESCE(orders.state,"NIL") != ?', 'success').map(&:id)

如果orders.state为 NULL,它将用 'NIL' 替换它,然后从比较中返回 true 或 false。

您当然可以使用附加条件:

Order.where('orders.state is null OR orders.state != ?', 'success').map(&:id)
于 2013-05-10T16:41:48.547 回答