【问题标题】:Simple Query to Grab Max Value for each ID获取每个 ID 的最大值的简单查询
【发布时间】:2010-10-19 20:19:50
【问题描述】:

好的,我有一张这样的桌子:

ID     Signal    Station    OwnerID
111     -120      Home       1
111     -130      Car        1
111     -135      Work       2
222     -98       Home       2
222     -95       Work       1
222     -103      Work       2

这是同一天。我只需要查询返回每个 ID 的最大信号:

ID    Signal    Station    OwnerID
111   -120      Home        1
222   -95       Work        1

我尝试使用 MAX() 并且聚合弄乱了每个记录的 Station 和 OwnerID 都不同。我需要加入吗?

【问题讨论】:

  • 您使用的是哪个版本的 SQL Server?

标签: sql sql-server max greatest-n-per-group


【解决方案1】:

选择 ID, 最大信号, 所有者, 所有者 ID 从 ( select * , rank() over(partition by id order by signal desc) as max_signal from table ) 其中 max_signal = 1;

【讨论】:

    【解决方案2】:
    从状态表中选择 * WHERE 信号输入 ( 选择 A.maxSignal FROM ( 选择 ID,MAX(信号)作为 maxSignal 从状态表 按 ID 分组 ) 作为一个 );

    【讨论】:

      【解决方案3】:
      with tab(id, sig, sta, oid) as ( select 111 as id, -120 as signal, 'Home' as station, 1 as ownerId union all select 111, -130, 'Car', 1 union all select 111, -135, 'Work', 2 union all select 222, -98, 'Home', 2 union all select 222, -95, 'Work', 1 union all select 222, -103, 'Work', 2 ) , tabG(id, maxS) as ( select id, max(sig) as sig from tab group by id ) select g.*, p.* from tabG g cross apply ( select top(1) * from tab t where t.id=g.id order by t.sig desc ) p

      【讨论】:

        【解决方案4】:

        我们可以使用self join

        SELECT  T1.ID,T1.Signal,T2.Station,T2.OwnerID
        FROM (select ID,max(Signal) as Signal from mytable group by ID) T1
        LEFT JOIN mytable T2
        ON T1.ID=T2.ID and T1.Signal=T2.Signal;
        

        或者你也可以使用下面的查询

        SELECT t0.ID,t0.Signal,t0.Station,t0.OwnerID 
        FROM mytable t0 
        LEFT JOIN mytable t1 ON t0.ID=t1.ID AND t1.Signal>t0.Signal 
        WHERE t1.ID IS NULL;
        

        【讨论】:

          【解决方案5】:

          您正在执行分组最大/最小操作。这是一个常见的陷阱:感觉应该很容易做到,但在 SQL 中却不是这样。

          有许多方法(标准 ANSI 和特定于供应商的方法)来解决这个问题,其中大多数在许多情况下都不是最佳的。当多行共享相同的最大值/最小值时,有些会为您提供多行;有些不会。有些在组数较少的桌子上效果很好;对于每组较小的行数,其他组更有效。

          Here's a discussion 一些常见的(偏向 MySQL 但普遍适用)。就个人而言,如果我知道没有多个最大值(或不关心获得它们),我通常倾向于使用 null-left-self-join 方法,我将发布其他人尚未发布的方法:

          SELECT reading.ID, reading.Signal, reading.Station, reading.OwnerID
          FROM readings AS reading
          LEFT JOIN readings AS highersignal
              ON highersignal.ID=reading.ID AND highersignal.Signal>reading.Signal
          WHERE highersignal.ID IS NULL;
          

          【讨论】:

          • 使用“reading”和“highersignal”别名使查询的理解变得轻而易举!谢谢。
          【解决方案6】:

          在经典的 SQL-92 中(不使用 Quassnoi 使用的 OLAP 操作),那么您可以使用:

          SELECT g.ID, g.MaxSignal, t.Station, t.OwnerID
            FROM (SELECT id, MAX(Signal) AS MaxSignal
                    FROM t
                    GROUP BY id) AS g
                 JOIN t ON g.id = t.id AND g.MaxSignal = t.Signal;
          

          (未经检查的语法;假设您的表是“t”。)

          FROM子句中的子查询标识每个id的最大信号值;连接将其与主表中的相应数据行相结合。

          注意:如果特定 ID 的多个条目都具有相同的信号强度并且该强度是 MAX(),那么您将获得该 ID 的多个输出行。


          针对在 Solaris 10 上运行的 IBM Informix Dynamic Server 11.50.FC3 进行测试:

          + CREATE TEMP TABLE signal_info
          (
              id      INTEGER NOT NULL,
              signal  INTEGER NOT NULL,
              station CHAR(5) NOT NULL,
              ownerid INTEGER NOT NULL
          );
          + INSERT INTO signal_info VALUES(111, -120, 'Home', 1);
          + INSERT INTO signal_info VALUES(111, -130, 'Car' , 1);
          + INSERT INTO signal_info VALUES(111, -135, 'Work', 2);
          + INSERT INTO signal_info VALUES(222, -98 , 'Home', 2);
          + INSERT INTO signal_info VALUES(222, -95 , 'Work', 1);
          + INSERT INTO signal_info VALUES(222, -103, 'Work', 2);
          + SELECT g.ID, g.MaxSignal, t.Station, t.OwnerID
            FROM (SELECT id, MAX(Signal) AS MaxSignal
                      FROM signal_info
                      GROUP BY id) AS g
                JOIN signal_info AS t  ON g.id = t.id AND g.MaxSignal = t.Signal;
          
          111     -120    Home    1
          222     -95     Work    1
          

          我为此测试命名了表 Signal_Info - 但它似乎产生了正确的答案。 这仅表明至少有一个 DBMS 支持该表示法。但是,我有点惊讶 MS SQL Server 没有 - 您使用的是哪个版本?


          没有表名的 SQL 问题提交的频率总是让我感到惊讶。

          【讨论】:

          • 我收到“FROM 子句中的语法错误”错误,它指向 JOIN
          【解决方案7】:
          select a.id, b.signal, a.station, a.owner from 
          mytable a
          join 
          (SELECT ID, MAX(Signal) as Signal FROM mytable GROUP BY ID) b
          on a.id = b.id AND a.Signal = b.Signal 
          

          【讨论】:

          • @thegreekness:你需要在表别名之间包含一个明确的 AS 吗? mytable AS a JOIN (SELECT ...) AS b?你不应该,但是......
          • 我刚刚意识到 - ON 条件也必须指定加入信号。
          • 这可能是唯一有效的解决方案。但它确实需要一个复合索引(id、signal)。
          【解决方案8】:
          WITH q AS
                   (
                   SELECT  c.*, ROW_NUMBER() OVER (PARTITION BY id ORDER BY signal DESC) rn
                   FROM    mytable
                   )
          SELECT   *
          FROM     q
          WHERE    rn = 1
          

          即使给定的ID 有重复的MAX(signal),这也会返回一行。

          (id, signal) 上建立索引将大大改进此查询。

          【讨论】:

          • 使用聚合和 jon 方法比创建列更好。优化器可以作为一个整体进行评估:这里的计算列需要首先计算,所以这很可能需要某个地方的假脱机
          • 如果你在这个列上有一个索引(你应该这样做),连接效率会降低。
          • + 不用于 SQL Server 200 以防万一
          • 我知道,但是使用索引对于 SQL Server 2005 来说效率更高。
          • 很高兴知道。我刚刚测试了 13k 行和 300k 行的表。 IO 更少,更适合小表
          【解决方案9】:

          这样的?将您的表与自身连接起来,并排除找到更高信号的行。

          select cur.id, cur.signal, cur.station, cur.ownerid
          from yourtable cur
          where not exists (
              select * 
              from yourtable high 
              where high.id = cur.id 
              and high.signal > cur.signal
          )
          

          这将为每个最高信号列出一行,因此每个 id 可能有多行。

          【讨论】:

          • 是的,如果多个站的信号相同,这确实会返回重复项。
          • 已编辑,因此每个信号您可以获得多行,但没有重复。如果您只想从具有最高信号的那些中随机排列一行,请使用 Quassnoi 的答案。
          • 是的,我认为这是可行的。我需要检查数据。不过非常感谢。
          • 通俗易懂,即使在 10 年后也能很好地工作,非常感谢!
          • 那么复杂性呢?这不是 O(N^2) 吗?
          猜你喜欢
          • 2021-06-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-06-11
          • 1970-01-01
          • 2021-01-10
          • 2018-11-26
          • 2015-10-07
          相关资源
          最近更新 更多