我想知道该列是如何user_id
出现在表格中的songs
?这意味着您对于歌曲和用户的每种组合都有一行?在规范化模式中,这将是使用三个表实现的n:m 关系:
song(song_id, ...)
usr(usr_id, ...) -- "user" is a reserved word
download (song_id, user_id, ...) -- implementing the n:m relationship
您问题中的查询会产生不正确的结果。相同的user_id
可以弹出多次。DISTINCT
没有做你似乎期望的事情。您需要DISTINCT ON
或其他一些方法,如聚合或窗口函数。
您还需要使用子查询或CTE,因为这不能一步完成。使用DISTINCT
时不能同时使用ORDER BY random()
,因为排序顺序不能与所规定的顺序不一致DISTINCT
。这个查询当然不是微不足道的。
简单案例,前50首歌曲
如果您很高兴只选择前 50 首歌曲(不知道其中有多少重复的 user_id),那么这个“简单”的案例就可以了:
WITH x AS (
SELECT *
FROM songs
WHERE is_downloadable
ORDER BY downloads_count DESC
LIMIT 50
)
, y AS (
SELECT DISTINCT ON (user_id) *
FROM x
ORDER BY user_id, downloads_count DESC -- pick most popular song per user
-- ORDER BY user_id, random() -- pick random song per user
)
SELECT *
FROM y
ORDER BY random()
LIMIT 8;
- 获得最高的 50 首歌曲
download_count
。用户可以多次出现。
- 为每位用户选择 1 首歌曲。随机或最受欢迎的,在你的问题中没有定义。
user_id
随机选择现在不同的 8 首歌曲。
您只需要一个索引就songs.downloads_count
可以快速:
CREATE INDEX songs_downloads_count_idx ON songs (downloads_count DESC);
具有唯一 user_id 的前 50 首歌曲
WITH x AS (
SELECT DISTINCT ON (user_id) *
FROM songs
WHERE is_downloadable
ORDER BY user_id, downloads_count DESC
)
, y AS (
SELECT *
FROM x
ORDER BY downloads_count DESC
LIMIT 50
)
SELECT *
FROM y
ORDER BY random()
LIMIT 8;
download_count
获取每位用户最高的歌曲。每个用户只能出现一次,所以它必须是最高的一首歌download_count
。
- 从中选出最高的 50 个
downloads_count
。
- 从中随机选择 8 首歌曲。
使用大表,性能会很差,因为您必须先为每个用户找到最佳行,然后才能继续。多列索引会有所帮助,但仍然不会很快:
CREATE INDEX songs_u_dc_idx ON songs (user_id, downloads_count DESC);
一样,更快
如果user_id
热门歌曲中的重复s 可以预见的很少,您可以使用一个技巧。从热门下载中挑选出足够多的下载量,这样具有独特user_id
性的前 50 名肯定就在其中。在这一步之后,像上面一样继续。对于大表,这会快得多,因为可以从索引顶部快速读取前 n 行:
WITH x AS (
SELECT *
FROM songs
WHERE is_downloadable
ORDER BY downloads_count DESC
LIMIT 100 -- adjust to your secure estimate
)
, y AS (
SELECT DISTINCT ON (user_id) *
FROM x
ORDER BY user_id, downloads_count DESC
)
, z AS (
SELECT *
FROM y
ORDER BY downloads_count DESC
LIMIT 50
)
SELECT *
FROM z
ORDER BY random()
LIMIT 8;
上面简单案例的索引足以使其几乎与简单案例一样快。
如果前 100 首“歌曲”中的不同用户少于 50 个,这将达不到要求。
所有查询都应适用于 PostgreSQL 8.4 或更高版本。
如果它必须更快,但是,创建一个包含预选前 50个的物化视图,并定期重写该表或由事件触发。如果你大量使用它并且桌子很大,我会去的。否则不值得开销。
通用的、改进的解决方案
后来,我将这种方法形式化并进一步改进,以适用于 dba.SE的这个相关问题下的一整类类似问题。