3

这是我的场景:我有一个包含事件的表,每个事件都有一个名为“created”的字段,其中包含创建该事件的时间戳。现在我需要从最新到最旧对事件进行排序,但我不希望 MySQL 将它们全部返回。我只需要给定时间间隔内的最新信息,例如 24 小时范围内(编辑:我希望有一个灵活的解决方案,不仅适用于 24 小时范围,而且可能每隔几个小时)。我只需要最后 10 天。我已经实现了这一点,但我确信以最低效的方式,也就是说,像这样:

$timestamp = time();

for($i = 0; $i < 10; $i++) {
    $query = "SELECT * FROM `eventos` WHERE ... AND `created` < '{$timestamp}' ORDER BY `created` DESC LIMIT 1";    
    $return = $database->query( $query );

    if($database->num( $return ) > 0) {
        $event = $database->fetch( $return );
        $events[] = $event;

        $timestamp = $timestamp - 86400;
    }
}

我希望我足够清楚。谢谢,耶稣。

4

6 回答 6

3

如果您有一个索引created作为前导列,MySQL 可能能够进行反向扫描。如果您有一个没有任何事件的 24 小时时段,则您可能会返回不是该时段的行。为了确保您在该期间获得一行,您确实需要在列上包含一个下限created,如下所示:

SELECT * FROM `eventos`
 WHERE ... 
   AND `created` <  FROM_UNIXTIME( {$timestamp} )
   AND `created` >= DATE_ADD(FROM_UNIXTIME( {$timestamp} ),INTERVAL -24 HOUR)
 ORDER BY `created` DESC
 LIMIT 1

我认为这里性能的关键是索引created作为前导列,以及 WHERE 子句中引用的所有(或大多数)其他列,并确保您的查询使用该索引。

如果您需要不同的时间间隔,直到秒,这种方法可以很容易地推广。

SELECT * FROM `eventos`
 WHERE ... 
   AND `created` <  DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL  0*{$nsecs} SECOND)
   AND `created` >= DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL -1*{$nsecs} SECOND)
 ORDER BY `created` DESC
 LIMIT 1

从您的代码来看,24 小时周期似乎是在任意时间有界的......如果时间函数返回例如 1341580800('2012-07-06 13:20'),那么您的十个周期都将来自 13当天20点到次日13点20分。

(注意:请确保如果您的参数是 unix 时间戳整数,那么数据库会正确解释它。)

在单个查询中提取十行可能更有效。如果可以保证 'timestamp' 是唯一的,则可以制作这样的查询,但查询文本将比您现在拥有的要复杂得多。我们可以在每个周期内获取 MAX(timestamp_) ,然后将其加入以获取行......但这将非常混乱。

如果我要尝试拉出所有十行,我可能会尝试采用一种UNION ALL方法,虽然不是很漂亮,但至少可以调整。

SELECT p0.*
  FROM ( SELECT * FROM `eventos` WHERE ... 
            AND `created` <  DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL  0*24 HOUR)
            AND `created` >= DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL -1*24 HOUR)
          ORDER BY `created` DESC LIMIT 1
       ) p0 
 UNION ALL           
SELECT p1.*
  FROM ( SELECT * FROM `eventos` WHERE ... 
            AND `created` <  DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL -1*24 HOUR)
            AND `created` >= DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL -2*24 HOUR)
          ORDER BY `created` DESC LIMIT 1
       ) p1 
 UNION ALL           
SELECT p2.*
  FROM ( SELECT * FROM `eventos` WHERE ... 
            AND `created` <  DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL -2*24 HOUR)
            AND `created` >= DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL -3*24 HOUR)
          ORDER BY `created` DESC LIMIT 1
       ) p2 
 UNION ALL           
SELECT p3.*
  FROM ...

同样,这可以概括为在几秒钟内作为参数传递。将 HOUR 替换为 SECOND,并将“24”替换为具有秒数的绑定参数。

它相当冗长,但它应该运行良好。


另一种在单个结果集中获取它的非常混乱和复杂的方法是使用内联视图来获取十个周期的结束时间戳,如下所示:

     SELECT p.period_end
       FROM (SELECT DATE_ADD(t.t_,INTERVAL -1 * i.i_* {$nsecs} SECOND) AS period_end
               FROM (SELECT FROM_UNIXTIME( {$timestamp} ) AS t_) t
               JOIN (SELECT 0 AS i_
                     UNION ALL SELECT 1
                     UNION ALL SELECT 2
                     UNION ALL SELECT 3
                     UNION ALL SELECT 4
                     UNION ALL SELECT 5
                     UNION ALL SELECT 6
                     UNION ALL SELECT 7
                     UNION ALL SELECT 8
                     UNION ALL SELECT 9
                    ) i
            ) p

然后把它加入你的桌子......

  ON `created` < p.period_end
 AND `created` >= DATE_ADD(p.period_end,INTERVAL -1 * {$nsecs} SECOND)

并为每个周期 GROUP BY p.period_end 拉回 MAX(created),将其包装在一个内联视图中。

然后将其连接回您的表格以获取每一行。

但这真的非常非常混乱,难以理解,而且不太可能比你已经在做的更快(或更高效)。您可以做出的最大改进是运行 9 个查询所需的时间。


于 2012-07-06T18:17:43.750 回答
1

我会添加另一列是日期(不是时间),然后使用 MySQL“分组依据”来获取每个日期的最新信息。

http://www.tizag.com/mysqlTutorial/mysqlgroupby.php/

本教程就是这样做的,但按产品类型而不是日期。这应该有帮助!

于 2012-07-06T18:07:02.577 回答
1

假设您想要过去 10 天内每天的最新(创建日期最长)事件。

所以让我们每天获取最新的时间戳

$today = date('Y-m-d');
$tenDaysAgo = date('Y-m-d', strtotime('-10 day'));

$innerSql = "SELECT date_format(created, '%Y-%m-%d') day, MAX(created) max_created FROM eventos WHERE date_format(created, '%Y-%m-%d') BETWEEN '$today' and '$tenDaysAgo' GROUP BY date_format(created, '%Y-%m-%d')";

然后我们可以选择与这些创建日期匹配的所有事件

$outerSql = "SELECT * FROM eventos INNER JOIN ($innerSql) as A WHERE eventos.created = A.max_created";

我还没有机会对此进行测试,但原则应该足够健全。

如果您想按其他任意小时数进行分组,您可以更改 innerSql:

$fromDate = '2012-07-06' // or if you want a specific time '2012-07-06 12:00:00'
$intervalInHours = 5;
$numberOfIntervals = 10;

$innerSql = "SELECT FLOOR(TIMESTAMPDIFF(HOUR, created, '$fromDate') / $intervalInHours) as grouping, MAX(created) as max_created FROM eventos WHERE created BETWEEN DATE_SUB('$fromDate', INTERVAL ($intervalInHours * $numberOfIntervals) HOUR) AND '$fromDate' GROUP BY FLOOR(TIMESTAMPDIFF(HOUR, created, '$fromDate') / $intervalInHours)";
于 2012-07-06T18:12:23.373 回答
0

您想要 10 天内的所有活动,还是 10 天内每天只需要一个活动?

无论哪种方式,请考虑MySQL 的日期函数以获得帮助。它应该可以帮助您获得所需的日期范围。

于 2012-07-06T18:01:45.697 回答
0

试试这个:

    SELECT *
      FROM eventos
     WHERE created BETWEEN DATE_SUB(DATE(NOW()), INTERVAL 10 DAY) AND DATE_ADD(DATE(NOW()), INTERVAL 1 DAY)
  ORDER BY created DESC
     LIMIT 10
于 2012-07-06T18:51:10.257 回答
0

这是一个可以让您获得过去 10 天当天的第一个事件。

  SELECT *
    FROM eventos
   WHERE created BETWEEN DATE_SUB(DATE(NOW()), INTERVAL 10 DAY) AND DATE_ADD(DATE(NOW()), INTERVAL 1 DAY)
GROUP BY DATE(created)
ORDER BY MAX(created) DESC
   LIMIT 10
于 2012-07-06T19:04:59.910 回答