Mysql查询执行时间取第二个结果?

时间:2013-12-12 09:43:46

标签: php mysql

我有两张表有以下结构

-

- 表visitors

的表结构
CREATE TABLE IF NOT EXISTS `visitors` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `a_id` int(11) DEFAULT NULL,
  `visited_by` int(11) DEFAULT '0',
  `ip` varchar(30) DEFAULT NULL,
  `browser_name` varchar(255) DEFAULT NULL,
  `browser_short_name` varchar(20) DEFAULT NULL,
  `browser_version` varchar(20) DEFAULT NULL,
  `os_platform` varchar(20) DEFAULT NULL,
  `visited_time` datetime DEFAULT NULL,
  `is_visited` tinyint(4) DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=MyISAM

- 表numbers

的表结构
CREATE TABLE IF NOT EXISTS `numbers` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB 

表格访问者的总记录为172,153,而表格编号的总记录为5,896

我正在尝试使用以下查询获取最近30天的记录

select  
x.ts AS timestamp,
COUNT( y.`id`) as no_of_visitors,
DATE( y.`visited_time`) as visited_date,
MONTH( y.`visited_time`) as month_visit,
MONTHNAME( y.`visited_time`) as visit_month_name,
WEEKOFYEAR( y.`visited_time`) as visit_week_no,
YEAR( y.`visited_time`) as year_of_visit
from 
  (SELECT  date(DATE_ADD( CURDATE( ) , INTERVAL CAST(n.id as SIGNED) - 30 DAY)) AS ts
                 FROM numbers n
                WHERE DATE_ADD(CURDATE( ), INTERVAL CAST(n.id as SIGNED) - 30 DAY) <= CURDATE( )) x 
LEFT JOIN  
visitors y 
ON  
date(y.`visited_time`) = x.ts 
GROUP BY DATE(x.ts) 
order by DATE( x.ts) desc

在localhost中执行需要4.7833秒。我在查询中做错了什么? 我如何快速执行查询?请提供建议

3 个答案:

答案 0 :(得分:1)

有两个考虑因素

  • DATE(y.visited_time) = x.ts
    • 即使visit_time是INDEXed,也不能使用INDEX。
  • GROUP BY DATE(x.ts)ORDER BY(x.ts)
    • 即使GROUP BY DATE(y.visited_date)
    • 也不能使用索引

如果你确实加快了查询速度,我建议添加额外的字段来存储visited_time的DATE部分并使其成为INDEXed。 e.g

CREATE TABLE IF NOT EXISTS `visitors` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `a_id` int(11) DEFAULT NULL,
  `visited_by` int(11) DEFAULT '0',
  `ip` varchar(30) DEFAULT NULL,
  `browser_name` varchar(255) DEFAULT NULL,
  `browser_short_name` varchar(20) DEFAULT NULL,
  `browser_version` varchar(20) DEFAULT NULL,
  `os_platform` varchar(20) DEFAULT NULL,
  `visited_time` datetime DEFAULT NULL,
  `visited_time_dt` DATE,
  `is_visited` tinyint(4) DEFAULT '0',
  INDEX(visited_time_dt),
  PRIMARY KEY (`id`)
) ENGINE=MyISAM

然后您的最终查询如下所示并且更快(我猜)

SELECT  
    x.ts AS timestamp,
    COUNT( y.`id`) as no_of_visitors,
    DATE( y.`visited_time`) as visited_date,
    MONTH( y.`visited_time`) as month_visit,
    MONTHNAME( y.`visited_time`) as visit_month_name,
    WEEKOFYEAR( y.`visited_time`) as visit_week_no,
    YEAR( y.`visited_time`) as year_of_visit
FROM 
  (
    SELECT
        date(DATE_ADD( CURDATE( ) , INTERVAL CAST(n.id as SIGNED) - 30 DAY)) AS ts
    FROM 
        numbers n
    WHERE
        DATE_ADD(CURDATE( ), INTERVAL CAST(n.id as SIGNED) - 30 DAY) <= CURDATE( )
) x LEFF JOIN visitors y ON y.`visited_time_dt` = x.ts 
GROUP BY y.visited_time_dt
ORDER BY y.visited_time_dt desc

更新

这个怎么样? z表仅返回带有MIN,MAX值的1条记录。此MIN / MAX值与visitors.visited_date_dt

连接在一起
SELECT  
    x.ts AS timestamp,
    COUNT( y.`id`) as no_of_visitors,
    DATE( y.`visited_time`) as visited_date,
    MONTH( y.`visited_time`) as month_visit,
    MONTHNAME( y.`visited_time`) as visit_month_name,
    WEEKOFYEAR( y.`visited_time`) as visit_week_no,
    YEAR( y.`visited_time`) as year_of_visit
FROM  numbers n 
WHERE
  (
    SELECT
        date(DATE_ADD(CURDATE(), INTERVAL CAST(n.id as SIGNED) - 30 DAY)) AS ts
    FROM 
        numbers n
    WHERE
        CURDATE() >= DATE_ADD(CURDATE(), INTERVAL CAST(n.id as SIGNED) - 30 DAY
   ) x
   LEFF JOIN visitors y ON y.`visited_time_dt` = x.ts 
   INNER JOIN
   (
        SELECT
            MAX(DATE_ADD(CURDATE(), INTERVAL CAST(n.id as SIGNED) - 30 DAY)) AS max_ts,
            MAX(DATE_ADD(CURDATE(), INTERVAL CAST(n.id as SIGNED) - 30 DAY)) AS min_ts,
        FROM 
            numbers n
        WHERE
            CURDATE() >= DATE_ADD(CURDATE(), INTERVAL CAST(n.id as SIGNED) - 30 DAY
   ) z ON y.visited_time_dt BETWEEN z.min_ts AND z.max_ts
GROUP BY y.visited_time_dt
ORDER BY y.visited_time_dt desc

答案 1 :(得分:1)

你不只是想要:

select
COUNT( y.`id`) as no_of_visitors,
DATE( y.`visited_time`) as visited_date,
MONTH( y.`visited_time`) as month_visit,
MONTHNAME( y.`visited_time`) as visit_month_name,
WEEKOFYEAR( y.`visited_time`) as visit_week_no,
YEAR( y.`visited_time`) as year_of_visit
from 
visitors y 
where y.`visited_time` > date_add(curdate(), interval -30 day)
group by DATE(y.`visited_time`) 
order by DATE(y.`visited_time`) desc

使用数字表的整个事情会导致一个非常大的连接操作,这是低效的,我认为是不必要的,尽管我真的不明白你在用表号做什么......

修改 如果您想要所有日期,我会在上述查询结束后加入:

select * from
all_dates a left join 
(select
COUNT( y.`id`) as no_of_visitors,
DATE( y.`visited_time`) as visited_date,
MONTH( y.`visited_time`) as month_visit,
MONTHNAME( y.`visited_time`) as visit_month_name,
WEEKOFYEAR( y.`visited_time`) as visit_week_no,
YEAR( y.`visited_time`) as year_of_visit
from 
visitors y 
where y.`visited_time` > date_add(curdate(), interval -30 day)
group by DATE(y.`visited_time`) 
order by DATE(y.`visited_time`) desc) b
on a.date = b.visited_date;

显然all_dates是你的日期表。

答案 2 :(得分:0)

visited_time

上创建索引
CREATE INDEX `visited_time_ix` on visitors( `visited_time` );

并尝试此查询:

select  
x.ts AS timestamp,
COUNT( y.`id`) as no_of_visitors,
DATE( y.`visited_time`) as visited_date,
MONTH( y.`visited_time`) as month_visit,
MONTHNAME( y.`visited_time`) as visit_month_name,
WEEKOFYEAR( y.`visited_time`) as visit_week_no,
YEAR( y.`visited_time`) as year_of_visit
from 
  (SELECT  date(DATE_ADD( CURDATE( ) , INTERVAL CAST(n.id as SIGNED) - 30 DAY)) AS ts
                 FROM numbers n
                WHERE DATE_ADD(CURDATE( ), INTERVAL CAST(n.id as SIGNED) - 30 DAY) <= CURDATE( )) x 
LEFT JOIN  
visitors y 
ON  
date(y.`visited_time`) = x.ts 
WHERE y.`visited_time` >= (  SELECT min( date(DATE_ADD( CURDATE( ) , INTERVAL CAST(n.id as SIGNED) - 30 DAY)) )
                                                 FROM numbers n
                                                 WHERE DATE_ADD(CURDATE( ), INTERVAL CAST(n.id as SIGNED) - 30 DAY) <= CURDATE( ) 
        )
      AND y.`visited_time` <=  (  SELECT max( date(DATE_ADD( CURDATE( ) , INTERVAL CAST(n.id as SIGNED) - 29 DAY)) )
                                                 FROM numbers n
                                                 WHERE DATE_ADD(CURDATE( ), INTERVAL CAST(n.id as SIGNED) - 30 DAY) <= CURDATE( ) 
        ) 
GROUP BY DATE(x.ts) 
order by DATE( x.ts) desc
;