列がどのようにuser_id
してテーブルに表示されたのだろうかsongs
?つまり、曲とユーザーの組み合わせごとに 1 つの行があるということですか? 正規化されたスキーマでは、次の3 つのテーブルで実装された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も使用する必要があります。これは 1 つのステップでは実行できないためです。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
ユーザーあたりの最高の曲を取得します。すべてのユーザーは 1 回しか表示できないため、download_count
.
- その中から高いものを50個選び
downloads_count
ます。
- その中からランダムに8曲選んでください。
大きなテーブルでは、続行する前にすべてのユーザーに最適な行を見つける必要があるため、パフォーマンスが低下します。複数列のインデックスは役に立ちますが、それでもあまり高速ではありません。
CREATE INDEX songs_u_dc_idx ON songs (user_id, downloads_count DESC);
同じ、より速い
user_id
上位の曲の重複が予想通りまれである場合は、トリックを使用できます。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のこの関連する質問の下で同様の問題のクラス全体に適用できるようにしました。