Techioz Blog

Ruby on Rails で日付ごとに評価データをグループ化する最良かつ最もパフォーマンスの高い方法は何ですか?

概要

タスク テーブルがあり、完了したタスクごとに、ユーザーはタスクを評価できます (1 から 5)。

タスク テーブルは次のようになります。

tasks
  id: integer
  closed_at: datetime
  rating: integer

次に、次のような形式で日付ごとの評価を取得する API 応答を構築する必要があります。

{ 
  "2023-02-23": [ 
    { rating: 1, count: 1 },
    { rating: 2, count: 5 },
    { rating: 3, count: 2 },
    { rating: 4, count: 30 },
    { rating: 5, count: 15}
  ], 
  "2023-02-24": [
    { rating: 1, count: 2 },
    ...
  ]
}

これは単なる例です。私が望むのは、日付ごとのすべての評価数を取得することです。 上の例では、2023 年 2 月 23 日に完了したタスクが 1 つあり、評価が 1、同じ日に完了したタスクが 5 つあり、評価が 2 です…

Rubyでそれを行う最善の方法は何でしょうか? それに対する SQL ソリューションを見つけようとしていましたが、それが可能かどうかさえわかりません。

それ以外の場合は、すべてのタスクを取得した後で Ruby コードのみを使用する最もパフォーマンスの高い方法を喜んで採用します。

私はすでにこの実用的なソリューションを持っています:

grouped_tasks_per_date = Task.all.group_by { |t| t.closed_at.to_date.to_s }

data = {}

grouped_tasks_per_date.each do |date, tasks|
  tasks_per_rating = tasks.group_by{|t| t.rating}

  data[date] = tasks_per_rating.map {|k, v| [k, v.length] }.to_h
end

data
=> {"2020-12-01"=>{1=>1, 2=>5, 3=>2, 4=>30, 5=>15}}

私が受け取った上記の形式はまったく問題ありませんが、私の好みに合わないループを多用しすぎています…それで…コードを改善するアイデアやその他の方法がある場合は、アイデアを共有してください。

ありがとう!

解決策

Model.all を実行したことがある場合は、テーブルが常に非常に小さいことを知っているか、より良い方法があるかのどちらかです。

SQL でグループ化、カウント、並べ替えを実行します…

select date_trunc('day', closed_at), rating, count(*)
from foo
group by 1, 2
order by 1, 2

1 と 2 は、最初と 2 番目に選択された列、date_trunc(‘day’, Closed_at) と Rating を参照します。

Closed_at を日付に正確に変換する方法は、使用している SQL データベースによって異なります。 Postgres の場合は date_trunc、MySQL の場合は day。

デモンストレーション。

同等の Rails では、group、order、count が使用されます。

grouped = Task
  .select("datetrunc('day', closed_at)", rating)
  .group(1, 2)
  .order(1, 2)
  .count

通常と同じように、特定の日に限定することもできます。

これにより、次のような配列のハッシュが得られます。

{[“2023-02-23”, 1] => 1, [“2023-02-23”, 2] => 5, …}