【问题标题】:How to find a record based on a full match of associations如何根据关联的完全匹配查找记录
【发布时间】:2017-11-20 16:35:20
【问题描述】:

我正在制作一个用户可以完成任务的应用程序。这些任务有一个关联RequirementsUser 有一个关联 Qualifications

两者通过共享QualificationCategory连接

Task 有很多要求,User 有很多Qualifications

需求的结构如下:

Requirement
- ID
- qualification_category_id
- task_type_id
- points_required

资格的结构如下:

Qualification:
- ID
- qualification_category_id
- user_id
- points

问题是这样的:

对于用户,我需要找到允许他完成的所有任务。所以我想从数据库中选择task_type.requirementsuser.qualifications 完全匹配的所有任务

因此,对于每个任务,检查要求是否与基于qualification_category 的任何用户资格相匹配,并且对于每个要求,检查相应的用户资格点是否高于或等于所需的点。

模型

class Requirement < ApplicationRecord
  belongs_to :qualification_category
  belongs_to :task_type, inverse_of: :requirements
end

class Task < ApplicationRecord
  belongs_to :task_type
  belongs_to :data_sourceable, polymorphic: true, optional: true
  belongs_to :user_id, optional: true
  belongs_to :solution, class_name: 'Hypothesis', optional: true
  belongs_to :payment_period, optional: true

  has_many :worker_groups, through: :task_type
  has_many :requirements, through: :task_type
end

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :timeoutable, :confirmable, :lastseenable,
         :recoverable, :rememberable, :trackable, :validatable, :registerable

  has_and_belongs_to_many :roles
  has_and_belongs_to_many :worker_groups
  has_many :payment_periods
  has_many :qualifications, inverse_of: :user
  has_many :qualification_categories, through: :qualifications
end

class Qualification < ApplicationRecord
  belongs_to :qualification_category
  belongs_to :user, inverse_of: :qualifications
end

我如何编写我的 MySQL 查询或 ARel 语句来完成此操作。该应用程序将容纳数百万个任务,因此在 ruby​​ 中这样做是不可取的。

db 小提琴:https://www.db-fiddle.com/f/2jBx1gQhzqn1hYS5xD82n/0

【问题讨论】:

    标签: mysql ruby-on-rails activerecord associations ruby-on-rails-5


    【解决方案1】:

    要返回特定user 符合条件的所有task(基于匹配单个限定条件)MySQL 查询,如下所示:

    SELECT t.id
      FROM task t 
      JOIN task_type tt
        ON tt.id = t.task_type_id
      JOIN requirement r
        ON r.task_type_id = tt.id
      JOIN qualification q
        ON q.qualification_category_id = r.qualification_category_id
       AND q.points                   >= r.points_required
      JOIN user u
        ON u.id = q.user_id
     WHERE u.id = ?
    

    这可能会返回“重复”;所以我们可以添加一个 GROUP BY 子句,或者将连接操作更改为 EXISTS 谓词。


    为了只匹配用户资格满足所有要求的任务,我们需要一些不同的东西:

    SELECT t.id
      FROM task t
      JOIN task_type tt
        ON tt.id = t.task_type_id
     WHERE EXISTS
           ( SELECT 1
               FROM requirement r
               JOIN qualification q
                 ON q.qualification_category_id = r.qualification_category_id
                AND q.points                   >= r.points_required
              WHERE r.task_type_id = tt.id
                AND q.user_id = ?                                 -- specific user
           )
       AND NOT EXISTS
           ( SELECT 1
               FROM requirement r
               LEFT
               JOIN qualification q
                 ON q.qualification_category_id = r.qualification_category_id
                AND q.user_id = ?                                 -- specific user
              WHERE r.task_type_id = tt.id
                AND ( q.user_id IS NULL OR q.points < r.points_required )
           )
    

    EXISTS 位检查用户是否具有至少一项符合要求的资格。

    NOT EXISTS 位检查用户没有匹配的qualification 的任务类别是否有任何requirement


    再想一想,我认为EXISTS部分可以省略,我们只需要检查NOT EXISTS...有没有requirement没有被qualification统计。

    SELECT t.id
      FROM task t
      JOIN task_type tt
        ON tt.id = t.task_type_id
     WHERE NOT EXISTS
           ( SELECT 1
               FROM requirement r
               LEFT
               JOIN qualification q
                 ON q.qualification_category_id = r.qualification_category_id
                AND q.user_id = ?                                 -- specific user
              WHERE r.task_type_id = tt.id
                AND ( q.user_id IS NULL OR q.points < r.points_required )
           )
    

    (假设qualification.pointsrequirement.points_required 不为空,因此不等式比较将评估为TRUE。如果我们有可能为NULL 值,我们将需要适当地处理它们。)

    【讨论】:

    • Unknown column 'r.task_type' in 'where clause我更新为task_type_id对吗?
    • 应该是r.task_type_id。我没有要测试的一组表格或示例数据,所以你得到的只是一些想法。考虑使用表和示例数据设置 SQLFiddle sqlfiddle.com... 我猜测某些列名,猜测 pointspoints_required 的数据类型等。
    • db-fiddle.com/f/2jBx1gQhzqn1hYS5xD82n/0这个链接有种子数据和结构
    • 我认为将点与 points_required 进行比较的条件需要移动到 WHERE 子句而不是外连接的 ON 子句(我的错)。答案中的 SQL 会随着该更改而更新。
    【解决方案2】:

    补充斯宾塞的答案,对于那些希望通过范围和 ARel 将其合并到他们的 Rails 项目中的人。

    scope :qualified_for, ->(user) {
        task_types = TaskType.arel_table
        requirements = Requirement.arel_table
        qualifications = Qualification.arel_table
        requirements_conditions = requirements[:task_type_id].eq(task_types[:id])
                                      .and(
                                          qualifications[:user_id].eq(nil).or(qualifications[:points].lt(requirements[:points_required]))
                                      )
    
        requirement_query = Requirement.select(1).joins("LEFT
                   JOIN qualifications
                     ON qualifications.qualification_category_id = requirements.qualification_category_id
                    AND qualifications.user_id = #{user.id}").where(requirements_conditions).exists.not
    
        joins(:task_type).where(requirement_query)
      }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-09-24
      • 2018-08-15
      • 2016-08-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多