要选择的 SQL 查询,直到 SUM(users_count) 达到 1000

Posted

技术标签:

【中文标题】要选择的 SQL 查询,直到 SUM(users_count) 达到 1000【英文标题】:A SQL query to select until SUM(users_count) reaches 1000 【发布时间】:2011-10-29 04:06:54 【问题描述】:

我需要一个 sql 查询来从我的消息队列中选择行,直到 SUM(users_count) 最多达到 1000。但是如果只返回一行并且该行的 users_count 为大于 1000。

我需要类似的东西:(我添加了自己的关键字)

SELECT * FROM `messages_queue` UNTIL SUM(users_count) < 1000 AT LEAST 1 ROW

这是我的表结构:

messages_queue - msg_id - msg_body - users_count(消息接收者的数量) - 时间(插入时间)

【问题讨论】:

users_count 列代表什么? 如果要选择行,如何在一行中有一个值的SUM。请更具体。 您想要的顺序是什么?即在确定您的运行总和中首先包含哪些记录时优先考虑哪些记录? users_count 表示我必须将此消息发送给的用户数。这里唯一重要的是我想控制流量,如果其中一个有 2,000,000 个接收器(users_count),则并行发送消息,这将是一个很长的队列,所以最好并行发送消息。 我有一个time 列,并且必须在此列上进行排序。 (先进先出) 【参考方案1】:

我尝试将此作为评论添加到 Mike 的答案中,但是,变量的 @ 符号存在问题。

为了借鉴 Mike 的回答,实际上可以通过在 FROM 子句中初始化变量来缩短查询,例如:

SELECT users_count, @total := @total + users_count AS total
    FROM (messages_queue, (select @total := 0) t)
WHERE @total < 1000;

【讨论】:

【参考方案2】:

此解决方案将执行累加求和,当总和超过 1000 时停止:

SELECT NULL AS users_count, NULL AS total
  FROM dual
 WHERE (@total := 0)
 UNION
SELECT users_count, @total := @total + users_count AS total
  FROM messages_queue
 WHERE @total < 1000;

这意味着如果您有两个值,例如 800,总和将为 1600。第一个 SELECT 只是初始化 @total 变量。

如果你想防止总和超过 1000,除了单行的值大于 1000 的情况外,我认为这是可行的,尽管你需要对其进行一些严格的测试:

SELECT NULL AS users_count, NULL AS total, NULL AS found
  FROM dual
 WHERE (@total := 0 OR @found := 0)
 UNION
SELECT users_count, @total AS total, @found := 1 AS found
  FROM messages_queue
 WHERE (@total := @total + users_count)
   AND @total < 1000
 UNION
SELECT users_count, users_count AS total, 0 AS found
  FROM messages_queue
 WHERE IF(@found = 0, @found := 1, 0);

【讨论】:

哇,谢谢@Mike,我需要很多时间来分析你非常好的查询,我是初学者。我将对你和其他人的答案进行基准测试,看看我的桌子上哪个更快。 @Aram Alipoor:值得一提的是,这仅适用于 mysql,因为它使用 MySQL 变量。您可以在这篇精彩的文章中找到更多信息:Advanced MySQL user variable techniques。 你太棒了,迈克谢谢你的询问,它就像一个魅力。也感谢这篇文章,我很久以前就需要它。 @Mike:您的解决方案非常适合 MySQL。我有一个符合 SQL 标准的 SQL 解析器引擎,我发现在那里很难做到。您将如何用 SQL 标准编写此查询?我很想听听你的意见。【参考方案3】:

对 Aducci 的纯 SQL 解决方案表示敬意,但正如 Thomas Berger 所说,这最终可能是一个非常昂贵的查询。根据表的大小,存储过程可能是更好的方法:

CREATE PROCEDURE messages_to_send
BEGIN
  DECLARE done INT DEFAULT 0;
  DECLARE oldest_date DATETIME;
  DECLARE cur_count INT;
  DECLARE que_size INT DEFAULT 0;
  DECLARE curs CURSOR FOR SELECT users_count, time FROM messages_que ORDER BY time;
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;

  OPEN curs;

  read_loop: LOOP
    FETCH curs INTO cur_count, oldest_date;
    IF done THEN
      LEAVE read_loop;
    END IF;
    que_size = que_size + cur_count
    IF que_size >= 1000
      LEAVE read_loop;
    END IF;
  END LOOP;

  CLOSE curs

  SELECT * FROM messages_que WHERE time < oldest_date;
END

CALL messages_to_send(); --> returns a result set of messages to send with a total user_count of 1000 or less

【讨论】:

这太棒了,非常感谢@ChrisBailey 的努力。我将对您的答案和 Aducci 的答案进行基准测试,看看哪一个在我的桌子上更快。 经过一些测试,我发现每当我的表变大时,都需要您的存储过程。所以最好使用 Mike 的查询,因为我的表中最多有 200 行。【参考方案4】:

我认为你不能用一个简单的 MySQL 查询来做到这一点。

您必须在应用程序中使用存储过程或过滤器。

编辑

我不是 MySQL 大师(只能在 oracle 和 postgres 上编写存储过程),但您可以从这里开始:http://www.mysqltutorial.org/sql-cursor-in-stored-procedures.aspx。

有关语法的更多一般信息位于此处:http://dev.mysql.com/doc/refman/5.0/en/create-procedure.html

【讨论】:

我觉得你是对的,你能不能给我一个存储过程,因为我以前从来没有写过任何存储过程。谢谢 编辑了我的答案,但你可以看看@Aducci 的答案【参考方案5】:

我认为你正在寻找这样的事情:

SELECT *
FROM
   (SELECT 
        *
      , (select sum(users_count) from `messages_queue` where time <= mq.time) RunningTotal       
   FROM `messages_queue` mq) mq2
WHERE mq2.RunningTotal < 1000

【讨论】:

如果表有很多数据,你会得到一个非常非常慢的语句:内部选择没有限制,所以它会进行全表扫描,根据表的大小必须这样做在磁盘上。但总的来说,它可以工作。 @Thomas Berger - 不确定 MySql 优化器有多聪明,但它可能会导致全表扫描 我必须说我的表最多有 200 行。因为在每次通话中我都会删除已发送的消息行。非常感谢@Aducci

以上是关于要选择的 SQL 查询,直到 SUM(users_count) 达到 1000的主要内容,如果未能解决你的问题,请参考以下文章

oracle查询选择语句——count、sum、order by、group by

选择所有非活动用户的 Sql 查询

如何在 Oracle SQL 中选择一个子字符串直到一个特定的字符?

如何在 SQL 的查询中创建一列的 SUM?

SQL查询

大量 sum() 的 SQL 性能