MySql,Postgres,Oracle和SQLServer忽略IS NOT NULL过滤器

时间:2014-10-22 22:36:56

标签: mysql sql-server database oracle postgresql

当我在这里准备回答我们的一位同事时,我遇到了一个奇怪的情况,至少对我而言。最初的问题是:Pivot Table Omitting Rows that Have Null values

我已修改查询以使用max代替group_concat以显示"问题"在所有数据库中。

SELECT 
  id, 
  max(case when colID = 1 then value else '' end) AS fn,
  max(case when colID = 2 then value else '' end) AS ln,
  max(case when colID = 3 then value else '' end) AS jt
FROM tbl 
GROUP BY id

此查询的结果是:

ID    FN        LN            JT
1    Sampo    Kallinen     Office Manager
2    Jakko    Salovaara    Vice President
3    (null)   Foo          No First Name

用户要求过滤标识为3的行,因为字段value为空。

当看起来很明显只需要做的就是在该查询上添加WHERE value IS NOT NULL约束以实现用户期望的内容。它不会工作。

所以我开始在其他数据库上测试它以查看会发生什么(使用WHERE CLAUSE进行查询)

SELECT 
  id, 
  max(case when colID = 1 then value else '' end) AS fn,
  max(case when colID = 2 then value else '' end) AS ln,
  max(case when colID = 3 then value else '' end) AS jt
FROM tbl 
  WHERE value is not null
GROUP BY id

令我惊讶的是,结果是一样的,没有效果。

然后我尝试了同一查询的不同版本:

SELECT * FROM (
    SELECT 
      id, 
      max(case when colID = 1 then value else '' end) AS fn,
      max(case when colID = 2 then value else '' end) AS ln,
      max(case when colID = 3 then value else '' end) AS jt
    FROM tbl 
    GROUP BY id
) T
WHERE fn IS NOT NULL
  AND ln IS NOT NULL
  AND jt IS NOT NULL

我能让它在所有数据库上运行的唯一方法就是使用此查询:

SELECT 
  id, 
  max(case when colID = 1 then value else '' end) AS fn,
  max(case when colID = 2 then value else '' end) AS ln,
  max(case when colID = 3 then value else '' end) AS jt
FROM tbl 
WHERE NOT EXISTS (SELECT * FROM tbl b WHERE tbl.id=b.id AND value IS NULL)
GROUP BY id

所以我问:
这里发生了什么,除了Oracle的特定情况,所有其他数据库似乎都忽略了IS NOT NULL过滤器?

3 个答案:

答案 0 :(得分:4)

要忽略 结果 中的行(如果 行中的任何一行{{1 {}有} id Postgres 中的解决方案是在value IS NULL中使用聚合函数every()或(历史原因的同义词)bool_and() }子句:

HAVING

SQL Fiddle.

解释

您使用SELECT id , max(case when colID = 1 then value else '' end) AS fn , max(case when colID = 2 then value else '' end) AS ln , max(case when colID = 3 then value else '' end) AS jt FROM tbl GROUP BY id HAVING every(value IS NOT NULL);子句的尝试只会消除示例中WHERE一个源行(id = 3的那一行),另外还有两个相同的colID = 1。因此,在聚合后,我们仍会在结果中获得id行。

但是由于我们没有id = 3行,我们在colID = 1的结果中为NULL得到一个空字符串(注意:不是fn值!)。

Postgres中更快的解决方案是使用id = 3。详细说明:

其他RDBMS

虽然在SQL:2008标准中定义了crosstab(),但许多RDBMS不支持它,可能是因为其中一些具有布尔类型的阴影实现。 (不要删除任何名称,如“MySQL”或“Oracle”......)。你可以在任何地方(包括Postgres)替换:

EVERY

因为SELECT id , max(case when colID = 1 then value else '' end) AS fn , max(case when colID = 2 then value else '' end) AS ln , max(case when colID = 3 then value else '' end) AS jt FROM tbl GROUP BY id HAVING count(*) = count(value);不计算NULL值。在MySQL中还有bit_and()。 更多相关问题:

答案 1 :(得分:2)

它在Oracle中有效,因为Oracle在NULL和''中错误地处理NULL。是相同的。其他数据库不这样做,因为它是错误的。 NULL是未知的,而不是''这只是一个空白的空字符串。

因此,如果你的where子句说WHERE (fn IS NOT NULL or fn <> '')之类的东西你可能会更进一步。

答案 2 :(得分:0)

我认为这是一个HAVING子句可以满足你需要的情况。

SELECT id, max ... (same stuff as before)
FROM tbl
GROUP by id
HAVING  fn IS NOT NULL
    AND ln IS NOT NULL
    AND jt IS NOT NULL