2012-02-14 7 views
0

私は非常に単純なはずのクエリを持っていますが、頭痛の原因になります。 私はいくつかの変数に基づいて広告をフィルタリングする必要があるシンプルな広告システムを持っています。 1日あたりのビュー数/クリック数と、特定の広告の合計ビュー数/クリック数を制限する必要があります。また、各広告は、広告が表示される1つ以上のスロットにリンクされています。私は各広告について必要な統計を保存するテーブルを持っています。統計表は頻繁に変更されることに注意してください。 これらは、私が使用しているテーブルです:これは私が与えられたスロットの広告を取得するために使用するクエリです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; 

(この例では、私はハードスロットIDとして20と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から変更できないことに注意してください)。

うまくいけば私の質問についてはっきりしていますが、そうでない場合は質問してください。事前に おかげで、 Kfir

+0

頭痛の原因は何ですか?私は演技を前提にしていますが...明確にしてください。 –

+1

ANSI-86とANSI-92スタイルの結合を混在させると、SQLを読みにくくなります。 '(ads.id = slots.ad_id)'をINNER JOINにすることもできます。 –

+2

このクエリを実行しようとすると、実際にはエラーが発生します。あなたのGROUP BYはads.idをリストアップしていますが、あなたのSELECTは4つの非集計フィールドを追加しています。 –

答えて

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

から変更してくださいあなたは非常にあなたが合計を実行している維持の代わりに持った場合、これは改善できる

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

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

へ:あなたはのようなものを変更することができますので、無制限を意味しない 2147483647に「制限なし」を意味しませんその都度計算してください。

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 

私は次の列がインデックス化されるべきであると信じている上にコメントを拡張し

0

これらの列にインデックスを適用するとSELECTが高速化されますが、INSERTが遅くなることに注意してくださいndexesも更新する必要があります。しかし、このすべてを一度に適用する必要はありません。あなたは段階的にそれを行い、選択だけでなく挿入のためにパフォーマンスがどのように振れるかを見ることができます。あなたが良いmiddlegroundを見つけることができない場合は、私は非正規化をお勧めします。