【问题标题】:ActiveRecord query throught join table and parent_idActiveRecord 查询通过连接表和 parent_id
【发布时间】:2018-11-16 15:28:46
【问题描述】:

我正在研究越来越深入的 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 的方式很讨人喜欢,ruby 很神奇。什么是 ActiveRecord 方法可以通过连接表执行这种请求,并且我们只选择父技能?

【问题讨论】:

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


    【解决方案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')
    

    这将返回 Skill 记录中的 ActiveRecord::Relation 以及额外的 .points.user_count 属性。

    【讨论】:

    • 将“父”和“子”技能结合在一起并获得集体聚合要复杂得多,并且很可能需要特定于数据库的解决方案才能有效地执行它。
    • 谢谢你,max,我今天下午试试你的解决方案!
    猜你喜欢
    • 2017-01-27
    • 1970-01-01
    • 2019-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-11
    • 2019-01-26
    • 1970-01-01
    相关资源
    最近更新 更多