【问题标题】:Is it possible to add conditions to a MAX() call in an aggregated query?是否可以在聚合查询中向 MAX() 调用添加条件?
【发布时间】:2012-01-22 05:11:10
【问题描述】:

背景

我的典型用例:

# Table

id     category    dataUID
---------------------------
0         A        (NULL)
1         B        (NULL)
2         C        text1
3         C        text1
4         D        text2
5         D        text3

# Query

SELECT MAX(`id`) AS `id` FROM `table`
GROUP BY `category`

这很好;它将删除正在处理的记录集中的所有“重复类别”,为我提供每个类别的“最高”ID。

然后我可以继续使用此 ID 再次提取所有数据:

# Query

SELECT * FROM `table` JOIN (
   SELECT MAX(`id`) AS `id` FROM `table`
   GROUP BY `category`
) _ USING(`id`)

# Result

id     category    dataUID
---------------------------
0         A        (NULL)
1         B        (NULL)
3         C        text1
5         D        text3

请注意,这与:

SELECT MAX(`id`) AS `id`, `category`, `dataUID` FROM `table`
GROUP BY `category`

the documentation:

在标准 SQL 中,包含 GROUP BY 子句的查询不能引用 到选择列表中未在 GROUP BY 子句。例如,此查询在标准 SQL 中是非法的 因为选择列表中的名称列没有出现在 分组依据:

SELECT o.custid, c.name, MAX(o.payment)   FROM orders AS o, customers
AS c   WHERE o.custid = c.custid   GROUP BY o.custid;

为了使查询合法,必须从 在 GROUP BY 子句中选择列表或命名。

MySQL 扩展了 GROUP BY 的使用,以便选择列表可以引用 未在 GROUP BY 子句中命名的非聚合列。 这意味着 前面的查询在 MySQL 中是合法的。您可以使用此功能 通过避免不必要的列排序来获得更好的性能和 分组。 但是,这主要在每个 未在 GROUP BY 中命名的非聚合列对于每个都相同 组。

[..]

此扩展假定未分组的列将具有相同的分组值。否则,结果是不确定的。

所以我会得到dataUID 的未指定值——例如,text2 text3 的结果是id 5

在我的真实案例中,这实际上是其他领域的问题;碰巧的是,特别是对于 dataUID 列,通常我并不真正关心我得到的 哪个 值。


问题

但是!

如果给定category 的任何行具有NULL dataUID,并且至少另一行具有非NULL dataUID,我希望MAX 忽略NULL 一个。

所以:

id     category    dataUID
---------------------------
4         D        text2
5         D        (NULL)

目前,由于我选择了ID最大的行,我得到:

5         D        (NULL)

但是,因为dataUIDNULL,所以我想要:

4         D        text2

我怎样才能得到这个?如何在聚合MAX 的使用中添加条件逻辑?


我想可能会交给MAX一个元组,然后将id从中拉出来:

GET_SECOND_PART_SOMEHOW(MAX((IF(`dataUID` NOT NULL, 1, 0), `id`))) AS `id`

但我认为MAX 不会接受这样的任意表达式,更不用说元组了,而且我不知道如何事后检索元组的第二部分。

【问题讨论】:

  • 如果一个类别的所有行都有dataUID NULL,会发生什么?
  • @ypercube:回退到按 ID 排序。在进行排序时,我只想优先考虑确实存在于组中的任何dataUID-present 行。
  • @ajreal:Ta。我找不到明显不合适的max 标签的替代品;)
  • @ajreal: 是的,我想that's true :)

标签: mysql group-by greatest-n-per-group


【解决方案1】:

@ypercube's answer 稍作调整。要获取ids,您可以使用

SELECT COALESCE(MAX(CASE
                      WHEN dataUID IS NOT NULL THEN id
                    END), MAX(id)) AS id
FROM   table
GROUP  BY category  

然后将其插入join

【讨论】:

  • 这似乎产生了与我的解决方案相同的结果,尽管我无法终生弄清楚如何:D
  • 不错。简单,可能比我扭曲的最终版本快得多。
【解决方案2】:

这最终比我想象的要容易,因为事实证明 MySQL 接受 MAX 中的任意表达式。

我可以通过在id 中注入一个前导字符作为排序提示来获得我想要的排序:

SUBSTRING(MAX(IF (`dataUID` IS NULL, CONCAT('a',`id`), CONCAT('b',`id`))) FROM 2)

演练:

id     category    dataUID    IF (`dataUID` IS NULL, CONCAT('a',`id`), CONCAT('b',`id`)
--------------------------------------------------------------------------------------
0         A        (NULL)                             a0
1         B        (NULL)                             a1
2         C        text1                              b2
3         C        text1                              b3
4         D        text2                              b4
5         D        (NULL)                             a5

所以:

SELECT
   `category`, MAX(IF (`dataUID` IS NULL, CONCAT('a',`id`), CONCAT('b',`id`)) AS `max_id_with_hint`
FROM `table`
GROUP BY `category`

category   max_id_with_hint
------------------------------
A          a0
B          a1
C          b3
D          b4

然后,再次删除订购提示是一件简单的事情。

特别感谢@JlStone 通过COALESCE 设置我在对MAX 的调用中嵌入表达式并直接操作提供给MAX 的值的路径。

【讨论】:

    【解决方案3】:

    据我所知,您可以在分组语句中使用COALESCE。例如。

    SELECT MAX(COALESCE(`id`,1)) ...
    

    hm 看来我第一次读得很快。我想也许你想要这样的东西?

    SELECT * FROM `table` JOIN (
       SELECT MAX(`id`) AS `id` FROM `table`
       WHERE `dataUID` IS NOT NULL
       GROUP BY `category`
    ) _ USING(`id`)
    

    或许

    SELECT MAX(`id`) AS `id`, 
      COALESCE (`dataUID`, 0) as `dataUID`
    FROM `table`
    GROUP BY `category`
    

    【讨论】:

    • 好的,所以我会写COALESCE(`dataUID`, `id`),但随后我会得到一些行的dataUID 和其他行的id.. 应用MAX 会产生令人惊讶的结果结果。
    • MAX(COALESCE(IF (`dataUID` IS NOT NULL, CONCAT('b',`id`), NULL), CONCAT('a',`id`))) 可能会做我想做的事。我总是在结果中得到id,但我也在非dataUID之前得到“dataUID”的订购信息;我可以稍后去掉那个排序字符...嗯..
    • @Tomalak 你的意思是MAX(IF (`dataUID` IS NULL, -1, `id`)) AS `id`
    • @JlStone:朝那个方向发展。我想我现在明白了。感谢您的提示!
    【解决方案4】:
    select * 
    from t1
    join (
      select max(id) as id,
        max(if(dataGUID is NULL, NULL, id)) as fallbackid,
        category
      from t1 group by category) as ids
    on if(ids.id = fallbackid or fallbackid is null, id, fallbackid) = t1.id; 
    

    【讨论】:

    • 我相信应该也能完成这项工作。谢谢!
    • ids.id = fallbackid or 是多余的吗?
    • @Tomalak,不是的,dataGUID只有max id为null时才需要。
    • 但如果ids.id = fallbackid 肯定可以使用两个操作数中的任何一个,因为根据条件发现它们是等价的?
    • @Tomalak,fallbackid 为 null 的条件只有当所有 dataguid 都为 null 时才为真,应该有一个附加条件
    【解决方案5】:
    SELECT t.*
    FROM table AS t
      JOIN
      ( SELECT DISTINCT category
        FROM table
      ) AS tdc
      ON t.id = 
      COALESCE(
        ( SELECT MAX(id) AS id 
          FROM table 
          WHERE category = tdc.category
            AND dataUID IS NOT NULL
        ) 
      , ( SELECT MAX(id) AS id 
          FROM table 
          WHERE category = tdc.category
            AND dataUID IS NULL
        ) 
      )
    

    【讨论】:

    • 不,因为这样我就不会得到任何类别 AB 的结果!来自 q:“如果给定类别的任何行具有 NULL dataUID,并且至少另一行具有非 NULL dataUID,我希望 MAX 忽略 NULL 那些。”如果没有任何行具有非 NULL dataUID,我想回退到 ID 排序。
    • 是的,我想就可以了。 Martin 需要对我现有的查询进行更本地化的更改,我发现它稍微更容易理解,所以这就是我的目标。不过还是谢谢你!
    【解决方案6】:

    你需要 OVER 子句

    SELECT id, category,dataUID 
    FROM
     (
        SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY id desc, dataUID desc ) rn, 
        id, category,dataUID FROM table 
     ) q
    WHERE rn=1
    

    考虑按 desc 排序最终会移动空值。

    【讨论】:

    • OVER 来自 Oracle,不是吗?我不知道 MySQL 是否支持它。
    • 哎呀我的错,我没有注意到 MySQL。实际上是 MS SQL。
    猜你喜欢
    • 2013-12-29
    • 1970-01-01
    • 2020-11-26
    • 2019-06-07
    • 2020-02-15
    • 2012-07-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多