2012-02-14 85 views
0

我有一個查詢應該很簡單,但它會導致我很多頭痛。 我有一個簡單的廣告系統,需要根據幾個變量過濾廣告。 我需要限制每天觀看次數/點擊次數以及給定廣告的總觀看次數/點擊次數。此外,每個廣告都與廣告可以展示的一個或多個廣告位相關聯。我有一張表格,可以保存每個廣告所需的統計信息。請注意,統計信息表變化非常頻繁。 這些是我使用的表格:SQL:重構多連接查詢

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。在我看來,這裏最大的問題是對統計表的雙重連接(我這樣做,以便能夠從特定記錄和多個記錄(總和)中獲取數據)。

如何實現此查詢以獲得更好的結果? (請注意,我無法從InnoDB更改)。

希望一切都清楚我的問題,但如果情況並非如此,請問,我會澄清。 由於提前, 幼獅

+0

準確的頭痛原因是什麼?我會假設表現,但...請澄清。 – 2012-02-14 22:10:18

+1

當你混合使用ANSI-86和ANSI-92風格的連接時,難以閱讀SQL。你也可以讓'(ads.id = slots.ad_id)'爲INNER JOIN。 – 2012-02-14 22:11:25

+2

試圖運行此查詢應該實際上導致錯誤。您的GROUP BY列出了ads.id,但您的SELECT列出了另外4個非聚合字段。 – 2012-02-14 22:17:43

回答

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') 

從變更,意思是「沒有限制」,以2147483647意味着沒有限制,所以你可以改變的事情,如:

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

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

您可以大大提高,這是如果你保持運行總和,而不必到每次計算它們。

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,但會減慢自第i個以來的INSERT ndexes也需要更新。但是,您不必一次全部應用所有這些。您可以逐步完成,並查看性能如何選擇以及插入。如果你找不到一個好的中間場地,那麼我會建議反規範化。