查询多对多关联

时间:2014-12-26 13:51:36

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

我的项目中有3个模型:用户,游戏,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编写的,所以我想要一个函数,对于游戏列表返回哈希数组,每个哈希包含user和status或nil的名称。数据显示为表格,cols - 玩家,行 - 游戏,单元格 - 玩家在游戏中的存在。 这是这种功能的例子:

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 =&gt;&#34;游戏0&#34;,:date =&gt;星期五,2014年12月19日11:16:20 UTC +00:00,:users =&gt; [{:name =&gt;&#34; Bob&#34;,:status =&gt; nil}]},  {:name =&gt;&#34;游戏1&#34;,:date =&gt;星期六,2014年12月20日11:16:20 UTC +00:00,:users =&gt; [{:name =&gt;& #34; Bob&#34;,:status =&gt; nil}]},  {:name =&gt;&#34;游戏2&#34;,:date =&gt;星期五,2014年12月19日11:16:20 UTC +00:00,:users =&gt; [{:name =&gt;& #34; Bob&#34;,:status =&gt; 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]]
..............

我该如何优化它?

1 个答案:

答案 0 :(得分:1)

您的user.game_visits.find_by_game_id(game.id)正在触发所有这些查询。您可以使用预先加载来解决此问题。

users的作业更改为以下内容:

users = self.users.includes(:game_visits)

然后将status中的users_array的值分配更改为:

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

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

另一个优化可能是为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