Переписать запрос MySQL - PullRequest
2 голосов
/ 07 февраля 2009

Я попытаюсь объяснить это лучше на другом вопросе. Это запрос, который, я думаю, должен работать, но, конечно, MySQL не поддерживает этот конкретный запрос на выборку:

select *
  from articles a
  where a.article_id in
      (select f.article_id
        from articles f
        where f.category_id = a.category_id
        order by f.is_sticky, f.published_at
        limit 3) /* limit isn't allowed inside a IN subquery */

То, что я пытаюсь заархивировать, таково: в таблице статей у меня есть несколько статей для нескольких категорий. Мне нужно получить не более трех статей в каждой категории (любое количество категорий).

Вот данные:

CREATE TABLE articles (
  article_id int(10) unsigned NOT NULL AUTO_INCREMENT,
  category_id int(10) unsigned NOT NULL,
  title varchar(100) NOT NULL,
  is_sticky boolean NOT NULL DEFAULT 0,
  published_at datetime NOT NULL,
  PRIMARY KEY (article_id)
);

INSERT INTO articles VALUES
(1, 1, 'foo', 0, '2009-02-06'),
(1, 1, 'bar', 0, '2009-02-07'),
(1, 1, 'baz', 0, '2009-02-08'),
(1, 1, 'qox', 1, '2009-02-09'),

(1, 2, 'foo', 0, '2009-02-06'),
(1, 2, 'bar', 0, '2009-02-07'),
(1, 2, 'baz', 0, '2009-02-08'),
(1, 2, 'qox', 1, '2009-02-09');

Я пытаюсь найти следующее:

1, 1, qox, 1, 2009-02-09
1, 1, foo, 0, 2009-02-06
1, 1, bar, 0, 2009-02-07
1, 2, qox, 1, 2009-02-09
1, 2, foo, 0, 2009-02-06
1, 2, bar, 0, 2009-02-07

Обратите внимание, как 'quox' поднялся на первое место в своей категории, потому что он липкий.

Можете ли вы найти способ избежать LIMIT внутри подзапроса?

Спасибо

Ответы [ 3 ]

1 голос
/ 07 февраля 2009

Это упрощение вашего решения

    select *
  from articles a
  where a.article_id =
      (select f.article_id
        from articles f
        where f.category_id = a.category_id
        order by f.is_sticky, f.published_at
        limit 1) or a.article_id =
      (select f.article_id
        from articles f
        where f.category_id = a.category_id
        order by f.is_sticky, f.published_at
        limit 1, 1) or 
    a.article_id =
      (select f.article_id
        from articles f
        where f.category_id = a.category_id
        order by f.is_sticky, f.published_at
        limit 2, 1)
1 голос
/ 07 февраля 2009

Взгляните на этот фрагмент кода, который называется Внутригрупповые квоты (наибольшее N на группу) .

В зависимости от размера вашего сета, предлагаются два решения: одно с подсчетом, а другое - с временным столом для больших столов.

Так что, по сути, если у вас большая таблица, пока MySQL не реализует LIMIT в подзапросах или что-то подобное, вам придется вручную (ну или с помощью динамического запроса в цикле) объединять все ваши категории с одной из Предлагаемые решения здесь.


// Решение с использованием временной таблицы и хранимой процедуры:

Запустите это один раз:

DELIMITER //
CREATE PROCEDURE top_articles()
BEGIN
    DECLARE done INT DEFAULT 0;
    DECLARE catid INT;
    DECLARE cur1 CURSOR FOR SELECT DISTINCT(category_id) FROM articles;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    OPEN cur1;
    # This temporary table will hold all top N article_id for each category
    CREATE TEMPORARY TABLE top_articles (
        article_id int(10) unsigned NOT NULL
    );
    # Loop through each category
    REPEAT
        FETCH cur1 INTO catid;
        INSERT INTO top_articles
        SELECT article_id FROM articles
        WHERE category_id = catid
        ORDER BY is_sticky DESC, published_at
        LIMIT 3;
    UNTIL done END REPEAT;
    # Get all fields in correct order based on our temporary table
    SELECT * FROM articles WHERE article_id 
    IN (SELECT article_id FROM top_articles)
    ORDER BY category_id, is_sticky DESC, published_at;
    # Remove our temporary table
    DROP TEMPORARY TABLE top_articles;
END;
//
DELIMITER ;

А затем, чтобы попробовать это:

CALL top_articles();

Вы должны увидеть результаты, которые вы ждали. И это должно работать для любого количества статей в каждой категории и любого количества категорий. Вот что я получаю:

+------------+-------------+-------+-----------+---------------------+
| article_id | category_id | title | is_sticky | published_at        |
+------------+-------------+-------+-----------+---------------------+
|          5 |           1 | qox   |         1 | 2009-02-09 00:00:00 | 
|          1 |           1 | foo   |         0 | 2009-02-06 00:00:00 | 
|          2 |           1 | foo   |         0 | 2009-02-06 00:00:00 | 
|          9 |           2 | qox   |         1 | 2009-02-09 00:00:00 | 
|          6 |           2 | foo   |         0 | 2009-02-06 00:00:00 | 
|          7 |           2 | bar   |         0 | 2009-02-07 00:00:00 | 
+------------+-------------+-------+-----------+---------------------+

Хотя я не знаю, как это будет переводить с точки зрения производительности. Вероятно, его можно немного оптимизировать и убрать.

0 голосов
/ 07 февраля 2009

Я нашел (ужасный, ужасный) обходной путь, который я, вероятно, даже не должен публиковать, но ...

select *
  from articles a
  where a.article_id =
      (select f.article_id
        from articles f
        where f.category_id = a.category_id
        order by f.is_sticky, f.published_at
        limit 1)
union
select *
  from articles a
  where a.article_id =
      (select f.article_id
        from articles f
        where f.category_id = a.category_id
        order by f.is_sticky, f.published_at
        limit 1, 1)
union
select *
  from articles a
  where a.article_id =
      (select f.article_id
        from articles f
        where f.category_id = a.category_id
        order by f.is_sticky, f.published_at
        limit 2, 1)
order by category_id

Поскольку мне нужно было только три статьи в каждой категории, я могу повторить запрос три раза (вместо повторения для каждой категории), один для первой статьи во всех категориях, один для второй статьи и один для третьей статьи во всех категориях и объединить их всех и упорядочить по категориям.

Кажется, LIMIT не поддерживается в сочетании с IN, но прекрасно работает для извлечения одной записи за раз.

Если у вас есть лучший способ, я все еще заинтересован в вашем решении.

Спасибо

...