Как оптимизировать этот запрос? - PullRequest
1 голос
/ 26 октября 2011

У меня есть система голосования, которая разработана так:

CREATE TABLE `vote` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `weight` int(11) NOT NULL,
  `submited_date` datetime NOT NULL,
  `resource_type` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2963832 DEFAULT CHARSET=latin1;

CREATE TABLE `article_preselection_vote` (
  `id` int(11) NOT NULL,
  `article_id` int(11) DEFAULT NULL,
  `user_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `IDX_9B145DEA62922701` (`article_id`),
  KEY `IDX_9B145DEAA76ED395` (`user_id`),
  CONSTRAINT `article_preselection_vote_ibfk_4` FOREIGN KEY (`article_id`) REFERENCES `article` (`id`),
  CONSTRAINT `article_preselection_vote_ibfk_5` FOREIGN KEY (`id`) REFERENCES `vote` (`id`) ON DELETE CASCADE,
  CONSTRAINT `article_preselection_vote_ibfk_6` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

v.weight может быть +1 или -1, мне нужно, учитывая кучу идентификаторов статей, чтобы получить сумму каждого положительного голоса (+1) и сумму отрицательного голоса (-1) на идентификатор статьи.

Тогда мой результат должен быть

article_id | vote_up | vote_down
-----------|---------|----------
    1      |    36   |     20
-----------|---------|----------
    68     |    12   |     56
-----------|---------|----------
    25     |    90   |     12
-----------|---------|----------

Я могу получить этот результат, выполнив следующий запрос, но он довольно тяжелый и медленный при 2 000 000 голосов.

SELECT apv.article_id, COALESCE(SUM(up),0) as up, COALESCE(SUM(down),0) as down 
FROM article_preselection_vote apv 
LEFT JOIN(
    SELECT id, weight up FROM vote WHERE weight > 0 AND vote.resource_type = 'article') v1 ON apv.id = v1.id 
LEFT JOIN(
    SELECT id, weight down FROM vote WHERE weight < 0 AND vote.resource_type = 'article') v2 ON apv.id = v2.id 
WHERE apv.article_id IN (11702,11703,11704,11632,11652,11658)
GROUP BY apv.article_id

Есть идеи?

Заранее спасибо.

Ответы [ 4 ]

2 голосов
/ 26 октября 2011

Подвыборы, IN (...) и GROUP BY в одном запросе являются убийцами.

Вы должны изменить дизайн, чтобы иметь более традиционное решение:

  1. Иметь таблицу с голосами article_id, votes_up, votes_down, vote_date, ...
  2. Обновить (cron) сводные поля в таблице статей votes_up, votes_down, ... одним UPDATE.

Таким образом, вы можете лучше обрабатывать блокировки строк / таблиц и иметь быстрые запросы

1 голос
/ 26 октября 2011

Вы можете попробовать одиночное объединение:

SELECT
    apv.article_id,
    SUM(COALESCE(weight, 0) > 0) AS up,
    SUM(COALESCE(weight, 0) < 0) AS down 
FROM article_preselection_vote apv 
LEFT JOIN vote
    ON apv.id = vote.id
    AND vote.resource_type = 'article'
WHERE apv.article_id IN (11702, 11703, 11704, 11632, 11652, 11658)
GROUP BY apv.article_id

Если вам нужно часто это вычислять, возможно, стоит денормализовать вашу базу данных и сохранить кэшированную копию результатов.

0 голосов
/ 26 октября 2011

в скорлупе ореха сделайте что-то вроде этого:

select * from article where article_id in (1,2,3);
+------------+-----------+---------------+-----------------+
| article_id | title     | up_vote_count | down_vote_count |
+------------+-----------+---------------+-----------------+
|          1 | article 1 |             2 |               3 |
|          2 | article 2 |             2 |               1 |
|          3 | article 3 |             1 |               1 |
+------------+-----------+---------------+-----------------+
3 rows in set (0.00 sec)


drop table if exists article;
create table article
(
article_id int unsigned not null auto_increment primary key,
title varchar(255) not null,
up_vote_count int unsigned not null default 0,
down_vote_count int unsigned not null default 0
)
engine = innodb;

drop table if exists article_vote;
create table article_vote
(
article_id int unsigned not null,
user_id int unsigned not null,
score tinyint not null default 0,
primary key (article_id, user_id)
)
engine=innodb;

delimiter #

create trigger article_vote_after_ins_trig after insert on article_vote
for each row
begin
 if new.score < 0 then
  update article set down_vote_count = down_vote_count + 1 where article_id = new.article_id;
 else
  update article set up_vote_count = up_vote_count + 1 where article_id = new.article_id;
 end if;
end#

delimiter ;

insert into article (title) values ('article 1'),('article 2'), ('article 3');

insert into article_vote (article_id, user_id, score) values
(1,1,-1),(1,2,-1),(1,3,-1),(1,4,1),(1,5,1),
(2,1,1),(2,2,1),(2,3,-1),
(3,1,1),(3,5,-1);

select * from article where article_id in (1,2,3);
0 голосов
/ 26 октября 2011

Вместо того, чтобы взвешивать голоса, почему бы вам просто не создать две таблицы, одну для голосов "за" и одну для голосов "за"? Единственное, что это усложнит, - это комбинация голосов, которая все равно будет простой суммой подсчета двух разных запросов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...