0

レポート/メトリクス ページで作業しており、クエリをできる限り最適化する必要があるため、効率のために find_by_sql を使用しています。

私のクエリの 1 つは、カウントといくつかの合計を返す集計関数を実行しています。このクエリの結果をモデルのインスタンス変数に割り当てています。

動作するコードがありますが、コードが怖いです。使用されているメソッドに関する Ruby/Rails の公式ドキュメントを読みましたが、それでも自分のやっていることに何か問題があると思います。

def initialize(args)
  @loans_count           = stats.loans_count
  @total_fees_in_cents   = stats.total_fees_in_cents
  @total_amount_in_cents = stats.total_amount_in_cents
end

def stats
  @stats ||= find_stats
end

def find_stats
  if single_year?
    loans = Loan.find_by_sql(["SELECT count(*) as loans_count, sum(amount) as total_amount_in_cents, sum(fee) as total_fees_in_cents FROM loans WHERE account_id = ? AND year = ? LIMIT 1", @account_id, @year]).first
  else
    loans = Loan.find_by_sql(["SELECT count(*) as loans_count, sum(amount) as total_amount_in_cents, sum(fee) as total_fees_in_cents FROM loans WHERE account_id = ? LIMIT 1", @account_id]).first
  end

  # Store results in OpenStruct for ease of access later on
  OpenStruct.new(
    loans_count: loans.loans_count || 0,
    total_fees_in_cents: loans.total_fees_in_cents || 0,
    total_amount_in_cents: loans.total_amount_in_cents || 0
  )
end

懸念

  1. find_by_sql配列を返すことになっています。一致するものが見つからない場合でも、SQL は常に行を返します (null 値ですが、有効な行)。.firstただし、返された配列を呼び出すべきではない理由はありますか? [].first => nil予想外の場合にぶつかるのが怖いです。
  2. メソッドを使用して結果を「キャッシュ」する私の方法はstats、DB を 1 回だけクエリする適切な方法ですか? 集計データを取得するためだけに、多くのコードとメソッドが必要になるようです。
4

1 に答える 1

1

ダン、

この問題には 2 つの見方があります...

方法 1

ここでは、1 つの答えで両方の質問に対処できると思います (^_-)。重要なのは、恐怖に対処することです。

あなたは主に全体について心配しています[].first => nil。次に、DB の効率が心配です。3 番目に、これをクリーンでリファクタリングしたいと考えています (よかったです!)。

あなたの答え... PostgreSQLにこの作業をさせて、毎回nil以外の答えを返すように強制してください。それを得る?

  1. OpenStructSQL クエリのラベルが同じであるため、必要はありません。
  2. PostgreSQL 関数COALESCE()を使用して、null を強制的にゼロにします。
  3. このすべてをワンライナーにすることもできますが、読みやすくするために少し切り詰めましょう。
  4. これはすでに集計クエリですLIMIT 1。.

コードを書き直してみましょう。

def initialize(args)
  @loans_count           = stats.loans_count
  @total_fees_in_cents   = stats.total_fees_in_cents
  @total_amount_in_cents = stats.total_amount_in_cents
end

def stats
  loans = Loan.select("count(*) as loans_count, COALESCE(sum(amount), 0) as total_amount_in_cents, COALESCE(sum(fee), 0) as total_fees_in_cents").where(account_id: @account_id)

  loans = loans.where(year: @year) if single_year?

  loans.first
end

方法 2

個人的には、DB の効率性について過度に心配していると思います。実際に PSQL サーバーに出力されているものを読み取るために開発/運用ログをいつでも確認できますが、ここで行っていることとほぼ同じであると確信しています。

また、私の記憶が正しければ、データが必要になるまで DB クエリは実際には実行されません。その間、ActiveRecord は QUERY 文字列を準備しているだけです。

.to_inull をゼロに変換します。

コードを書き直してみましょう。

def initialize(args)
  stats = Loan.where(account_id: @account_id)
  stats = stats.where(year: @year) if single_year?
  stats.first

  @loans_count           = stats.count
  @total_fees_in_cents   = stats.sum(:fee).to_i
  @total_amount_in_cents = stats.sum(:amount).to_i
end
于 2014-09-12T18:47:20.083 に答える