提高ActiveRecord查询的性能

时间:2015-04-14 06:29:24

标签: ruby-on-rails postgresql activerecord

我正在尝试编写一个获取所有User状态的查询,以及这些状态的计数。然而,我的方法非常昂贵,并且通过大量查询对数据库造成了损失。

我还是新手,想要重构这段代码,以便停止计时。以下是我到目前为止的代码:

用户模型:

# app/models/user.rb
class User < ApplicationActiveRecordBase
  has_many :purchases
end

购买型号:

# app/models/purchase.rb
class Purchase < ApplicationActiveRecordBase
  belongs_to :user
end

架构:

# app/db/schema.rb
create_table "users", force: :cascade do |t|
  t.boolean  "has_registered"
  t.boolean "has_unsubscribed"
end

create_table "purchases", force: :cascade do |t|
  t.string "item"
  t.integer "price"
  t.integer "user_id"
end

我想要优化的代码:

status = Hash.new(0)
User.find_each do |user|
  status[check_user_status(user)] += 1
end

def check_user_status(user)
  if user.purchases.count > 0
    'purchased'
  elsif user.has_registered?
    'registered'
  elsif user.has_unsubscribed?
    'unsubscribed'
  end
end

3 个答案:

答案 0 :(得分:2)

我相信你可以用3个查询来解决这个问题,如果你刚刚做了以下的话,就不会进行迭代:

首先,您必须为每个用户的购买保留一个计数器缓存。幸运的是,Rails已经拥有了一个非常漂亮和优雅的方式。看看http://railscasts.com/episodes/23-counter-cache-column

所以你必须改变模型:

# app/models/purchase.rb
class Purchase < ApplicationActiveRecordBase
  belongs_to :user, counter_cache: :purchases_count
end

然后创建迁移以创建计数器列:

# db/migrate/000_add_purchases_counter_to_user.rb
def self.up
  add_column :users, :purchases_count, :integer, :default => 0

  User.reset_column_information
  User.all.each { |u| User.reset_counters u.id, :purchases }
end

def self.down
  remove_column :users, :purchases_count
end

此追踪后,确保在创建新购买的已删除邮件时保持purchase_count更新。

现在您可以使用这3个查询来检索所需的数据:

purchased = User.count_by_sql("SELECT COUNT(*) FROM USERS WHERE purchases_counter > 0")
registered = User.count_by_sql("SELECT COUNT(*) FROM USERS WHERE purchases_counter = 0 AND has_registered=?", true)
unsubscribed = User.count_by_sql("SELECT COUNT(*) FROM USERS WHERE purchases_counter = 0 AND has_unsubscribed=?", true)

答案 1 :(得分:0)

分别执行

ids =   Purchase.unscoped.uniq.pluck(:user_id)
status[:purchased] = ids.count 
ids = User.where.not(id: ids).where(has_registered: true).pluck(:id)
status[:registered] = ids.count
status[:unsubscribed] = User.where.not(id: ids).where(has_unsubscribed: true).count

答案 2 :(得分:0)

这是我的建议,

  1. 将一个integer类型的user_status列添加到您的用户模型中。

  2. 将数据从has_registered和has_unsubscribed列以及purchases.count迁移到新的user_status列。例如,如果purchases.count> 0然后他们的user_status将是1.如果has_registered,则user_status为0 ....

  3. 在您的用户模型中添加:

    enum user_status:[:已注册,:已购买,:取消订阅]

  4. 然后您可以使用以下方式查询并获取计数:

    User.group(:user_status).Count之间的

  5. 这将返回类似{1 =&gt; 3,2 =&gt; 1}的内容,其中1是user_status,在这种情况下是“purchase”,3是计数。因此,在此示例中,我们有3个已购买状态的用户和1个已取消订阅的用户。

    1. 您也可以使用此方法进行三次查询。

      User.purchased.count

      User.registered.count

      User.unregistere.count