1

我正在尝试提高查询的性能。据我了解,当我认为没有必要时EXPLAIN ANALYZE,我的查询考虑了太多记录。songs

共有三个表artists(artist_id, score)和。songs(song_id, artist_id)listened(song_id)

我当前的查询如下所示:

WITH artists_ranked AS (
    SELECT
      artist_id
      , rank() OVER (ORDER BY score ) rnk
    ORDER BY rnk ASC
),
    not_listened_songs AS (
      SELECT *
      FROM songs
      WHERE NOT EXISTS(
          SELECT 1
          FROM listened
          WHERE listened.song_id = songs.song_id) -- bad: I go through all songs
  ),
    shuffled_songs AS (
      SELECT *
      FROM artists_ranked
        JOIN not_listened_songs ON not_listened_songs.artist_id = artists_ranked.artist_id
      ORDER BY random() --bad: I shuffle all songs
  )
SELECT DISTINCT ON (artist_id) *
FROM shuffled_songs
LIMIT 1;

理想情况下(至少在我看来),查询应遵循以下步骤:

  1. 按等级对artists表格进行排名。
  2. 拿一批评分最高的艺术家。可以是一位或多位艺术家。

  3. 加入表格songs,但排除已listened歌曲。

  4. 现在我们想随机选择一首歌曲,给每个艺术家平等的机会。 ORDER BY random(), DISTINCT BY (artist_id),LIMIT 1

  5. 如果有这样的歌曲,我们停止并返回它。否则,取下一批艺术家(排名最接近的较低等级)并重复这些步骤。

    • 要停止,要么返回一首歌曲(很可能在几次迭代之后),要么已经考虑了所有艺术家。

谢谢你。

4

2 回答 2

1

从关系代数的角度考虑问题,而不是循环。

artists要获取尚未播放的歌曲,songs请加入. 按分数降序排列,首先从评分最高的艺术家那里获得歌曲,然后在每个分数内随机洗牌。限制为 1 条记录。song_idlistened

SELECT song_id
FROM artists a
JOIN songs s ON s.artist_id = a.artist_id
WHERE NOT EXISTS (SELECT TRUE FROM listened l WHERE l.song_id = s.song_id)
ORDER BY score DESC, RANDOM()
LIMIT 1

我们是否可以通过考虑相同数量的歌曲来为每个得分最高的艺术家提供平等的机会。艺术家可以拥有不同数量的歌曲。如果有 2 位最高分的歌手,一位有 100 首歌曲,另外 1 首歌曲,那么从第二位歌手中挑选歌曲的概率是 0.01,但应该是 0.5

这会为每个艺术家随机排列尚未听过的歌曲,然后按分数降序排列然后按歌曲排名显示最终结果,这实际上是交错来自同一排名的所有艺术家的随机歌曲:

SELECT song_id
FROM artists a
NATURAL JOIN songs s 
WHERE NOT EXISTS (
    SELECT TRUE 
    FROM listened l 
    WHERE l.song_id = s.song_id
)
ORDER BY score DESC
       , ROW_NUMBER() OVER (PARTITION BY artist_id ORDER BY RANDOM())
       , FIRST_VALUE(RANDOM()) OVER (PARTITION BY artist_id)
于 2018-04-17T00:54:01.500 回答
1

我会尝试使用让引擎按顺序LATERAL JOIN逐一浏览艺术家。score

添加artist_idlistened表中以避免额外的连接并将搜索限制为一次只能搜索一位艺术家。

向表中添加索引。拥有这些索引很重要。

artists (score, artist_id)
songs (artist_id, song_id)
listened (artist_id, song_id)

询问

SELECT
    artists.artist_id
    ,s.song_id
FROM
    artists
    INNER JOIN LATERAL
    (
        SELECT songs.song_id
        FROM songs
        WHERE
            songs.artist_id = artists.artist_id
            AND NOT EXISTS
            (
                SELECT 1
                FROM listened
                WHERE
                    listened.artist_id = songs.artist_id
                    -- limit listened songs to one artist
                    AND listened.song_id = songs.song_id
            )
        ORDER BY random()
        -- shuffle only songs of one artist
        LIMIT 1
    ) AS s ON true
ORDER BY artists.score ASC, random()
-- if there are several artists with the same score
-- pick one random artist among them
LIMIT 1;

该查询将选择顶级艺术家,随机播放其歌曲,选择下一位顶级艺术家,随机播放他的歌曲,等等。

当艺术家有歌曲要播放并且会变得越来越慢并且它会遍历顶级艺术家列表到排名较低的行时,此查询应该运行得很快。

如果score不是唯一的,那么ORDER BY score LIMIT 1将返回一个具有最高分的“随机”行。没有定义将选择哪个艺术家。它不是严格随机的,只是没有定义。它可以在每次查询运行时更改或保持不变。要使其真正随机,只需random()显式添加。

通过此添加,查询将以相同的概率在具有相同最高分的几位艺术家之间进行选择,而不管他们拥有多少首歌曲。


您可以扩展查询以拥有它考虑的“批次”顶级N艺术家,而不仅仅是每次的单个顶级艺术家:

WITH
CTE
AS
(
    SELECT
        artists.artist_id
        ,s.song_id
    FROM
        artists
        INNER JOIN LATERAL
        (
            SELECT songs.song_id
            FROM songs
            WHERE
                songs.artist_id = artists.artist_id
                AND NOT EXISTS
                (
                    SELECT 1
                    FROM listened
                    WHERE
                        listened.artist_id = songs.artist_id
                        -- limit listened songs to one artist
                        AND listened.song_id = songs.song_id
                )
            ORDER BY random()
            -- shuffle only songs of one artist
            LIMIT 1
        ) AS s ON true
    ORDER BY artists.score ASC
    LIMIT 5 -- pick top N artists, N = 5
)
SELECT
    artist_id
    ,song_id
FROM CTE
ORDER BY random() -- shuffle top N artists
LIMIT 1 -- pick one random artist out of top N
于 2018-04-17T01:26:27.793 回答