非常具体的MySQL查询我想改进

时间:2012-07-06 17:52:28

标签: php mysql

这是我的场景:我有一个包含事件的表,每个事件都有一个名为“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;
    }
}

我希望我足够清楚。谢谢, 赫苏斯。

6 个答案:

答案 0 :(得分: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(已创建),将其包装在内联视图中。

然后将它连接到你的桌子以获得每一行。

但这确实非常混乱,难以理解,并且不可能比你现在做的更快(或更有效)。您可以做的最大改进是运行9个查询所需的时间。


答案 1 :(得分:1)

我会添加另一个列,即日期(而不是时间),然后使用MySQL“group by”获取每个日期的最新日期。

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

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

答案 2 :(得分: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)";

答案 3 :(得分:0)

您想要在10天内完成所有活动,还是在10天内每天只举办一次活动?

无论哪种方式,请考虑MySQL's date functions寻求帮助。它应该可以帮助您获得所需的日期范围。

答案 4 :(得分: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

答案 5 :(得分: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