【问题标题】:Query for many-to-many association查询多对多关联
【发布时间】:2014-12-26 13:51:35
【问题描述】:

我的项目中有 3 个模型:Users、Games、GameVisits

class User < ActiveRecord::Base
  has_many :game_visits
  has_many :games, through: :game_visits
end

class Game < ActiveRecord::Base
  has_many :game_visits
  has_many :users, through: :game_visits
end

class GameVisit < ActiveRecord::Base
  self.table_name = :users_games
  enum status: [:visited, :not_visited, :unknown]
  belongs_to :user
  belongs_to :game
end

我的前端是用 angularjs 编写的,所以我想要一个函数,它可以为游戏列表返回散列数组,每个散列包含用户名和状态或 nil。数据显示为表格,列 - 玩家,行 - 游戏,单元格 - 玩家在游戏中的存在。 这是此类功能的示例:

def team_json(game_ids = nil)
  game_ids ||= Game.pluck(:id)
  games = Game.find(game_ids)
  users = self.users
  result = []
  games.each do |game|
    record = {name: game.name, date: game.date }
    users_array = []
    users.each do |user|
      users_array << {
        name: user.name,
        status: user.game_visits.find_by_game_id(game.id)
      }
    end
    record[:users] = users_array
    result << record
  end
  result
end

输出:

[{:name=>"game 0", :date=>2014 年 12 月 19 日星期五 11:16:20 UTC +00:00, :users=>[{:name=>"Bob", :status =>无}]}, {:name=>"game 1", :date=>Sat, 2014 年 12 月 20 日 11:16:20 UTC +00:00, :users=>[{:name=>"Bob", :status=>nil} ]}, {:name=>"game 2", :date=>Fri, 19 Dec 2014 11:16:20 UTC +00:00, :users=>[{:name=>"Bob", :status=>nil} ]}]

但是这个函数会为 GameVisits 生成大量的 sql 查询:

 (1.0ms)  SELECT "games"."id" FROM "games"
  Game Load (1.3ms)  SELECT "games".* FROM "games" WHERE "games"."id" IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101)
  User Load (0.6ms)  SELECT "users".* FROM "users" WHERE "users"."team_id" = $1  [["team_id", 1]]
  GameVisit Load (0.6ms)  SELECT  "users_games".* FROM "users_games" WHERE "users_games"."user_id" = $1 AND "users_games"."game_id" = $2 LIMIT 1  [["user_id", 7], ["game_id", 1]]
  GameVisit Load (0.6ms)  SELECT  "users_games".* FROM "users_games" WHERE "users_games"."user_id" = $1 AND "users_games"."game_id" = $2 LIMIT 1  [["user_id", 7], ["game_id", 2]]
  GameVisit Load (0.5ms)  SELECT  "users_games".* FROM "users_games" WHERE "users_games"."user_id" = $1 AND "users_games"."game_id" = $2 LIMIT 1  [["user_id", 7], ["game_id", 3]]
..............

如何优化它?

【问题讨论】:

    标签: sql ruby-on-rails json angularjs has-and-belongs-to-many


    【解决方案1】:

    您的user.game_visits.find_by_game_id(game.id) 是触发所有这些查询的原因。您可以使用急切加载来解决这个问题。

    将users 的分配更改为以下内容:

    users = self.users.includes(:game_visits)
    

    然后将users_array中status的赋值改为这样:

    status: user.game_visits.find{ |gv| gv.game_id == game.id }
    

    以上行使用可枚举的find,而不是ActiveRelation。如果你坚持使用 find_by_game_id 或 AR 的其他 find,它仍然会触发查询。

    另一个优化可能是为game_visits 创建一个哈希,这样您就可以完全绕过find 来执行以下操作:

    user_game_visits_hash = \
      user.game_visits.each_with_object({}) do |gv, hash|
        hash[gv.game_id] = gv
      end
    
    # more code here
    
    status: user_game_visits_hash[game.id]
    

    还有一点:如果您在方法参数中的game_ids 为nil,则您方法的前两行将不必要地触发两个查询,因为您pluck 来自游戏的所有ID 只是为了获取所有游戏。您可以将其更改为:

    games = game_ids ? Game.find(game_ids) : Game.all
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-01-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多