通过连接表和parent_id进行ActiveRecord查询

时间:2018-11-15 21:28:59

标签: sql ruby-on-rails relational-database

我正在研究越来越深入的RoRails,并且停留在我们提供给我的数据上,以重现这个可爱的框架。 首先让我向您展示我的表格和模型助理。

  create_table "skills", force: :cascade do |t|
    t.string "name"
    t.bigint "parent_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["parent_id"], name: "index_skills_on_parent_id"
  end

  create_table "skills_users", id: false, force: :cascade do |t|
    t.bigint "skill_id", null: false
    t.bigint "user_id", null: false
    t.index ["skill_id", "user_id"], name: "index_skills_users_on_skill_id_and_user_id"
  end

  create_table "users", force: :cascade do |t|
    t.integer "points"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

和型号:

class Skill < ApplicationRecord
  validates :name, presence: true
  has_many :children, class_name: "Skill", foreign_key: :parent_id
  belongs_to :parent, class_name: "Skill", foreign_key: :parent_id, optional: true
  has_and_belongs_to_many :users
end

class User < ApplicationRecord
  validates :points, presence: true
  has_and_belongs_to_many :skills
end

目标是对“技能”进行分类,获取“技能”子级并在每个父“技能”中计算用户的积分。这是数据示例

SKILLS
+-----------------------+
|ID|NAME      |PARENT_ID|
+-----------------------+
|1 |Football  |         |
+-----------------------+
|2 |Basketball|         |
+-----------------------+
|3 |Foot      |1        |
+-----------------------+
|4 |Basket    |2        |
+-----------------------+
|5 |Soccer    |1        |
+-----------------------+

SKILLS_USERS
+-------------------+
|ID|SKILL_ID|USER_ID|
+-------------------+
|1 |1       |1      | 
+-------------------+
|2 |1       |2      | 
+-------------------+
|3 |3       |3      | 
+-------------------+
|4 |2       |4      | 
+-------------------+
|5 |5       |5      |
+-------------------+

USERS
+---------+
|ID|POINTS|
+---------+
|1 |100   |
+---------+
|2 |200   |
+---------+
|3 |100   |
+---------+
|4 |50    |
+---------+
|5 |10    |
+---------+

预期的请求应如下所示:

+--------------------------------+
|ID|NAME      |POINTS|USERS_COUNT|
+--------------------------------+
|1 |Football  |410   |4          |
+--------------------------------+
|2 |Basketball|50    |1          |
+--------------------------------+

我首先尝试使用以下查询在纯sql中回答它:

SELECT id , Name , count ( points)  as POINTS , count (USER_ID ) as USERS_COUNT 
FROM SKILLS 
INNER JOIN SKILLS as SK ON id = SK.parent_id 
INNER JOIN SKILLS_USERS AS su ON su.skill_id = id 
INNER JOIN USERS AS User ON SU.USER_ID = User.id

但是似乎我在某个地方错了。

我认为ActiveRecord方式很有趣,红宝石是魔术。 ActiveRecord对联接表进行这种请求的方法是什么?我们仅选择父级Skills?

1 个答案:

答案 0 :(得分:1)

如果要记录给定技能的每个用户的分数,则需要将其放在联接表上,而不是用户表上。

 create_table "skills", force: :cascade do |t|
    t.string "name"
    t.bigint "parent_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["parent_id"], name: "index_skills_on_parent_id"
  end

  create_table "user_skills", force: :cascade do |t|
    t.bigint "skill_id", null: false
    t.bigint "user_id", null: false
    t.integer "points"
    t.index ["skill_id", "user_id"], name: "index_skills_users_on_skill_id_and_user_id"
  end

  create_table "users", force: :cascade do |t|
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

您想使用has_many through:而不是has_and_belongs_to_many来建立关联,这不允许您直接查询联接表或访问该points列。

class Skill < ApplicationRecord
  has_many :user_skills
  has_many :users, through: :user_skills
end

class UserSkill < ApplicationRecord
  belongs_to :user
  belongs_to :skill
end

class User < ApplicationRecord
  has_many :user_skills
  has_many :users, through: :user_skills
end

您的查询也比需要的复杂得多,但仍未达到要求。您可以通过查询技能表并加入user_skills来获得所需的结果。

SELECT skills.id, skills.name, 
  SUM(user_skills.points) AS points, 
  count(user_skills.user_id) AS user_count 
FROM "skills" 
INNER JOIN "user_skills" ON "user_skills"."skill_id" = "skills"."id" 
GROUP BY skills.id

我们可以在ActiveRecord中执行以下操作:

Skill.joins(:user_skills)
     .select('skills.id, skills.name, SUM(user_skills.points) AS points, COUNT(user_skills.user_id) AS user_count')
     .group('skills.id')

这将返回ActiveRecord::Relation条记录中的Skill条记录,并附带其他.points.user_count属性。