-1

问题

我有两个 SQL 表albumsphotos. 专辑可以分层嵌套为树并使用嵌套集范例。照片是相册的孩子。

我需要一个 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 支持。

4

1 回答 1

0

这只是使用没有相关行为的标准 SQL 来确定每张专辑的封面照片的一个建议。

WITH cte AS (
        SELECT covered_albums.*
             , p.id AS photo_id
             , p.album_id AS photo_album
             , ROW_NUMBER() OVER (PARTITION BY covered_albums.id ORDER BY is_starred DESC, created_at DESC) AS rn
          FROM photos AS p
          JOIN albums AS direct_parents ON (direct_parents.id = p.album_id)
          JOIN albums AS covered_albums
            ON direct_parents._lft >= covered_albums._lft
           AND direct_parents._rgt <= covered_albums._rgt
     )
SELECT *
  FROM cte
 WHERE rn = 1
;

MySQL 8.0 的小提琴

结果(给定一些测试数据)。

注意:对于这个测试,创建了一个简单的相册层次结构(11 个相册),每个相册都有一张照片(巧合的是,ID 相同),只是为了展示一个简单的案例。

对于每张照片,这些created_at值都被初始化为不同的,以避免出现平局问题。

+----+-----------+------+------+----------+-------------+----+
| id | parent_id | _lft | _rgt | photo_id | photo_album | rn |
+----+-----------+------+------+----------+-------------+----+
|  1 |      NULL |    1 |   22 |       10 |          10 |  1 |
|  2 |      NULL |    2 |    9 |        6 |           6 |  1 |
|  3 |      NULL |   10 |   21 |       10 |          10 |  1 |
|  4 |      NULL |    3 |    8 |        6 |           6 |  1 |
|  5 |      NULL |    4 |    5 |        5 |           5 |  1 |
|  6 |      NULL |    6 |    7 |        6 |           6 |  1 |
|  7 |      NULL |   11 |   16 |       10 |          10 |  1 |
|  8 |      NULL |   17 |   18 |        8 |           8 |  1 |
|  9 |      NULL |   19 |   20 |        9 |           9 |  1 |
| 10 |      NULL |   12 |   13 |       10 |          10 |  1 |
| 11 |      NULL |   14 |   15 |       11 |          11 |  1 |
+----+-----------+------+------+----------+-------------+----+
于 2021-10-24T15:35:39.370 回答