【问题标题】:Efficient way to select one from each category - Rails从每个类别中选择一个的有效方法 - Rails
【发布时间】:2018-02-26 16:30:27
【问题描述】:

我正在开发一个简单的应用程序来返回随机选择的exercises,每个bodypart 一个。

bodypartExercise 模型上的索引 enum 列。 DB 是 PostgreSQL。

以下实现了我想要的结果,但感觉非常低效(每个bodypart 访问一次数据库):

BODYPARTS = %w(legs core chest back shoulders).freeze

@exercises = BODYPARTS.map do |bp|
  Exercise.public_send(bp).sample
end.shuffle

因此,这会为每个 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 中编写此内容。任何想法都非常感谢。

【问题讨论】:

  • 尝试以下操作:Exercise.group(:bodypart).select('distinct on (bodypart) *').order('bodypart, random());
  • Star @MrYoshiji - 我必须将:id 添加到组子句中,即Exercise.group(:bodypart, :id).select('distinct on (bodypart) *').order('bodypart, random()') 以克服以下错误:ActiveRecord::StatementInvalid: PG::GroupingError: ERROR: column "exercises.id" must appear in the GROUP BY clause or be used in an aggregate function。我会将它插入基准代码并查看它的比较情况,但这看起来只是一张票 - 如果你弹出一个很高兴接受你的答案!

标签: sql ruby-on-rails ruby postgresql activerecord


【解决方案1】:

根据我的评论,您应该可以做到(感谢 PostgreSQL 的DISTINCT ON):

Exercise.select('distinct on (bodypart) *')
        .order('bodypart, random()')

【讨论】:

  • 再次感谢 - 我已将您的方法添加到问题中的基准测试中,而且速度也非常快。
  • @SRack 数据库总是更擅长处理这类事情 ;)
  • 是的,那是我的希望 :) 刚刚玩了一场——如果我错了,请纠正我——你实际上可以通过省略分组来剃掉一点:Exercise.select('distinct on (bodypart) *').order('bodypart, RANDOM()')
【解决方案2】:

Postgres 的DISTINCT ON 非常方便,performance is typically great, too - 用于许多 个不同的正文部分,每个部分很少 行。但是对于只有 few 个不同的 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 是一个enum support function(引用手册):

在有序数组中返回输入枚举类型的所有值

我取消嵌套它并为每个值运行一个LATERAL 子查询,当支持正确的索引时,它非常快。查询技术和所需索引的详细解释(关注章节“2a. LATERAL join”):

对于每个 bodypart 的任意一行,exercises(bodypart) 上的简单索引就可以完成这项工作。但是您可以使用正确的多列索引和匹配的ORDER BY 子句以及几乎相同的性能来进行确定性选择,例如“最新条目”。 相关:

【讨论】:

  • 无法与之竞争,+1
  • 这看起来棒极了,我肯定会做推荐的阅读; 然而,我无法让它工作。是否由于我的问题含糊不清,bodypart 列是 Rails 枚举列,在数据库中作为整数下降?不管@ErwinBrandstetter,非常感谢您的回答+1。
  • @SRack:我一直在假设一个实际的Postgres enum 数据类型。但这不是答案的核心。 任何获取不同身体部位列表的廉价方法都很好......
  • 太好了 - 明天会好好看看这个。非常感谢您的回答。
  • 如果您在数据库中的某处没有明确的身体部位列表(您可能有),您甚至可以没有它。参考chapter "1. No separate table with unique users" in the related answer.
猜你喜欢
  • 2012-09-13
  • 1970-01-01
  • 1970-01-01
  • 2012-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多