【问题标题】:How to display hourly stats from mysql table如何从 mysql 表中显示每小时统计信息
【发布时间】:2012-06-26 18:58:19
【问题描述】:

我正在尝试为 PHP 中的图表生成数据,该图表显示 mysql 表在特定时间范围内按每小时细分的记录数量。每条记录都有一个 unix 时间戳。

例如,假设我想显示今天的统计数据。下面的代码“有效”,但在运行它并查看我所做的之后,它只是发生了可怕的胡言乱语。当我在具有数百万条索引记录的表上运行它时,速度很慢www。

它现在所做的是每小时执行一次查询,直到达到 24 小时。问题是我试图同时从多达 10 个其他表中提取数据。这意味着我在每次页面加载时可能会运行多达 240 个查询,这并不好。

$c = '0';
$h = '1';
while($h < 25){
    $hr_start = 3600 * $c;
    $hr_stop = 3600 * $h;
    $query = "SELECT `reason`,`timestamp`
    FROM `c_blacklist` 
    WHERE `timestamp` > '".strtotime('today')."'  + ".$hr_start." AND `timestamp` < '".strtotime('today')."' + ".$hr_stop." AND `reason` = 'hardbounce'";
    $result = mysql_query($query) or die(mysql_error());
    $hardbounce_count = mysql_num_rows($result);
    $dataset5[] = array($h,$hardbounce_count);
    $h++;
    $c++;
}

我知道有更好的方法可以做到这一点,但我无法找到更多相关信息。有没有办法运行 1 个查询,然后让 PHP 按小时分解它并插入数据集中?我很困惑,我很感激任何帮助。谢谢。

【问题讨论】:

  • 你能按小时分组吗(FROM_UNIXTIME(unixtime)) WHERE date = DATE(FROM_UNIXTIME(unixtime))?
  • c_blacklist 中名为timestamp 的列的数据类型是什么?是TIMESTAMP 还是DATETIME?时间戳列上的 WHERE 子句可能不是最优的。

标签: php mysql date time


【解决方案1】:

您可以创建一种“报告查询”,在调用时会为您提供最近 24 小时的数据。

第一步是创建一个包含 24 行的引用表,其中包含数字 1-24(或 0-23,具体取决于您的逻辑)。我将把这张桌子称为hours。通过使用此参考表,如果在给定小时内未发生任何活动,您仍将获得 0 计数。这与仅对时间戳执行 GROUP BY 的方法不同。

然后,使用TIMEDIFFHOUR 函数的组合左连接到该表。像这样的东西(未经测试,但你明白了):

SELECT
    COUNT(c_blacklist.reason) as num_reasons,
    hours.hour as hour
FROM hours
LEFT JOIN c_blacklist
   ON HOUR(TIMEDIFF(now(), c_blacklist.timestamp)) = hours.hour
GROUP BY hours.hour

这将输出 24 行,其中包含过去 24 小时中每个小时的“原因”数。如果需要,您可以很容易地添加一些时间戳

【讨论】:

  • 我倾向于同意这个概念。我们会为 8 万所不同学校记录数百万个视频的每个视频事件(播放/暂停/停止/开始),因此需要一个通宵流程来获取日志并创建历史摘要和视图,以优化用户希望看到的内容
  • +1 让数据库返回一个 COUNT 比从数据库中检索所有行并在客户端对它们进行计数要快得多。
  • +1。这是一种可行的方法。如果 c_blacklist 具有跨越大日期范围的大量行,则一个问题是性能。我不相信 MySQL 将能够通过此查询使用时间戳列上的索引。
【解决方案2】:

让数据库返回一个计数会快得多,而不是拉回所有详细信息行并在客户端进行计数。

您可以在一次查询中提取整整 24 小时的计数,这(可能)比在数据库中往返 24 次以获取单个计数要高效得多。

如果您在c_blacklist(timestamp) 上有一个索引,或者在c_blacklist(timestamp,reason) 上有一个覆盖索引,则可能会提高(查询的)性能。

如果timestamp 列的数据类型为TIMESTAMP,那么我们可以做一些简单的算术来推导出“小时”,并按每个“小时”计算。

SELECT FROM_UNIXTIME((UNIX_TIMESTAMP(cb.`timestamp`) DIV 3600) * 3600) AS `cb_hour`
     , COUNT(1) AS cb_count
  FROM `c_blacklist` cb
 WHERE cb.`timestamp` >= DATE_ADD('2012-06-26 18:00',INTERVAL -1 DAY)
   AND cb.`timestamp` <  '2012-06-26 18:00'
   AND cb.`reason` = 'hardbounce'
 GROUP BY FROM_UNIXTIME((UNIX_TIMESTAMP(cb.`timestamp`) DIV 3600) * 3600)
 ORDER BY FROM_UNIXTIME((UNIX_TIMESTAMP(cb.`timestamp`) DIV 3600) * 3600)

如果时间戳列的数据类型为DATETIME,则使用不同的表达式来获取小时可能会更快:

SELECT DATE_FORMAT(cb.`timestamp`,'%Y-%m-%d %H:00:00') AS `cb_hour`
     , COUNT(1) AS cb_count
  FROM `c_blacklist` cb
 WHERE cb.`timestamp` >= DATE_ADD('2012-06-26 18:00',INTERVAL -1 DAY)
   AND cb.`timestamp` <  '2012-06-26 18:00'
 GROUP BY DATE_FORMAT(cb.`timestamp`,'%Y-%m-%d %H:00:00')
 ORDER BY DATE_FORMAT(cb.`timestamp`,'%Y-%m-%d %H:00:00')

此查询将有“空白”,其中没有要计算的行数,也就是说,它们不会返回零计数。

这可以通过提供一个返回每个“小时”值的行源来解决,然后对结果集执行左连接。在以下语句中,别名为 h 的子查询返回 24 行,每小时一行。我们将其用作针对“结果”查询(从上方)进行左连接的驱动行源。任何我们没有匹配到的地方,我们都会得到一个 NULL 来计数。我们可以通过一个简单的函数调用将 NULL 替换为 0。

SELECT h.hour AS cb_hour
     , IFNULL(c.cb_count,0) AS cb_count
  FROM (SELECT DATE_ADD('2012-06-26 18:00',INTERVAL -1*d.i HOUR) AS `hour`
          FROM (SELECT 00 AS i UNION ALL SELECT 01 UNION ALL SELECT 02 UNION ALL SELECT 03 
                UNION ALL SELECT 04 UNION ALL SELECT 05 UNION ALL SELECT 06 UNION ALL SELECT 07 
                UNION ALL SELECT 08 UNION ALL SELECT 09 UNION ALL SELECT 10 UNION ALL SELECT 11 
                UNION ALL SELECT 12 UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL SELECT 15 
                UNION ALL SELECT 16 UNION ALL SELECT 17 UNION ALL SELECT 18 UNION ALL SELECT 19 
                UNION ALL SELECT 20 UNION ALL SELECT 21 UNION ALL SELECT 22 UNION ALL SELECT 23 
                ORDER BY 1 DESC
               ) d
       ) h
  LEFT
  JOIN (SELECT FROM_UNIXTIME((UNIX_TIMESTAMP(cb.`timestamp`) DIV 3600) * 3600) AS `cb_hour`
             , COUNT(1) AS cb_count
          FROM `c_blacklist` cb
         WHERE cb.`timestamp` >= DATE_ADD('2012-06-26 18:00',INTERVAL -1 DAY)
           AND cb.`timestamp` < '2012-06-26 18:00'
           AND cb.`reason` = 'hardbounce'
         GROUP BY FROM_UNIXTIME((UNIX_TIMESTAMP(cb.`timestamp`) DIV 3600) * 3600)
         ORDER BY FROM_UNIXTIME((UNIX_TIMESTAMP(cb.`timestamp`) DIV 3600) * 3600)
       ) c
    ON c.cb_hour = h.hour
 ORDER BY h.hour

当然,这比您目前拥有的查询文本要多得多。

为了在我的代码中加入这一点,我会将出现的三个日期文字替换为“%s”,并使用 sprintf 将出现的三个日期替换为格式化的日期字符串。 (所有三个事件都传递相同的值。)

【讨论】:

    【解决方案3】:

    按时间戳的小时值分组。

    SELECT
        date_format(`timestamp`,'%H') day_hour,
        count(*) count
    FROM
        `c_blacklist`
    WHERE
        `timestamp` between $start and $end
        and `reason` = 'hardbounce'
    GROUP BY
        date_format(`timestamp`,'%H')
    ORDER BY
        1;
    
    $result = mysql_query($query) or die(mysql_error());
    foreach($row = mysql_fetch_array($result)) {
        $dataset5[] = array($row['day_hour'],$row['count'])
    }
    

    【讨论】:

      【解决方案4】:
      $query = "SELECT `reason`,`timestamp`,FROM_UNIXTIME(timestamp, '%H') as Hour
      FROM `c_blacklist` 
      WHERE `timestamp` > ('".strtotime('today')."'  + ".$hr_start.") AND (`timestamp` < '".strtotime('today')."' + ".$hr_stop.") AND `reason` = 'hardbounce'
      GROUP BY FROM_UNIXTIME(timestamp, '%H')";
      

      添加了一些 () 用于操作保护顺序,但添加了 FROM_UNIXTIME('%H', timestamp) 假设时间戳是纪元/unix 时间戳,它将为您提供小时。

      【讨论】:

        猜你喜欢
        • 2014-05-27
        • 1970-01-01
        • 2010-10-23
        • 2020-11-06
        • 1970-01-01
        • 1970-01-01
        • 2018-06-28
        • 1970-01-01
        • 2014-10-18
        相关资源
        最近更新 更多