Лимит по критерию - PullRequest
       21

Лимит по критерию

3 голосов
/ 14 декабря 2008

У меня есть таблица статей и таблица категорий. Я хочу получить 7 статей для каждой категории. В настоящее время у меня есть это, но это ужасно медленно на больших таблицах, так что это не совсем решение:

SELECT id, 
       title, 
       categories_id, 
       body, 
       DATE_FORMAT(pubdate, "%d/%m/%y %H:%i") as pubdate   
FROM articles AS t 
WHERE ( 
    SELECT COUNT(*) 
    FROM articles 
    WHERE t.categories_id = categories_id 
      AND id< t.id AND publish = 1 
      AND expires > '2008-12-14 18:38:02' 
      AND pubdate <= '2008-12-14 18:38:02' 
    ) < 7 
ORDER BY categories_id DESC

Используя объяснение, он показывает, что выполняет соединение типа ALL & REF. Выбранные типы: ПЕРВИЧНАЯ и ЗАВИСИМАЯ ПОДПИСЬ.

Есть ли лучшее решение?

Ответы [ 6 ]

5 голосов
/ 15 декабря 2008

Вот как бы я решил эту проблему:

SELECT a1.id, 
       a1.title, 
       a1.categories_id, 
       a1.body, 
       DATE_FORMAT(a1.pubdate, "%d/%m/%y %H:%i") as pubdate  
FROM articles AS a1
  LEFT OUTER JOIN articles AS a2
  ON (a1.categories_id = a2.categories_id AND 
     (a1.pubdate < a2.pubdate OR (a1.pubdate = a2.pubdate AND a1.id < a2.id)))
GROUP BY a1.id
HAVING COUNT(*) < 7;

Коррелированный подзапрос обычно работает плохо, поэтому вместо этого метода используется объединение.

Для данной статьи найдите статьи (a2), которые соответствуют категории рассматриваемой статьи (a1), и имеют более позднюю дату (или больше id в случае связи). Если существует менее семи статей, удовлетворяющих этим критериям, то текущая должна быть одной из самых последних в своей категории.

Если вы можете полагаться на уникальный столбец id, имеющий тот же порядок сортировки, что и pubdate, то вы можете упростить объединение, поскольку по уникальному столбцу не будет связей:

  ON (a1.categories_id = a2.categories_id AND a1.id < a2.id)
2 голосов
/ 14 декабря 2008
  1. Насколько велики таблицы, и насколько медленно это медленно?

  2. Какие индексы есть в таблицах?

  3. Какова вся информация из EXPLAIN?

Кроме того, два значения datetime являются явными, поэтому, похоже, это либо генерируется из кода, сгенерированного чем-то, составляющим его из другой информации. Есть ли еще какой-нибудь SQL-запрос, который выполняет это в цикле списка?

Непонятно, какие 7 статей выбираются - самые последние? К какой дате?

1 голос
/ 15 декабря 2008

Похоже, вы запрашиваете те категории с менее чем 7 статьями; вот где должен начинаться запрос -

SELECT categories_id,  COUNT(1)  
FROM articles  
WHERE publish = 1 
  AND expires > '2008-12-14 18:38:02' 
  AND pubdate <= '2008-12-14 18:38:02'
GROUP BY categories_id
HAVING COUNT(1) < 7

Затем создайте этот подзапрос с помощью:

SELECT 
    c.id, c.title, c.id, a.body,  
    DATEFORMAT(a.pubdate, "%d/%m/%y %H:%i") as pubdate  
FROM categories c  
JOIN articles a ON c.id = a.categories_id  
JOIN 
(  
    SELECT DISTINCT categories_id  
    FROM articles  
    WHERE publish = 1  
        AND expires > '2008-12-14 18:38:02'  
        AND pubdate <= '2008-12-14 18:38:02'  
    GROUP BY categories_id  
    HAVING COUNT(1) <= 7  
) AS j ON c.id = j.categories_id  
ORDER BY whatever  

Следующим шагом является ограничение количества возвращаемых статей до 7 - я могу заняться этим следующим, если это будет выглядеть правильно. (Попробуйте как есть и посмотрите, как выглядит EXPLAIN.)

РЕДАКТИРОВАТЬ: изменено "<7" на <= 7 "</p>

0 голосов
/ 15 декабря 2008

Хотя запрос Билла может в среднем работать немного лучше, на один прогон уходит 230 секунд. Я не делал полный тест (несколько запусков), но он все еще слишком медленный, поэтому я думаю, что лучшим вариантом было бы сделать 1 запрос на категорию, извлекая последние 7 элементов - похоже, что он будет быстрее, чем все другие варианты.

0 голосов
/ 14 декабря 2008

При тестировании я обнаружил, что Limit 7 не работает в подзапросах в MySQL, см. Предложение Билла, которое я проверил, что он работает хорошо.

SELECT id, 
       title, 
       categories_id, 
       body, 
       DATE_FORMAT(pubdate, "%d/%m/%y %H:%i") as pubdate   
FROM articles A INNER JOIN articles B ON B.categories_ID = A.Categories_ID
WHERE A.ID IN ( 
    SELECT ID
    FROM Articles  
    WHERE categories_id = A.categories_id 
      AND publish = 1 
      AND expires > '2008-12-14 18:38:02' 
      AND pubdate <= '2008-12-14 18:38:02' 
    LIMIT 7
    ORDER BY Categories_ID DESC) 
ORDER BY B.Categories_ID DESC
0 голосов
/ 14 декабря 2008

У вас есть несколько вариантов - некоторые могут привести к проблемам с производительностью, но это зависит от многих факторов.

Вы можете разбить его на несколько запросов. Один запрос для считывания всех категорий:

SELECT categories_id FROM Categories

Затем для каждой категории зачитайте семь лучших статей:

SELECT 
  id, 
  title, 
  ...etc.
FROM articles
where categories_id = 1 

... и так далее для каждой категории.

Преимущество в том, что его немного проще понять, но недостатком является то, что он превращает один запрос в 1 + (1 * количество кошек). Опять же, вы можете ограничить количество категорий, чтобы у вас был элемент управления. Иногда вы обнаруживаете, что 5 простых запросов работают намного лучше, чем 1 сложный!

Этот тип предполагает, что вы вызываете SQL из некоторого кода, которым вы управляете - так ли это?

...