如何优化和组合这些低效的数据库查询?

时间:2013-02-12 23:08:08

标签: ruby-on-rails ruby-on-rails-3 rails-activerecord

以下是我的数据库查询:

用户has_many UserFollow(UserFollow是用户模型之间的关系)。 用户has_many照片。 照片has_many PhotoFollow关系(PhotoFollow是用户和照片模型之间的关系)。

@user_list = Array.new
user_followers = UserFollow.where("user_1_id = ?", current_user.id).includes(:follower)
user_followers.each do |f|
  @user_list << f.user
end

photos = Photo.where("user_id = ?", current_user.id).includes(:follow_relationships => [:photo])
photos.each do |p|
  p.follow_relationships.each do |f|
    @user_list << f.user if !@user_list.include? f.user
  end
end

if @user_list.size < 150
  users = User.where("verified = ? and first_name IS NOT NULL and last_name IS NOT NULL", true).includes(:solutions).limit(150 - @user_list.size)
  users.each do |u|
    @user_list << u if !@user_list.include? u
  end
end

所有这些显然需要花费大量时间。使用包括帮助,但我想知道是否有一些方法可以更有效地执行这组操作。

谢谢, 林檎

6 个答案:

答案 0 :(得分:2)

首先附加您的关联。

class User < ActiveRecord::Base
  has_many :follows_to_user, :class_name => 'UserFollow', :foreign_key => 'follower_id'
  has_many :follows_to_photo, :class_name => 'PhotoFollow', :foreign_key => 'user_id' # if you don't have it now
end

现在,前两个查询可以在一个SQL查询中更加优雅,返回 AR :: Relation 范围。

@user_list = User.includes(:follows_to_user => {}, :follows_to_photo => {:photo => {}}).where(["user_follows.user_1_id = :user_id OR photos.user_id = :user_id", :user_id => current_user.id])

大约150 ... [更新]

当然,您最好实现附加条件的前一个SQL语句和 UNION 语句(仅使用 SQL语法)的逻辑,该语句应返回AR ::关系会更快一点。但是你可以保持懒惰而把它留在红宝石中,虽然它会返回数组

if (count = @user_list.count) && count < 150 # run COUNT just once and store value into local variable
  @user_list |= User.where("verified = ? and first_name IS NOT NULL and last_name IS NOT NULL", true).includes(:solutions).limit(150 - count)
end

答案 1 :(得分:1)

我的答案必须有一个更好的方法,但是为什么不包括:user,因为你在迭代查询时加载它们?

@user_list = Array.new
user_followers = UserFollow.includes(:user).where("user_1_id = ?", current_user.id)
# why did you include followers?
user_followers.each do |f|
  @user_list << f.user
end

photos = Photo.includes(follow_relationships: { photo: :user }).where("user_id = ?", current_user.id)
photos.each do |p|
  p.follow_relationships.each do |f|
    @user_list << f.user unless @user_list.include? f.user
  end
end

if @user_list.size < 150
  users = User.where("verified = ? and first_name IS NOT NULL and last_name IS NOT NULL", true).limit(150 - @user_list.size)
  # why did you include solutions?
  users.each do |u|
    @user_list << u unless @user_list.include? u
  end
end

也许这更快,我不确定:

@follower_ids = UserFollow.where("user_1_id = ?", current_user.id).pluck(:user_1_id).uniq

@photo_ids = Photo.joins(follow_relationships: :photo)
@photo_ids = @photo_ids.where("user_id = ? and user_id not in (?)", current_user.id, @follower_ids)
@photo_ids = @photo_ids.pluck(:user_id).uniq

@followers = User.where("id in (?)", @follower_ids)
@photo_users = User.where("id in (?) and not in (?)", @photo_ids, @follower_ids) 

@array_size = (@follower_ids + @photo_ids).size
if @array_size < 150
  @users = User.where("verified = ? and first_name is not null and last_name is not null", true)
  @users = @users.where("id not in (?)", @photo_ids + @follower_ids).limit(150 - @array_size)
else
  @users = []
end

@final_array = @followers + @photo_users + @users

我没有测试过这是否有效,或者它是否更快。它有更多的数据库查询但迭代次数更少。

更新

如果您向用户模型添加了另一列,并使用1到3的值进行更新,具体取决于他们是否有关注者,照片或任何内容。

然后您需要做的就是:

# in User model
def self.valid_users
  where("verified = ? and first_name is not null and last_name is not null", true)
end


@users = User.valid_users.order("sortable ASC").limit(150)

答案 2 :(得分:1)

查看您的代码,您计划获取@user_list的用户列表。您可以先建立用户ID列表,这样我们就不会创建不必要的AR对象

第一个代码

@user_list = Array.new
user_followers = UserFollow.where("user_1_id = ?", current_user.id).includes(:follower)
user_followers.each do |f|
  @user_list << f.user
end

可以更改为

# assuming you have a user_id column on user_follows table
user_ids = User.joins(:user_follows).where(user_follows: { user_1_id: current_user.id })
  .uniq.pluck('user_follows.user_id')

第二个代码

photos = Photo.where("user_id = ?", current_user.id).includes(:follow_relationships =>[:photo])
photos.each do |p|
  p.follow_relationships.each do |f|
    @user_list << f.user if !@user_list.include? f.user
  end
end

可以更改为

user_ids += Photo.where(user_id: current_user.id).joins(follow_relationships: :photo)
  .uniq.pluck('follow_relationships.user_id')

第三个代码

if @user_list.size < 150
  users = User.where("verified = ? and first_name IS NOT NULL and last_name IS NOT NULL", true).includes(:solutions).limit(150 - @user_list.size)
  users.each do |u|
    @user_list << u if !@user_list.include? u
  end
end

可以更改为

user_ids += users = User.where(verified: true)
  .where('first_name IS NOT NULL AND last_name IS NOT NULL')
  .where('id NOT IN (?)', user_ids)
  .limit(150 - user_ids.size).pluck(:id)

然后,您可以使用user_ids

获取所有用户
@user_list = User.where(id: user_ids)

答案 3 :(得分:0)

这只是猜测。

@user_list = current_user.followers.where("verified = ? and first_name IS NOT NULL and last_name IS NOT NULL", true).includes(:solutions, :photos => {:follow_relationships}).limit(150)

总而言之,由于单纯的复杂性,我认为这一切都是错误的。如果你无法清楚地了解它的作用,那么你应该重新开始。

答案 4 :(得分:0)

您是否为表设置了表索引?

如果还没有,请为所有外键和需要包含在条件中的列设置它们。在您的数据库迁移脚本中(当然,使用正确的表和列名称)。它们将加快您的查询速度,特别是如果您有大型数据集:

add_index :user_follows, :follower_id
add_index :user_follows, :followed_id

add_index :photos, :user_id

add_index :photo_follow_relationships, :photo_id
add_index :photo_follow_relationships, :follower_id

add_index :users, :verified
add_index :users, :first_name
add_index :users, :last_name

另外,一些意见和建议:

# ... [SNIP] Query and add to user list.
user_followers = []  # You are not actually using the UserFollow records. Unload
                     # them from memory. Otherwise, they will be stored until we
                     # leave the block.

# There is no need to fetch other Photo data here, and there is no need to load
# :photo for FollowRelationship. But there is a need to load :user.
photos = Photo.where(:user_id => current_user.id).select('photos.id').
    includes(:follow_relationships => [:user])
# ... [SNIP] Add to user list.
photos = []  # Unload photos.

# ... [SNIP] Your condition.
# There is no need to load :solutions for the users here.
users = User.where("verified = ? and first_name IS NOT NULL and last_name IS NOT NULL", true).limit(150 - @user_list.size)
# ... [SNIP] Add to user list.

当然,如果您重构代码也会更好,就像在mind blank的一些建议中那样。您也可以使用has_many :through关联来清理控制器。

答案 5 :(得分:0)

将以下关系添加到模型中

class User  
  has_many :user_follows
  has_many :inverse_user_follows, :class_name=>'UserFollow', :foreign_key=>:follower_id

  # followers for user
  has_many :followers, :through => :user_follows
  # users followed by user
  has_many :followed_users, :through => :inverse_user_follows, :source => :user


  # photos created by user
  has_many :photos

  has_many :photo_user_follows, :through => :photos, :source => :user_follows

  # followers for user's photo

  has_many :photo_followers, :through => :photo_user_follows, :source => :follower


  has_many :photo_follows
  # photos followed by user
  has_many :followed_photos, :source => :photo, :through => :photo_follows

end  

class UserFollow
  # index user_id and follower_id columns
  belongs_to :user
  belongs_to :follower, :class_name => "User"
end

照片相关模型

class Photo
  # index user_id column
  belongs_to :user
  has_many   :photo_follows
  has_many   :followers, :through => :photo_follows
end

class PhotoFollow
  # index photo_id and follower_id columns
  belongs_to :photo
  belongs_to :follower, :class_name => "User"
end  

现在,您可以获得当前用户关注者或当前用户的照片关注者或活跃用户的用户..

user_ids = current_user.follower_ids | current_user.photo_follower_ids

User.where("ids IN (?) OR 
   ( verified = ? and first_name IS NOT NULL and last_name IS NOT NULL )", 
  user_ids, true).limit(150)