【问题标题】:Rewriting a MySQL query重写 MySQL 查询
【发布时间】:2009-02-07 03:47:03
【问题描述】:

我将尝试在另一个问题上更好地解释这一点。这是我认为应该可以工作的查询,但当然,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 吗?

谢谢

【问题讨论】:

  • 让我想起 MySQL 中何时不允许子查询
  • @Brettski,我相信他的意思是在查询中有一个限制。也许阅读他更新的问题?
  • IMO 正确的答案是“停止使用 MySQL”:) 这个查询在 PostgreSQL 中运行良好。

标签: sql mysql subquery limit


【解决方案1】:

这是对您的解决方案的简化

    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)

【讨论】:

    【解决方案2】:

    看看这个名为Within-group quotas (Top N per group)的代码sn-p。

    根据您的集合的大小,提出了两种解决方案,一种使用计数,另一种使用临时表用于更大的表。

    所以基本上,如果你有一个大表,在 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 | 
    +------------+-------------+-------+-----------+---------------------+
    

    虽然我不知道它会如何转化为性能方面的。大概可以稍微优化和清理一下。

    【讨论】:

    • 这是非常好的链接。谢谢。虽然我不能使用这些解决方案。如果可以的话,我仍然会投票赞成你的答案。
    • 我认为我们可以以 15 分进行投票,而您似乎有 23 分。但别担心,我只是对我们如何做到这一点感到好奇。 :)
    • 在这里,您可以尝试专门为您的文章示例调整的临时解决方案。祝你好运!
    【解决方案3】:

    我找到了一个(可怕的、可怕的)解决方法,我什至不应该发布它,但是......

    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 结合使用,但每次检索一条记录时效果很好。

    如果您有更好的方法,我仍然对您的解决方案感兴趣。

    谢谢

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-30
      • 2021-11-01
      • 2011-12-13
      • 1970-01-01
      • 2012-10-19
      • 2016-12-12
      相关资源
      最近更新 更多