0

在报告/指标页面上工作,我需要尽可能优化查询,因此我使用 find_by_sql 来提高效率。

我的一个查询是做一些聚合函数,我返回一个计数和一些总和。我将此查询的结果分配给模型的实例变量。

我有工作代码,但代码让我害怕。我已经阅读了有关使用方法的官方 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 将始终返回一行,即使没有找到匹配项(空值,但有效行)。但是,有没有理由我不应该调用.first返回的数组?我害怕[].first => nil在我没有预料到的情况下被击中。
  2. 我使用该stats方法“缓存”结果的方法是仅查询 DB 1 次的适当方法吗?似乎有很多代码和方法只是为了获取一些聚合数据。
4

1 回答 1

1

担,

有两种方法来看待这个问题......

方法一

我想我可以在这里用一个答案来解决这两个问题(^_-),关键是解决你的恐惧。

你主要担心的是整体[].first => nil。其次,您担心数据库效率。第三,你想让这个干净并重新考虑(对你有好处!)。

您的答案...让 PostgreSQL 为您完成这项工作,并强制它每次都返回非零答案。得到它?

  1. 不需要,OpenStruct因为 SQL 查询中的标签是相同的。
  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

方法二

我个人认为您过度担心数据库效率问题。您始终可以查看您的开发/生产日志以读取实际输出到 PSQL 服务器的内容,但我很确定它与您在这里所做的事情接近。

另外,如果我没记错的话,数据库查询实际上并没有执行,直到你想要数据。在此期间,ActiveRecord 只是在准备 QUERY 字符串。

.to_i会将您的 null 转换为零。

让我们重写代码:

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 回答