【问题标题】:How to find least non-null column in one particular row in SQL?如何在 SQL 的某一特定行中找到最少的非空列?
【发布时间】:2020-08-04 05:41:22
【问题描述】:

我试图在同一个表中的一行的两列中找到最小的数字,但需要注意的是,其中一列在特定行中可能为空。如果其中一列为空,我希望为该行返回另一列中的值,因为在这种情况下这是最低的非空列。如果我在 MySQL 5.1 中使用 least() 函数:

select least(1,null)

这返回 null,这不是我想要的。在这种情况下,我需要查询返回 1。

我已经能够通过这个查询得到我想要的一般结果:

select least(coalesce(col1, col2)) , coalesce(col2,col1))

只要 col1 和 col2 都不为 null,每个 coalesce 语句都会返回一个数字,并且 least() 会处理查找最小值。

有没有更简单/更快的方法来做到这一点?我在这种情况下使用 MySQL,但欢迎使用通用解决方案。

【问题讨论】:

  • 更快?你确定它很慢吗?
  • 没有判断它一定很慢,但它调用了3个函数而不是可能的一个。我在查询的 order by 子句中使用它,我需要尽可能快。

标签: sql mysql


【解决方案1】:

不幸的是(对于您的情况) LEAST 的行为在 MySQL 5.0.13 (http://dev.mysql.com/doc/refman/5.0/en/comparison-operators.html#function_least) 中发生了变化 - 只有当所有参数都是 NULL 时它才会返回 NULL。

此更改甚至被报告为错误:http://bugs.mysql.com/bug.php?id=15610 但修复只是针对 MySQL 文档,解释了新的行为和兼容性问题。

您的解决方案是推荐的解决方法之一。另一个可以使用 IF 运算符:

SELECT IF(Col1 IS NULL OR Col2 IS NULL, COALESCE(Col1, Col2), LEAST(Col1,Col2))

【讨论】:

  • 很好的信息,但解决方法仅适用于 2 个值,如果您添加 Col3,则它不再有效。在这种情况下,这是解决方法:LEAST(COALESCE(a,b,c), COALESCE(b,c,a), COALESCE(c,a,b)),例如 10 列会变得非常混乱。让我们希望有一天会添加一个NLEAST() 功能......
【解决方案2】:

根据您将所有值都设为null 的极端情况,我会选择这样的语法,这种语法更具可读性(如果您正好有两列,下面是一个更简单的解决方案!)

SELECT LEAST( IFNULL(5, ~0 >> 1), IFNULL(10, ~0 >> 1) ) AS least_date;
-- Returns: 5

SELECT LEAST( IFNULL(null, ~0 >> 1), IFNULL(10, ~0 >> 1) ) AS least_date;
-- Returns: 10

SELECT LEAST( IFNULL(5, ~0 >> 1), IFNULL(null, ~0 >> 1) ) AS least_date;
-- Returns: 5

SELECT LEAST( IFNULL(null, ~0 >> 1), IFNULL(null, ~0 >> 1)) AS least_date
-- Returns: @MAX_VALUE (If you need to use it as default value)

SET @MAX_VALUE=~0 >> 1;
SELECT LEAST( IFNULL(null, @MAX_VALUE), IFNULL(null, @MAX_VALUE)) AS least_date;
-- Returns: @MAX_VALUE (If you need to use it as default value). Variables just makes it more readable!

SET @MAX_VALUE=~0 >> 1;
SELECT NULLIF(
    LEAST( IFNULL(null, @MAX_VALUE), IFNULL(null,@MAX_VALUE)),
    @MAX_VALUE
) AS least_date;
-- Returns: NULL

这是我的首选方式

  • 可以保证至少一列不能是NULL
  • 在极端情况下(所有列都是NULL),您需要一个大于任何可能值或可以限制在某个阈值的非空默认值
  • 您可以处理变量以使该语句更具可读性

如果您质疑自己 ~0 >> 1 的含义: 这只是说“给我可用的最大数量”的简写。另见:https://stackoverflow.com/a/2679152/2427579

更好的是,如果您只有两列,您可以使用:

SELECT LEAST( IFNULL(@column1, @column2), IFNULL(@column2, @column1) ) AS least_date;
-- Returns: NULL (if both columns are null) or the least value

【讨论】:

    【解决方案3】:

    这可能性能好一点(可能需要转换成对应的MySql语法):

    SELECT
      CASE
        WHEN Col1 IS NULL THEN Col2
        WHEN Col2 IS NULL THEN Col1
        ELSE Least(Col1, Col2)
      END
    

    另一种选择(虽然可能更慢,但值得一试):

    SELECT Col1
    WHERE Col2 IS NULL
    UNION
    SELECT Col2
    WHERE Col1 IS NULL
    UNION
    SELECT least(Col1, Col2)
    WHERE Col1 IS NOT NULL AND Col2 IS NOT NULL
    

    【讨论】:

      【解决方案4】:

      为什么不将一列的值设置为 NULL 时与另一列相等?

      SELECT LEAST(IFNULL(COL1, COL2), IFNULL(COL2, COL1));
      

      使用上面的代码,null 值将被忽略,除非两者都是 null。

      例如

      COL1 = NULL,COL2 = 5

      LEAST(IFNULL(NULL, 5), IFNULL(5, NULL)) -> LEAST(5, 5) -> 5
      

      COL1 = 3,COL2 = NULL

      LEAST(IFNULL(3, NULL), IFNULL(NULL, 3)) -> LEAST(3, 3) -> 3
      

      COL1 = NULL,COL2 = NULL

      LEAST(IFNULL(NULL, NULL), IFNULL(NULL, NULL)) -> LEAST(NULL, NULL) -> NULL
      

      【讨论】:

        【解决方案5】:

        我就是这样解决的:

        select coalesce(least(col1, col2), col1, col2)
        

        如果一个值为 NULL,则查询将返回第一个非 NULL 值。如果两个值都可以为 NULL,您甚至可以添加一个默认值作为最后一个参数。

        【讨论】:

          【解决方案6】:

          选择 最小(最小(COALESCE(COL1,COL2),COALESCE(COL2,CO1))) COL1 不为空的地方 并且 COL2 不为空;

          【讨论】:

            【解决方案7】:

            我创建了一个处理任意数量日期的函数,通过将它们与分隔符 (CONCAT_WS) 连接起来作为函数的第一个参数。

            CONCAT_WS 除了动态参数数量外,将删除所有 NULL 日期;)

            函数接受两个参数:

            • 分隔符分隔的日期字符串为 TEXT
            • 作为 TEXT 的分隔符(与在 CONCAT_WS 上使用的相同!!) - 如果您在 CONCAT_WS 上仅使用首选分隔符,则可以将其删除。
            CREATE FUNCTION `min_date`(`dates` TEXT, `delim` VARCHAR(10)) RETURNS DATE NO SQL DETERMINISTIC
            BEGIN
            
            DECLARE `result` DATE DEFAULT NULL;
            
            DECLARE `count` TINYINT DEFAULT 0;
            DECLARE `temp` DATE DEFAULT NULL;
            
            IF `delim` IS NULL THEN SET `delim` = ','; END IF;
            
            IF `dates` IS NOT NULL AND CHAR_LENGTH(`dates`) > 0 THEN 
              SET `count` = LENGTH(`dates`) - LENGTH(REPLACE(`dates`, `delim`, SPACE(CHAR_LENGTH(`delim`) - 1)));
            
              WHILE `count` >= 0 DO
                SET `temp` = SUBSTRING_INDEX(SUBSTRING_INDEX(`dates`, `delim`, `count` + 1), `delim`, -1);
            
                IF `result` IS NULL OR `result` > `temp` THEN SET `result` = `temp`; END IF;
            
                SET `count` = `count` - 1;
              END WHILE;
            END IF;
            
            RETURN `result`;
            
            END
            

            然后,您可以使用日期字段的任意组合或作为静态字符串(只要是有效日期或 NULL):

            SELECT min_date(CONCAT_WS(',', `date_column_1`, NULL, '2019-03-04', `date_column_2`), ',') AS `min_date`
            

            【讨论】:

              【解决方案8】:

              一个简单(但不美观)的解决方案如下。

              如果您正在寻找最小的非空值,您可以使用 IFNULL,第二个参数是“INT 限制”

              ORDER BY LEAST(
                  IFNULL(properties.sale_value, 2147483647),
                  IFNULL(properties.rental_value, 2147483647),
                  IFNULL(properties.daily_rental_value, 2147483647)
              ) ASC
              

              如果你正在寻找最大的非空值,你可以使用 IFNULL,第二个参数是 1,(或者低于你限制的第一个负值,如果你不知道,使用负 int限制)

              ORDER BY GREATEST(
                  IFNULL(properties.sale_value, 1),
                  IFNULL(properties.rental_value, 1),
                  IFNULL(properties.daily_rental_value, 1)
              ) ASC
              

              【讨论】:

                猜你喜欢
                • 2010-11-22
                • 2015-01-26
                • 1970-01-01
                • 2022-10-12
                • 2010-09-09
                • 1970-01-01
                • 2023-01-31
                • 2021-01-10
                相关资源
                最近更新 更多