SQL:重构多连接查询

时间:2012-02-14 22:06:28

标签: mysql database

我的查询应该非常简单但却让我头疼不已。 我有一个简单的广告系统,需要根据一些变量过滤广告。 我需要限制每天的观看次数/点击次数以及给定广告的观看次数/点击次数总数。此外,每个广告都链接到广告可以展示的一个或多个广告位。我有一个表格可以保存每个广告所需的统计信息。请注意,统计信息表会经常更改。 这些是我正在使用的表格:

CREATE TABLE `t_ads` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `name` varchar(255) NOT NULL,
  `content` text NOT NULL,
  `is_active` tinyint(1) unsigned NOT NULL,
  `start_date` date NOT NULL,
  `end_date` date NOT NULL,
  `max_views` int(10) unsigned NOT NULL,
  `type` tinyint(3) unsigned NOT NULL default '0',
  `refresh` smallint(5) unsigned NOT NULL default '0',
  `max_clicks` int(10) unsigned NOT NULL,
  `max_daily_clicks` int(10) unsigned NOT NULL,
  `max_daily_views` int(10) unsigned NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1;

CREATE TABLE `t_ad_slots` (
  `id` int(10) unsigned NOT NULL auto_increment ,
  `name` varchar(255) NOT NULL,
  `width` int(10) unsigned NOT NULL,
  `height` int(10) unsigned NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1;

CREATE TABLE `t_ads_to_slots` (
  `ad_id` int(10) unsigned NOT NULL,
  `slot_id` int(10) unsigned NOT NULL,
  `value` int(10) unsigned NOT NULL,
  PRIMARY KEY  (`ad_id`,`slot_id`),
  KEY `slot_id` (`slot_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


ALTER TABLE `t_ads_to_slots`
  ADD CONSTRAINT `t_ads_to_slots_ibfk_1` FOREIGN KEY (`ad_id`) REFERENCES `t_ads` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION,
  ADD CONSTRAINT `t_ads_to_slots_ibfk_2` FOREIGN KEY (`slot_id`) REFERENCES `t_ad_slots` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION;

CREATE TABLE `t_ad_stats` (
  `ad_id` int(10) unsigned NOT NULL,
  `slot_id` int(10) unsigned NOT NULL,
  `date` date NOT NULL COMMENT,
  `views` int(10) unsigned NOT NULL,
  `unique_views` int(10) unsigned NOT NULL,
  `clicks` int(10) unsigned NOT NULL default '0',
  PRIMARY KEY  (`ad_id`,`slot_id`,`date`),
  KEY `slot_id` (`slot_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


ALTER TABLE `t_ad_stats`
  ADD CONSTRAINT `t_ad_stats_ibfk_1` FOREIGN KEY (`ad_id`) REFERENCES `t_ads` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION,
  ADD CONSTRAINT `t_ad_stats_ibfk_2` FOREIGN KEY (`slot_id`) REFERENCES `t_ad_slots` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION;

这是我用来获取给定广告位的广告的查询(请注意,在此示例中,我将硬编码为20作为广告位ID,0,1,2作为广告类型,我从php脚本获取此数据它会调用此查询)

SELECT      `ads`.`content`, `slots`.`value`, `ads`.`id`, `ads`.`refresh`, `ads`.`type`,
            SUM(`total_stats`.`views`) AS "total_views",
            SUM(`total_stats`.`clicks`) AS "total_clicks"
FROM        (`t_ads` AS `ads`,
            `t_ads_to_slots` AS `slots`)
LEFT JOIN   `t_ad_stats` AS `total_stats`
ON          `total_stats`.`ad_id` = `ads`.`id`
LEFT JOIN   `t_ad_stats` AS `daily_stats`
ON          (`daily_stats`.`ad_id` = `ads`.`id`)    AND
            (`daily_stats`.`date` = CURDATE())
WHERE       (`ads`.`id` = `slots`.`ad_id`)          AND
            (`ads`.`type` IN(0,1,2))                    AND
            (`slots`.`slot_id` = 20)                AND
            (`ads`.`is_active` = 1)                 AND
            (`ads`.`end_date` >= NOW())             AND
            (`ads`.`start_date` <= NOW())           AND
            ((`ads`.`max_views` = 0) OR
             (`ads`.`max_views` > "total_views"))   AND
            ((`ads`.`max_clicks` = 0) OR
             (`ads`.`max_clicks` > "total_clicks")) AND
            ((`ads`.`max_daily_clicks` = 0) OR
             (`ads`.`max_daily_clicks` > IFNULL(`daily_stats`.`clicks`,0))) AND
            ((`ads`.`max_daily_views` = 0) OR
             (`ads`.`max_daily_views` > IFNULL(`daily_stats`.`views`,0)))
GROUP BY    (`ads`.`id`)

我相信这个查询是自我解释的,即使它很长。请注意,我使用的MySQL版本是:5.0.51a-community。在我看来,这里的重要问题是对stats表的双重连接(我这样做,以便我能够从特定记录和多个记录(总和)中获取数据)。

如何实现此查询以获得更好的结果? (请注意,我无法从InnoDB更改。)

希望一切都清楚我的问题,但如果不是这样,请询问,我会澄清。 提前致谢, Kfir

2 个答案:

答案 0 :(得分:0)

将索引添加到以下列:

t_ads.is_active
t_ads.start_date
t_ads.end_date

将t_ad_stats上主键的顺序更改为:

(`ad_id`,`date`,`slot_id`)

或向t_ad_stats添加覆盖索引

('ad_id', 'date')

0意味着“无限制”更改为2147483647意味着无限制,因此您可以更改以下内容:

((`ads`.`max_views` = 0) OR (`ads`.`max_views` > "total_views"))

(`ads`.`max_views` > "total_views")

如果您保持总计运行而不必每次都计算它们,那么您可以大大改善这一点。

答案 1 :(得分:0)

扩展上面的评论我认为应该将以下列编入索引:

ads.id
ads.type
ads.start_date
ads.end_date
daily_stats.date

以及这些:

slots.slot_id
ads.is_active

还有这些:

ads.max_views
ads.max_clicks
ads.max_daily_clicks
ads.max_daily_views
daily_stats.clicks
daily_stats.views

请注意,在这些列上应用索引会加快SELECT的速度,但会降低INSERT的速度,因为索引也需要更新。但是,您不必一次性应用所有这些。您可以逐步执行此操作,并查看性能如何影响选择和插入。如果你找不到一个好的中间地带那么我会建议非规范化。