问题
我有两个 SQL 表albums
和photos
. 专辑可以分层嵌套为树并使用嵌套集范例。照片是相册的孩子。
我需要一个 SQL 查询,它返回每个专辑 ID 的封面照片的 ID。封面照片应为相册所有递归子照片中评分最高的照片。
该解决方案必须适用于 PostgreSQL、MySQL 和 SQLite;即它应该基本上只使用标准SQL 特性,最多只使用所有DBMS 提供的非标准特性。
我知道已经提出了类似的问题(例如,在“选择每个 GROUP BY 组中的第一行?”),但我只能找到使用特定 DBMS 的 SQL 扩展的答案。
我已经有一种方法,但是速度太慢了。
环境
表格(albums
简化):
CREATE TABLE albums (
id INTEGER PRIMARY KEY,
parent_id INTEGER,
_lft INTEGER NOT NULL,
_rgt INTEGER NOT NULL,
)
表photos
(简化)
CREATE TABLE photos (
id INTEGER PRIMARY KEY,
created_at DATETIME NOT NULL,
album_id INTEGER,
mime_type VARCHAR NOT NULL
width INTEGER NOT NULL,
height INTEGER NOT NULL,
is_starred BOOLEAN NOT NULL DEFAULT false
)
评分最高的照片被定义为最好加星标的照片,然后是最近的照片。
第一种方法:正确,但速度太慢
SELECT
best_child_photo.album_id AS album_id,
best_child_photo.cover_id AS cover_id,
photos.mime_type AS cover_mime_type,
photos.width AS cover_width,
photos.height AS cover_height
FROM (
SELECT
covered_albums.id AS album_id,
(
SELECT p.id
FROM photos AS p
LEFT JOIN albums AS direct_parents ON (direct_parents.id = p.album_id)
WHERE direct_parents._lft >= covered_albums._lft AND direct_parents._rgt <= covered_albums._rgt
ORDER BY p.is_starred DESC, p.created_at DESC
LIMIT 1
) AS cover_id
FROM albums AS covered_albums
) AS best_child_photo
LEFT JOIN photos ON (photos.id = best_child_photo.cover_id);
缓慢的部分是内部的单值查询,它为每个相册 ID 找到最佳子照片的 ID。
第二种方法:快速但不完整
一个错误但更快的查询是
SELECT
albums.id AS album_id,
photos.id AS cover_id,
photos.mime_type AS cover_mime_type,
photos.width AS cover_width,
photos.height AS cover_height
FROM albums
LEFT JOIN (
photos
LEFT JOIN albums AS direct_parents
ON (direct_parents.id = photos.album_id)
)
ON (direct_parents._lft >= albums._lft AND direct_parents._rgt <= albums._rgt);
ORDER BY album_id ASC, photos.is_starred DESC, photos.created_at DESC;
这是错误的,因为它不会为每个相册返回一行,而是将每个相册映射到其所有递归子照片。尽管它返回了很多很多行,但它比第一个查询快了两个数量级。使用第二种方法,查询计划器可以使用其索引树来执行左连接。
讨论
根据经验,可以说:“首先排序,限制为 1,最后加入”比“首先加入(所有内容),最后排序”慢。如您所见,第二种方法错过了“限制为 1”的步骤。
所以我想知道是否可以使用基于第二种方法的东西,然后使用相同的album_id
. 不幸的是,像
SELECT
albums.id AS album_id,
FIRST(photos.id) AS cover_id,
FIRST(photos.type) AS cover_type
FROM ...
...
ORDER BY album_id ASC, photos.is_starred DESC, photos.created_at DESC
GROUP BY album_id
无效,因为没有聚合函数FIRST
。
有任何想法吗?
最后的评论:我知道 MySQL 允许SELECT
-clause 中的列既不是分组函数也不是聚合函数。在这种情况下,MySQL 使用第一行的值,这正是我所需要的,但它是特定于 MySQL 的。使用 PostgreSQL,我可以使用DISTINCT ON (album_id)
which 也会给出我想要的结果,但DISTINCT ON
仅受 PostgreSQL 支持。