如何获得 maximal/minimal 多个字段的显着值,其中某些字段可以是 'null'

How to get maximal/minimal significant value of multiple fields where some fields can be 'null'

我有一个包含多个相同类型列的查询。它们源自一个细节 table,它与它的主人 table 多次 left join-ed。

每个结果记录,我需要这些列的有效值的最大值和最小值。也就是说,不应考虑 null 个值。

Firebird 中有 MAX() and MIN() 个聚合函数。不幸的是,他们只接受一个字段作为参数。

此外,还有 maxvalue() and minvalue() 个接受多个参数的函数。不幸的是,当至少一个值是 null 时,这些函数 return null。我只想忽略 null-values 和 return null 只有当所有值都是 null.


下面的SQL Fiddle只是一个查询的例子,其中return的多个列可以是null。由于 SQL Fiddle 不支持 Firebird,我使用 MySQL 5.6 仅用于 演示目的 。但最后,我需要一个 Firebird 的解决方案。

MySQL 5.6 架构设置:

CREATE TABLE Master
    (`ID` int)
;

INSERT INTO Master
    (`ID`)
VALUES
    (1),
    (2),
    (3)
;


CREATE TABLE Detail
    (`MREF` int,
     `MYVALUE` int)
;

INSERT INTO Detail
    (`MREF`, `MYVALUE`)
VALUES
    (1, 1),
    (2, 2),
    (3, 3)
;

查询 1:

select m.ID, d1.MYVALUE, d2.MYVALUE, d3.MYVALUE
  from Master m
  left join Detail d1
    on d1.MREF = m.ID and d1.MYVALUE = 1
  left join Detail d2
    on d2.MREF = m.ID and d2.MYVALUE = 2
  left join Detail d3
    on d3.MREF = m.ID and d3.MYVALUE = 3

Results:

| ID | MYVALUE | MYVALUE | MYVALUE |
|----|---------|---------|---------|
|  1 |       1 |  (null) |  (null) |
|  2 |  (null) |       2 |  (null) |
|  3 |  (null) |  (null) |       3 |

对于上面非常简单的示例,所需的输出将是:

| ID | MYVALUE | MYVALUE | MYVALUE | MAX | MIN |
|----|---------|---------|---------|-----|-----|
|  1 |       1 |  (null) |  (null) |   1 |   1 |
|  2 |  (null) |       2 |  (null) |   2 |   2 |
|  3 |  (null) |  (null) |       3 |   3 |   3 |

火鸟

select m.ID
       ,nullif(maxvalue(coalesce(d1.MYVALUE,-999999999),coalesce(d2.MYVALUE,-999999999),coalesce(d3.MYVALUE,-999999999)),-999999999) as max_val
       ,nullif(minvalue(coalesce(d1.MYVALUE, 999999999),coalesce(d2.MYVALUE, 999999999),coalesce(d3.MYVALUE, 999999999)), 999999999) as min_val
  from Master m
  left join Detail d1
    on d1.MREF = m.ID and d1.MYVALUE = 1
  left join Detail d2
    on d2.MREF = m.ID and d2.MYVALUE = 2
  left join Detail d3
    on d3.MREF = m.ID and d3.MYVALUE = 3

MySQL

select m.ID
       ,nullif(greatest(coalesce(d1.MYVALUE,-999999999),coalesce(d2.MYVALUE,-999999999),coalesce(d3.MYVALUE,-999999999)),-999999999) as max_val
       ,nullif(least   (coalesce(d1.MYVALUE, 999999999),coalesce(d2.MYVALUE, 999999999),coalesce(d3.MYVALUE, 999999999)), 999999999) as min_val
  from Master m
  left join Detail d1
    on d1.MREF = m.ID and d1.MYVALUE = 1
  left join Detail d2
    on d2.MREF = m.ID and d2.MYVALUE = 2
  left join Detail d3
    on d3.MREF = m.ID and d3.MYVALUE = 3

Mysql 等价于 maxvalue()minvalue()GREATEST()LEAST() 但只接受两个参数,所以有点复杂。但同样的逻辑

SQL DEMO

select m.ID, d1.MYVALUE, d2.MYVALUE, d3.MYVALUE,
       NULLIF(
               GREATEST( 
                        GREATEST( COALESCE(d1.MYVALUE,-99999),
                                  COALESCE(d2.MYVALUE,-99999)),
                        COALESCE(d3.MYVALUE,-99999)
                       ),
               -99999
             ) as max_value,
       NULLIF(
               LEAST( 
                        LEAST( COALESCE(d1.MYVALUE,99999),
                               COALESCE(d2.MYVALUE,99999)),
                        COALESCE(d3.MYVALUE,99999)
                       ),
               99999
             ) as min_value             


  from Master m
  left join Detail d1
    on d1.MREF = m.ID and d1.MYVALUE = 1
  left join Detail d2
    on d2.MREF = m.ID and d2.MYVALUE = 2
  left join Detail d3
    on d3.MREF = m.ID and d3.MYVALUE = 3

输出

编辑:只有 CASE 版本,不需要 -99999 伪代码,但没有 5 列

SQL Demo

select m.ID, d1.MYVALUE, d2.MYVALUE, d3.MYVALUE,
       CASE WHEN d1.MYVALUE IS NULL AND d2.MYVALUE IS NULL AND d3.MYVALUE IS NULL  THEN NULL
            WHEN d1.MYVALUE IS NULL THEN CASE WHEN d2.MYVALUE IS NULL THEN d3.MYVALUE
                                              WHEN d3.MYVALUE IS NULL THEN d2.MYVALUE
                                              ELSE GREATEST(d2.MYVALUE, d3.MYVALUE)
                                         END
            WHEN d2.MYVALUE IS NULL THEN CASE WHEN d1.MYVALUE IS NULL THEN d3.MYVALUE
                                              WHEN d3.MYVALUE IS NULL THEN d1.MYVALUE
                                              ELSE GREATEST(d1.MYVALUE, d3.MYVALUE)
                                         END
            WHEN d3.MYVALUE IS NULL THEN CASE WHEN d1.MYVALUE IS NULL THEN d2.MYVALUE
                                              WHEN d2.MYVALUE IS NULL THEN d1.MYVALUE
                                              ELSE GREATEST(d1.MYVALUE, d2.MYVALUE)
                                         END                                         
            ELSE GREATEST(GREATEST(d1.MYVALUE,d2.MYVALUE), d3.MYVALUE)
       END as t,


  from Master m
  left join Detail d1
    on d1.MREF = m.ID and d1.MYVALUE = 1
  left join Detail d2
    on d2.MREF = m.ID and d2.MYVALUE = 2
  left join Detail d3
    on d3.MREF = m.ID and d3.MYVALUE = 3

看了其他的答案,我的结论是必须有封装术语。由于这些会产生复杂的查询,我查看了 UDFs(用户定义的函数)。

我在 FreeAdhocUDF's date functions 中找到了 F_MAXDATE()F_MINDATE()。不幸的是,它们的行为类似于 Firebird 的内部函数 maxvalue()minvalue() 因为它们 return null,如果任何输入参数是 null.

好像没有其他的UDF符合我的需要,就自己写了。 起初,似乎是它们 return 模糊值。如果 null 被传递给他们,他们在某些情况下 returned 17-11-1898 00:00:00。也就是说,因为默认情况下 Firebird 不会将 null 传递给 UDF,而是传递 null 等效值。在 TIMESTAMP 的情况下,这是 17-11-1898 00:00:00

要启用 null-传递给 UDF,必须 extend the UDFs params declaration with NULL

因此,当我将 UDF 的声明更改为如下内容时它起作用了:

DECLARE EXTERNAL FUNCTION MAX_TIMESTAMP
    TIMESTAMP NULL,
    TIMESTAMP NULL
RETURNS TIMESTAMP
ENTRY_POINT 'MAX_TIMESTAMP' MODULE_NAME 'HKSCommonUDF';

使用我新编写的 UDF,我的示例查询如下所示(对于 TIMESTAMP 个值):

select m.ID, d1.MYVALUE, d2.MYVALUE, d3.MYVALUE,
       max_timestamp(max_timestamp(d1.MYVALUE, d2.MYVALUE), d3.MYVALUE),
       min_timestamp(min_timestamp(d1.MYVALUE, d2.MYVALUE), d3.MYVALUE)
  from Master m
  left join Detail d1
    on d1.MREF = m.ID and d1.MYVALUE = 1
  left join Detail d2
    on d2.MREF = m.ID and d2.MYVALUE = 2
  left join Detail d3
    on d3.MREF = m.ID and d3.MYVALUE = 3