3

サーバーへのデータ取得要求を表す数十万行のデータテーブルがあります。各レコードには、タイムスタンプ、サーバー 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に取得して計算したり、何かを平均したりするよりも、はるかに好ましいと思います。

4

2 に答える 2

2

これは、適切にソートされた行セットで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)はUNIXタイムスタンプと同等の値で初期化されますtime。別の変数は前のタイムスタンプを保持するために使用され、2つの間の差を計算できます。他の変数は、前のサーバーIDと前のステータスを記憶するために使用されるため、必要に応じて、差は0として返されます(これは、サーバーの最初のイベントとイベントの後に続く行を記録するすべての行に対して行われofflineます)。

これで、上記のクエリによって生成された結果セットをグループ化し、値をSUM()し、seconds_onlineそれらを60で割って、次のように分を取得できます(秒に満足できない場合)。

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

ただし、最初のクエリは、それぞれの最後のイベント以降にオンラインで費やされたサーバーの秒数を実際には計算しないことに注意してください。つまり、現在の時刻は最新のイベントレコードの時刻とは大きく異なる可能性があり、クエリは前の行からの行ごとの秒数を計算するため、考慮されません。

これを解決する1つの方法は、現在のタイムスタンプと最後のレコードと同じステータスを含むサーバーごとに1つの行を追加することです。したがって、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
;

また、SQL Fiddleでも試してみることができます(試してみることができます)。

于 2012-09-09T19:12:38.523 に答える
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.
于 2012-09-09T06:15:43.460 に答える