你快到了,你只需要使用你的item_category
表进行分组,因为那是 cat_id 所在的位置。
SELECT ...
FROM item_category AS c1
LEFT OUTER JOIN item_category AS c2
ON c1.cat_id = c2.cat_id AND c1.image_id < c2.image_id
GROUP BY c1.cat_id
HAVING COUNT(*) < 4
然后,一旦你知道了,你就知道它c1
包含每个类别的前四张图像。c1
然后,您可以加入image
表以获取其他属性:
SELECT i.id, i.title, c.cat_name AS CAT
FROM item_category AS c1
LEFT OUTER JOIN item_category AS c2
ON c1.cat_id = c2.cat_id AND c1.image_id < c2.image_id
INNER JOIN image AS on c1.image_id = i.id
INNER JOIN categories AS c on c1.cat_id = c.id
GROUP BY c1.image_id
HAVING COUNT(*) < 4;
尽管由于单值规则,这不是严格合法的 SQL ,但MySQL 将允许它。
从评论线程复制:
我会获取完整的结果,将其存储在缓存中,然后使用应用程序代码按我的意愿对其进行迭代。那将更简单并且具有更好的性能。SQL 功能强大,但另一种解决方案可能更易于开发、调试和维护。
您当然可以使用LIMIT
来遍历结果集:
SELECT i.id, i.title, c.cat_name AS CAT
FROM item_category AS c1
LEFT OUTER JOIN item_category AS c2
ON c1.cat_id = c2.cat_id AND c1.image_id < c2.image_id
INNER JOIN image AS on c1.image_id = i.id
INNER JOIN categories AS c on c1.cat_id = c.id
GROUP BY c1.image_id
HAVING COUNT(*) < 4
ORDER BY c.cat_id
LIMIT 4 OFFSET 16;
但请记住,执行 OFFSET 意味着每次查看另一组查询时都必须重新运行查询。MySQL 中有一些优化,因此一旦找到足够的行,它就会退出查询,但如果你频繁迭代,并且深入到一系列页面,它仍然很昂贵。
您可以使用两种可能的优化:一种是缓存部分结果,理论上很少有用户会想要浏览大型分页结果的每一页。因此,例如,获取足以填充十页结果的内容,并将其缓存。它大大减少了查询的数量,也许只有 1% 的时间会用户进入下一组十页。
SELECT i.id, i.title, c.cat_name AS CAT
FROM item_category AS c1
LEFT OUTER JOIN item_category AS c2
ON c1.cat_id = c2.cat_id AND c1.image_id < c2.image_id
INNER JOIN image AS on c1.image_id = i.id
INNER JOIN categories AS c on c1.cat_id = c.id
GROUP BY c1.image_id
HAVING COUNT(*) < 4
ORDER BY c.cat_id
LIMIT 40 OFFSET 40; /* second set of ten pages */
如果您可以假设 page 的任何视图N
都来自 page 的视图,则另一个优化N-1
是请求根据在N-1
st页面中看到的最大类别 id 过滤类别。您需要这样做,因为 OFFSET 按结果集中的行号工作,但索引偏移按在这些行上找到的值工作。如果可能存在间隙或未使用的 cat_id 值,则这些偏移量不同。
SELECT i.id, i.title, c.cat_name AS CAT
FROM item_category AS c1
LEFT OUTER JOIN item_category AS c2
ON c1.cat_id = c2.cat_id AND c1.image_id < c2.image_id
INNER JOIN image AS on c1.image_id = i.id
INNER JOIN categories AS c on c1.cat_id = c.id
WHERE c1.cat_id > 47 /* this value is the largest seen in previous page */
GROUP BY c1.image_id
HAVING COUNT(*) < 4
ORDER BY c.cat_id
LIMIT 40; /* no offset needed */
回复您的评论:
...使用 LIMIT 和 OFFSET 只会修剪这些结果,而不会将我移到行列表中。
LIMIT
正在按预期工作;它适用于完成工作之后 GROUP BY
的结果行。HAVING
在每个类别查询最大 N 之前我这样做的方式是
1. 拉入 x 数量的图像,
2. 记住哪个是最后一张图像,然后
3. 在我的后续查询中使用子查询来获取下一个 x id 小于最后一张图像的图像数量。每组 N 最大的情况是否可能发生这样的事情?
这就是我的WHERE
子句在上面最后一个示例中所做的,没有使用子查询。而且我假设您正在进入下一个更高的 cat_id 集。此解决方案仅在您一次前进一页并且朝正方向前进时才有效。
好吧,还有另一种适用于 MySQL 的每个组最大 n 的解决方案,但它依赖于用户变量功能。SQLite 没有这个特性。
SELECT * FROM (
SELECT
p.id as image_ID, p.imageURL as URL, c.cat_name as CAT, ic.cat_id,
IF(@cat=ic.cat_id, @row:=@row+1, @row:=1) AS _row, @cat:=ic.cat_id AS _cat
FROM (SELECT @cat:=null, @row:=0) AS _init
CROSS JOIN image_category AS ic
INNER JOIN portfolio AS p ON ic.image_id = p.id
INNER JOIN categories AS c on ic.cat_id = c.cat_id
ORDER BY ic.cat_id, ic.image_id
) AS x
WHERE _row BETWEEN 4 AND 6; /* or choose any range you want */
这类似于ROW_NUMBER() OVER (PARTITION BY cat_id)
标准 SQL 和大多数 RDBMS 支持的使用,但 SQLite 还不支持。