Запрос ActiveRecord через таблицу соединений и parent_id - PullRequest
0 голосов
/ 16 ноября 2018

Я изучаю все глубже и глубже 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 может выполнить этот вид запроса через соединительную таблицу и где мы выбираем только родительские навыки?

1 Ответ

0 голосов
/ 16 ноября 2018

Если цель состоит в том, чтобы записать оценку каждого пользователя для данного навыка, то вам нужно поместить ее в таблицу соединений, а не в таблицу пользователей.

 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.

...