限制每个标准

时间:2008-12-14 20:00:08

标签: sql mysql optimization greatest-n-per-group

我有一个文章表和一个类别表。我想为每个类别获取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&amp; REF。选择类型是PRIMARY和DEPENDENT SUBQUERY。

有更好的解决方案吗?

6 个答案:

答案 0 :(得分:4)

以下是我如何解决这个问题:

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;

相关子查询通常表现不佳,因此该技术使用连接。

对于给定的文章,搜索与所考虑的当前文章的类别(a1)匹配的文章(a2),并且具有更新的日期(或者在平局的情况下更高id)。如果满足该标准的文章少于七篇,那么当前的文章必须属于其最新类别。

如果您可以依赖与id具有相同排序顺序的唯一pubdate列,那么您可以简化连接,因为在唯一列上没有关联:

  ON (a1.categories_id = a2.categories_id AND a1.id < a2.id)

答案 1 :(得分:2)

  1. 桌子有多大,慢得多慢?

  2. 桌子上有哪些索引?

  3. EXPLAIN的全部信息是什么?

  4. 此外,两个日期时间值是显式的,因此看起来这是从由其他信息组成的代码生成的代码生成的。是否存在某种类型的SQL查询,它在列表的循环中执行它?

    目前尚不清楚哪7篇文章被选中 - 最新?到哪几天?

答案 2 :(得分:1)

所以看起来你要求的文章少于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的样子。)

编辑:将“&lt; 7”更改为&lt; = 7“

答案 3 :(得分:0)

您有多种选择 - 有些可能会导致性能问题,但这取决于很多因素。

您可以将其拆分为多个查询。一个查询读出所有类别:

SELECT categories_id FROM Categories

然后对于每个类别,请阅读前七篇文章:

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

......等等每个类别。

这有一个简单易懂的好处,但缺点是它将一个查询转换为1 +(1 *猫数)。然后,你可以限制类别的数量,这样你就有了一些控制元素。有时您会发现5个简单查询比1个复杂查询执行得更好!

这种假设您从一些您可以控制的代码调用SQL - 是这种情况吗?

答案 4 :(得分:0)

在测试中我发现限制7在MySQL的子查询中不起作用,请参阅Bill的建议,我证实它运行良好。

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

答案 5 :(得分:0)

虽然比尔的查询平均可能会有所改善,但一次运行需要230秒。我没有做一个完整的测试(几次运行),但它仍然太慢所以我想更好的选择是每个类别获取最新的7个项目进行1次查询 - 看起来它会比所有其他选项更快。