如何select每个类别最新的四个项目?

我有一个项目的数据库。 每个项目都使用类别表中的类别ID进行分类。 我正在尝试创build一个列出每个类别的页面,并在每个类别下面显示该类别中的4个最新项目。

例如:

宠物用品

img1 img2 img3 img4 

宠物食品

 img1 img2 img3 img4 

我知道我可以很容易地通过查询每个类别的数据库来解决这个问题,如下所示:

  SELECT id FROM类别 

然后遍历数据并查询每个类别的数据库以获取最新的项目:

  SELECT image FROM item category_id =:category_id 
 ORDER BY date_listed DESC LIMIT 4 

我想知道的是,如果我可以只使用1个查询,并抓住所有的数据。 我有33个类别,所以我想也许这将有助于减less对数据库的调用数量。

任何人都知道这是可能的吗? 或者,如果33个电话不是什么大事,我应该简单的做。

这是每组最大的问题,这是一个非常常见的SQL问题。

以下是我如何使用外连接解决它:

 SELECT i1.* FROM item i1 LEFT OUTER JOIN item i2 ON (i1.category_id = i2.category_id AND i1.item_id < i2.item_id) GROUP BY i1.item_id HAVING COUNT(*) < 4 ORDER BY category_id, date_listed; 

我假设item表的主键是item_id ,而且这是一个单调递增的pseudokey。 也就是说, item_id的较大值对应于item较新的一行。

以下是它的工作原理:对于每个项目,都有一些其他更新的项目。 例如,有三个项目比第四个最新的项目更新。 有零的项目比最新的项目更新。 所以我们想比较每个项目( i1 )和更新的项目( i2 ),并且与i1具有相同的类别。 如果这些新项目的数量less于四个,那么i1就是我们包含的项目之一。 否则,不要包含它。

这个解决scheme的优点在于,无论您拥有多less类别,它都能正常工作,并且如果更改类别,它将继续工作。 即使某些类别的项目数量less于四个,它也可以工作。


另一个可行的解决scheme,但依赖于MySQL用户variablesfunction:

 SELECT * FROM ( SELECT i.*, @r := IF(@g = category_id, @r+1, 1) AS rownum, @g := category_id FROM (@g:=null, @r:=0) AS _init CROSS JOIN item i ORDER BY i.category_id, i.date_listed ) AS t WHERE t.rownum <= 3; 

这个解决scheme是从另一个SO解决scheme的改编,感谢RageZfind这个相关/类似的问题。

注意

这个解决scheme对Justin的用例来说似乎是满意的。 根据您的使用情况,您可能需要在此文章中检查Bill Karwin或David Andres的解决scheme。 比尔的解决scheme有我的投票! 明白为什么,因为我把两个查询相邻的;-)

我的解决scheme的好处是每个category_id返回一个logging(项目表中的信息是“汇总”的)。 我的解决scheme的主要缺点是缺乏可读性,随着期望行数的增加(即每个类别有6行而不是6行),它越来越复杂。 也可能会随着项目表中的行数增长而稍慢。 (无论如何,所有的解决scheme都会在项目表中使用更less数量的符合条件的行时执行得更好,因此build议定期删除或移动旧项目和/或引入标志来帮助SQL尽早过滤行)

首先尝试(没有工作!!!)…

这种方法的问题在于,子查询会根据自连接所定义的笛卡尔积来生成非常多的行[正确但对我们不利] …

 SELECT id, CategoryName(?), tblFourImages.* FROM category JOIN ( SELECT i1.category_id, i1.image as Image1, i2.image AS Image2, i3.image AS Image3, i4.image AS Image4 FROM item AS i1 LEFT JOIN item AS i2 ON i1.category_id = i2.category_id AND i1.date_listed > i2.date_listed LEFT JOIN item AS i3 ON i2.category_id = i3.category_id AND i2.date_listed > i3.date_listed LEFT JOIN item AS i4 ON i3.category_id = i4.category_id AND i3.date_listed > i4.date_listed ) AS tblFourImages ON tblFourImages.category_id = category.id --WHERE here_some_addtional l criteria if needed ORDER BY id ASC; 

第二次尝试。 (工作正常!)

为子查询添加了一个WHERE子句,分别强制i1,i2,i3等中列出的date为最新的,第二次的,最后的,最后的等等(也允许为less于4个项目给定的类别id)。 还添加了无关的过滤条款,以防止显示“已售出”的条目或没有图像的条目(添加要求)

这个逻辑假定没有重复的date列表值(对于给定的category_id)。 否则这种情况会创build重复的行。 有效地使用列出的date是比尔解决scheme中定义/要求的单调递增的主键。

 SELECT id, CategoryName, tblFourImages.* FROM category JOIN ( SELECT i1.category_id, i1.image as Image1, i2.image AS Image2, i3.image AS Image3, i4.image AS Image4, i4.date_listed FROM item AS i1 LEFT JOIN item AS i2 ON i1.category_id = i2.category_id AND i1.date_listed > i2.date_listed AND i2.sold = FALSE AND i2.image IS NOT NULL AND i1.sold = FALSE AND i1.image IS NOT NULL LEFT JOIN item AS i3 ON i2.category_id = i3.category_id AND i2.date_listed > i3.date_listed AND i3.sold = FALSE AND i3.image IS NOT NULL LEFT JOIN item AS i4 ON i3.category_id = i4.category_id AND i3.date_listed > i4.date_listed AND i4.sold = FALSE AND i4.image IS NOT NULL WHERE NOT EXISTS (SELECT * FROM item WHERE category_id = i1.category_id AND date_listed > i1.date_listed) AND (i2.image IS NULL OR (NOT EXISTS (SELECT * FROM item WHERE category_id = i1.category_id AND date_listed > i2.date_listed AND date_listed <> i1.date_listed))) AND (i3.image IS NULL OR (NOT EXISTS (SELECT * FROM item WHERE category_id = i1.category_id AND date_listed > i3.date_listed AND date_listed <> i1.date_listed AND date_listed <> i2.date_listed))) AND (i4.image IS NULL OR (NOT EXISTS (SELECT * FROM item WHERE category_id = i1.category_id AND date_listed > i4.date_listed AND date_listed <> i1.date_listed AND date_listed <> i2.date_listed AND date_listed <> i3.date_listed))) ) AS tblFourImages ON tblFourImages.category_id = category.id --WHERE -- ORDER BY id ASC; 

现在…比较下面我介绍一个item_id键,并使用比尔的解决scheme,以提供这些列表“外部”的查询。 你可以看到为什么比尔的方法更好…

 SELECT id, CategoryName, image, date_listed, item_id FROM item I LEFT OUTER JOIN category C ON C.id = I.category_id WHERE I.item_id IN ( SELECT i1.item_id FROM item i1 LEFT OUTER JOIN item i2 ON (i1.category_id = i2.category_id AND i1.item_id < i2.item_id AND i1.sold = 'N' AND i2.sold = 'N' AND i1.image <> '' AND i2.image <> '' ) GROUP BY i1.item_id HAVING COUNT(*) < 4 ) ORDER BY category_id, item_id DESC 

在其他数据库中,您可以使用ROW_NUMBER函数执行此操作。

 SELECT category_id, image, date_listed FROM ( SELECT category_id, image, date_listed, ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY date_listed DESC) AS rn FROM item ) AS T1 WHERE rn <= 4 

不幸的是,MySQL不支持ROW_NUMBER函数,但可以使用variables来模拟它:

 SELECT category_id, image, date_listed FROM ( SELECT category_id, image, date_listed, @rn := IF(@prev = category_id, @rn + 1, 1) AS rn, @prev := category_id FROM item JOIN (SELECT @prev := NULL, @rn = 0) AS vars ORDER BY category_id, date_listed DESC ) AS T1 WHERE rn <= 4 

看看它在线工作: sqlfiddle

它的工作原理如下:

  • Intial @prev被设置为NULL,并且@rn被设置为0。
  • 对于我们看到的每一行,检查category_id是否与前一行相同。
    • 如果是,则增加行号。
    • 否则,开始一个新的类别,并将行号重置为1。
  • 当子查询完成时,最后一步是过滤,只保留行号小于或等于4的行。

不是很漂亮,但是:

 SELECT image FROM item WHERE date_listed IN (SELECT date_listed FROM item ORDER BY date_listed DESC LIMIT 4) 

下面的代码显示了一个方法来做一个循环它肯定需要大量的编辑,但我希望它可以帮助。

  declare @RowId int 

declare @CategoryId int declare @CategoryName varchar(MAX)

(RowId int,CategoryId int,CategoryName varchar)从[Category Table]中selectRowId = ROW_NUMBER(),CategoryId,CategoryName的RowId int,CategoryId int,CategoryName varchar,Image image

  set @PartId = 0 

set @CategoryId = 0 while @Part_Id <= –count begin set @PartId = @PartId + 1 SELECT @CategoryId = category_id,@CategoryName = category_name from PART where PartId = @Part_Id SELECT RowId = @PartId,image,CategoryId = @ category_id,CategoryName = @category_name从项目到NEWESTFOUR其中category_id =:category_id ORDER BY date_listed DESC LIMIT 4

结束select*从NEWESTFOUR下降表NEWESTFOUR下降表部分

根据您的类别的常量,以下是最简单的路线

 SELECT C.CategoryName, R.Image, R.date_listed FROM ( SELECT CategoryId, Image, date_listed FROM ( SELECT CategoryId, Image, date_listed FROM item WHERE Category = 'Pet Supplies' ORDER BY date_listed DESC LIMIT 4 ) T UNION ALL SELECT CategoryId, Image, date_listed FROM ( SELECT CategoryId, Image, date_listed FROM item WHERE Category = 'Pet Food' ORDER BY date_listed DESC LIMIT 4 ) T ) RecentItemImages R INNER JOIN Categories C ON C.CategoryId = R.CategoryId ORDER BY C.CategoryName, R.Image, R.date_listed 

确定后google快速回答将是不可能的,至less在MySQL

这个线程仅供参考

也许你应该caching该查询的结果,如果你害怕跌倒服务器,并希望代码执行得更好