2015-04-14 2 views
2

Я пытаюсь написать запрос, который получает все статусы User и количество этих статусов. Однако мой подход является очень дорогостоящим и сказывается на базе данных, создавая множество запросов.Улучшение производительности моего запроса ActiveRecord

Я до сих пор новичок в этом, и хочу реорганизовать этот код, чтобы он остановил тайм-аут. Ниже приведен код, который я до сих пор: модель

Пользователь:

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

Purchase модель:

# 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 
+0

Пожалуйста выкладываю методы 'register_for_account',' 'purchase_something' и has_unsubscribed' если не они столбцы базы данных, тогда вы должны это указать. – karlingen

+0

ваше определение schema.rb и модели действительно помогло бы :) – Anko

+0

Есть ли у вас опция добавить к коду? Если возможно, одним из подходов было бы добавить «enum: status» в вашу модель «Пользователь», которая обновляется, когда пользователь регистрирует, покупает или отменяет подписку. Это немного изменит вашу базу данных, но я думаю, что это будет вполне приемлемая оптимизация, а также сделает ваш код намного понятнее. – Drenmi

ответ

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 
+0

Проблемы с этим методом заключаются в том, что у пользователя должен быть только один статус. Если пользователь что-то купил, они должны считаться «купленными», а не «купленными» и «зарегистрированными». –

+0

Это хороший подход с точки зрения производительности, но не эквивалентен исходному коду. Например, это будет считать того, кто зарегистрирован и куплен в обеих категориях, но исходный код будет считать их только в категории «купленные». – Drenmi

+0

@ Энди Квонг Я отредактировал решение. Пожалуйста, проверьте. – Faizan

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 

После этого рельсы убеждается держать purchases_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) 
+0

Или один запрос, если вы говорите такие вещи, как 'sum (case when purchase_counter> 0 then 1 else 0 end) в качестве купленного, сумма (случай, когда purchase_counter = 0 и has_registered, затем 1 else 0 end), как зарегистрировано, ...'. Я бы предположил, что вы все равно будете делать сканирование таблиц, чтобы вы могли сделать только одно. –

0

Вот что я предлагаю,

  1. Добавить столбец user_status типа целого числа в модели пользователя.

  2. Перенос данных в новый столбец user_status из столбцов has_registered и has_unsubscribed и purchase.count. Например, если purchase.count> 0, то их user_status будет 1. Если has_registered, то user_status будет 0 ....

  3. В модели пользователя добавить:

    перечислимую user_status: [: зарегистрировано: куплено,: отписался]

  4. Тогда вы можете запросить и получить счетчик с:

    User.group (: user_status) .Count

Это возвращает что-то вроде {1 => 3, 2 => 1}, где 1 является user_status, в данном случае "купил", и 3 это счет. Таким образом, в этом примере у нас есть 3 пользователя, у которых есть приобретенный статус, и 1 пользователь, который отказался от подписки.

  1. Вы также можете сделать три запроса вместо этого подхода.

    User.purchased.count

    User.registered.count

    User.unregistere.count

Смежные вопросы