【问题标题】:Addition of time differences with where clause in mysqlmysql中where子句加时差
【发布时间】:2012-09-02 10:30:23
【问题描述】:

我有一个包含数十万行的数据表,这些行代表对服务器获取数据的请求。每条记录都有一个时间戳、服务器 ID 和服务器是否正确响应的二进制值 (tinyint)。查询时间不是恒定的。

我试图通过将服务器在线的查询之间的时间相加来获得服务器被视为“在线”的总时间(非常优选 mysql 查询)。 例如。

server | time | status
1 | 1/1/2012 11:00 online
1 | 1/1/2012 11:02 online
1 | 1/1/2012 11:05 offline
2 | 1/1/2012 11:10 online
1 | 1/1/2012 11:30 online
Time now: 11:40
Server 1 Online Time = 2+3+10 = 15 minutes

在 mysql 中可以做到这一点吗?我更喜欢它而不是将所有行都输入 php 并计算它或平均任何东西。

【问题讨论】:

  • 什么是列类型????????????/
  • 不确定如何被认为是“在线” 您打算使用该指标做什么?显示“正常运行时间”等状态;对我来说更有意义
  • @shail 服务器Id是int,timestamp是timestamp,在线状态是tinyint(0,1)
  • @Dagon 这些行代表以随机间隔发送到数百个远程服务器的查询,最终给出一定百分比的在线时间。但我不只是想要百分比,我也想要原始时间
  • 我希望它不是僵尸网络!

标签: php mysql sql timestamp


【解决方案1】:

这可以通过对正确排序的行集使用 UNIX 时间戳转换和变量分配来完成。 “正确排序”是指行必须按server 排序,然后按time 排序。以下是您如何使用变量来获取自上次事件以来表中每一行的在线时间(间隔)(以秒为单位)(为此答案称为server_status):

SELECT
  *,
  @currenttime := UNIX_TIMESTAMP(`time`),
  @lasttime := CASE
    WHEN server <> @lastserver OR @laststatus = 'offline'
    THEN @currenttime
    ELSE @lasttime
  END,
  @currenttime - @lasttime AS seconds_online,
  @lasttime := @currenttime,
  @lastserver := server,
  @laststatus := status
FROM
  server_satus s,
  (SELECT @lastserver := 0) x
ORDER BY
  s.server,
  s.`time`

如您所见,一个临时变量 (@currenttime) 使用等同于time 的 UNIX 时间戳进行初始化,另一个用于保存前一个时间戳,以便计算两者之间的差异。其他变量用于记住先前的服务器 ID 和先前的状态,以便在必要时将差异返回为 0(对记录服务器第一个事件的每一行以及 offline 之后的每一行都执行此操作事件)。

您现在可以将上述查询生成的结果集、SUM() 和 seconds_online 值进行分组,然后将它们除以 60 以获得分钟(如果您对秒不满意),如下所示:

SELECT
  server,
  SUM(seconds_online) DIV 60 AS minutes
FROM (
  the query above
) s

但是请注意,第一个查询并没有真正计算服务器自其各自的 last 事件以来在线花费的秒数。也就是说,当前时间很可能与任何最新事件记录中的时间不同,因此不会将其考虑在内,因为查询计算的是 自上一行以来每行的秒数

解决此问题的一种方法是为每个服务器添加一行,其中包含当前时间戳和与上一条记录中相同的状态。因此,除了server_status,您还可以将以下内容作为源表:

SELECT
  server,
  `time`,
  status
FROM server_status

UNION ALL

SELECT
  s.server,
  NOW() AS `time`,
  s.status
FROM server_status s
  INNER JOIN (
    SELECT
      server,
      MAX(`time`) AS last_time
    FROM server_status
    GROUP BY
      server
  ) t
    ON s.server = t.server AND s.`time` = t.last_time

UNION ALL 的左边部分只返回来自server_status 的所有行。右侧部分首先获取每个服务器的最后一个time,然后将结果集加入server_status 以获取相应的状态,同时将time 替换为NOW()

现在表已完成,其中包含反映当前时间的“假”事件行,您可以应用第一个查询中使用的方法。这是最终查询的样子:

SELECT
  server,
  SUM(seconds_online) DIV 60 AS minutes_online
FROM (
  SELECT
    *,
    @currenttime := UNIX_TIMESTAMP(`time`),
    @lasttime := CASE
      WHEN server <> @lastserver OR @laststatus = 'offline'
      THEN @currenttime
      ELSE @lasttime
    END,
    @currenttime - @lasttime AS seconds_online,
    @lasttime := @currenttime,
    @lastserver := server,
    @laststatus := status
  FROM
    (
      SELECT
        server,
        `time`,
        status
      FROM server_status

      UNION ALL

      SELECT
        s.server,
        NOW() AS `time`,
        s.status
      FROM server_status s
        INNER JOIN (
          SELECT
            server,
            MAX(`time`) AS last_time
          FROM server_status
          GROUP BY
            server
        ) t
          ON s.server = t.server AND s.`time` = t.last_time
    ) s,
    (SELECT @lastserver := 0) x
  ORDER BY
    s.server,
    s.`time`
) s
GROUP BY
  server
;

你也可以试试(也可以玩)at SQL Fiddle

【讨论】:

  • 哇,谢谢。这将是我在这个网站上读过的最长、最详细的答案,纯粹的质量。
【解决方案2】:

这是我创建的示例表结构:

-- SQL EXAMPLE
CREATE TABLE IF NOT EXISTS `stack_test` (
  `server` int(11) NOT NULL,
  `rtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `status` tinyint(4) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
INSERT INTO `stack_test` (`server`, `rtime`, `status`) VALUES
    (1, '2012-01-01 11:00:24', 1),
    (1, '2012-01-01 11:02:24', 1),
    (1, '2012-01-01 11:05:24', 0),
    (2, '2012-01-01 11:10:24', 1),
    (1, '2012-01-01 11:30:24', 1);
-- SQL EXAMPLE END

这是 PHP 代码:

<?php
$query = 'SELECT DISTINCT(`server`) `server` FROM stack_test';
$res = sql::exec($query); // replace with your function/method to execute SQL
while ($row = mysql_fetch_assoc($res)) {
    $server = $row['server'];
    $uptimes = sql::exec('SELECT * FROM stack_test WHERE server=? ORDER BY rtime DESC',$server);
    $online = 0;
    $prev = time();
    $prev = strtotime('2012-01-01 11:40:00'); // just to show that it works given the example
    while ($uptime = mysql_fetch_assoc($uptimes)) {
        if ($uptime['status'] == 1) {
            echo date('g:ia',$prev) . ' to ' . date('g:ia',strtotime($uptime['rtime'])) . ' = '.(($prev-strtotime($uptime['rtime']))/60).' mins<br />';
            $online += $prev-strtotime($uptime['rtime']);
        }
        $prev = strtotime($uptime['rtime']);
    }
    echo 'Server '.$server.' is up for '.($online/60).' mins.<br />';
}
?>

这是我得到的输出:

11:40am to 11:30am = 10 mins
11:05am to 11:02am = 3 mins
11:02am to 11:00am = 2 mins
Server 1 is up for 15 mins.

11:40am to 11:10am = 30 mins
Server 2 is up for 30 mins.

【讨论】:

  • 这绝对是一个绝妙的答案,但是我试图获得尽可能多的 SQL 和尽可能少的 PHP。问题在于有数十万行并且将每一行传输到 php 太慢了
猜你喜欢
  • 2014-02-11
  • 1970-01-01
  • 2011-07-06
  • 2021-10-13
  • 2019-10-04
  • 1970-01-01
  • 1970-01-01
  • 2011-03-18
  • 1970-01-01
相关资源
最近更新 更多