[英]Efficient way to select one from each category - Rails
我正在開發一個簡單的應用程序返回的隨機選擇exercises
,每一個bodypart
。
bodypart
是Exercise
模型上的索引enum
列。 DB是PostgreSQL。
以下實現了我想要的結果,但感覺非常低效(每個bodypart
擊中db一次):
BODYPARTS = %w(legs core chest back shoulders).freeze
@exercises = BODYPARTS.map do |bp|
Exercise.public_send(bp).sample
end.shuffle
因此,這為每個exercise
bodypart
提供隨機exercise
,並在最后混合順序。
我還可以將所有練習存儲在內存中並從中選擇; 但是,我想這會有可怕的擴展(目前只有十幾個種子記錄)。
@exercises = Exercise.all
BODYPARTS.map do |bp|
@exercises.select { |e| e[:bodypart] == bp }.sample
end.shuffle
對這些進行基准測試表明, select
方法在小范圍內更有效:
Queries: 0.072902 0.020728 0.093630 ( 0.088008)
Select: 0.000962 0.000225 0.001187 ( 0.001113)
MrYoshiji's answer: 0.000072 0.000008 0.000080 ( 0.000072)
我的問題是,是否有一種有效的方法來實現這一輸出,如果是,那么這種方法可能是什么樣子。 理想情況下,我想將其保留為單個數據庫查詢。
很高興使用ActiveRecord或直接在SQL中撰寫。 任何想法都非常感激。
從我的評論中,你應該能夠做到(感謝PostgreSQL的DISTINCT ON
):
Exercise.select('distinct on (bodypart) *')
.order('bodypart, random()')
Postgres的' DISTINCT ON
非常方便, 性能通常也很好 - 對於許多不同的身體部位,每個部分都很少 。 但是只有少數不同的bodypart
值,每個都有很多行(大表 - 和你的用例),有很多優秀的查詢技術。
在這種情況下,這將大大加快:
SELECT e.*
FROM unnest(enum_range(null::bodypart)) b(bodypart)
CROSS JOIN LATERAL (
SELECT *
FROM exercises
WHERE bodypart = b.bodypart
-- ORDER BY ??? -- for a deterministic pick
LIMIT 1 -- arbitrary pick!
) e;
假設bodypart
是枚舉的名稱以及表列。
enum_range
是一個枚舉支持函數 (引用手冊):
返回有序數組中輸入枚舉類型的所有值
我不需要它並為每個值運行一個LATERAL
子查詢,當使用正確的索引支持時,它非常快。 查詢技術和所需索引的詳細說明(關注章節“2a.LATERAL join” ):
對於每個bodypart
的任意行,一個簡單的exercises(bodypart)
索引exercises(bodypart)
就可以完成這項工作。 但是你可以使用正確的多列索引和匹配的ORDER BY
子句以及幾乎相同的性能來獲得像“最新條目”這樣的確定性選擇。 有關:
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.