2012-02-14 5 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; 

이 예에서 열심히 슬롯 ID와 0,1로 (20)를 코딩 2는 광고 유형으로, 나는

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

나는이 쿼리 심지어는 아주 긴하지만, 자명하다고 생각)이 쿼리를 호출하는 PHP 스크립트에서이 데이터를 얻을. 내가 사용하고있는 MySQL 버전은 5.0.51a-community입니다. 나에게 큰 문제는 통계 테이블에 대한 이중 조인 (여기서는 특정 레코드와 여러 레코드 (합계)에서 데이터를 가져올 수 있도록하는 것임)입니다.

더 나은 결과를 얻기 위해 어떻게이 쿼리를 구현 하시겠습니까? (InnoDB에서 변경할 수 없다는 점에 유의하십시오.)

내 질문에 대해 분명히 모든 것이 명확하지만, 사실이 아니라면 질문하고 명확히 할 것입니다.

t_ads.is_active 
t_ads.start_date 
t_ads.end_date 

변경에 t_ad_stats에 기본 키의 순서 : 미리 감사합니다, Kfir

+0

두통의 원인은 무엇입니까? 나는 성과를 취할 것이지만 ... 분명히하십시오. –

+1

ANSI-86 및 ANSI-92 스타일 조인을 혼합 할 때 SQL을 읽기가 어렵습니다. '(ads.id = slots.ad_id)'를 내부 결합으로 만들 수도 있습니다. –

+2

이 쿼리를 실행하려고하면 실제로 오류가 발생합니다. GROUP BY는 ads.id를 나열하지만 SELECT는 4 개의 추가 집계되지 않은 필드를 나열합니다. –

답변

0

다음 컬럼에 인덱스를 추가

(`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 

을 그리고이뿐만 아니라 : 나는 다음과 같은 열이 인덱싱해야한다고 생각합니다 위의 의견에 확장

0

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

이러한 열에 인덱스를 적용하면 SELECT 속도가 빨라지지만 INSERT가 느려지므로 유의하십시오. ndexes도 업데이트해야합니다. 그러나이 모든 것을 한꺼번에 적용 할 필요는 없습니다. 점진적으로 할 수 있으며 선택뿐만 아니라 삽입에 대한 성능이 어떻게 변하는 지 확인할 수 있습니다.좋은 중간 거리를 찾을 수 없다면 나는 비정규 화를 제안 할 것입니다.