【问题标题】:ActiveRecords Join with conditions: how to join on the same table with different sets of conditions using squeel sifters?ActiveRecord Join with conditions:如何使用 squeal sifters 在同一张表上加入不同的条件集?
【发布时间】:2013-12-22 13:07:54
【问题描述】:

我有两个活动记录类,用户和用户技能。 UserSkill有一个squeel sifter,如下图:

class User < ActiveRecord::Base
  has_many   :user_skills
end

class UserSkill < ActiveRecord::Base
  belongs_to :user
  belongs_to :skill

  sifter :with_skill_id_and_value_of_at_least do |params|
    if params[1].present?  
      skill_id.eq(params[0]) & value.gteq(params[1])
    else
      skill_id.eq(params[0])
    end
  end
end

我现在正在执行搜索,并且在控制器中有这个:

  def search
    users = User.skip_user(current_user)

    if params[:user_skills].present?
      params[:user_skills].each do |user_skill|
        #user_skill is an array where [0] is the id, and [1] the minimum value. 
        #if [1] is empty, it will default to 0 in the sifter

        users = users.joins(:user_skills).where do
         {
            user_skills: sift(:with_skill_id_and_value_of_at_least, user_skill)
         }
        end
      end 
    end   
  end

我的问题是,基本上,当应用关系时,我最终得到以下 sql:

SELECT "users".* FROM "users" 
  INNER JOIN "user_skills" ON "user_skills"."user_id" = "users"."id" 
  WHERE "user_skills"."skill_id" = 10 
  AND (("user_skills"."skill_id" = 11 AND "user_skills"."value" >= 2))

这不是我想要的:

确实,我希望用户的 user_skill 的技能 ID 为 10,并且用户技能的技能 ID 为 11 且值高于 2。

ActiveRecords 似乎“链接”了条件,我总是以空关系结束,因为 UserSkill 不能同时具有 10 和 11 的 id!

我想以某种方式区分第一个条件和第二个条件,以便我让具有 user_skills 的用户按顺序满足第一个条件和第二个条件,而不是相同的 user_skill 行,我不确定如何做到这一点!

我认为别名将是可行的方法,但我如何使用 squeel 或 ActiveRecord 语法为要引用的每个 user_skill 指定不同的别名?

我想我的问题与this one 非常相似,但是由于我使用的是循环,我认为我不能使用建议的解决方案!

编辑:我也在 squeel github 上找到了这个,但不确定如何将其应用于我的特定用例:Need a way to alias joins for multiple joins on the same relation

最后编辑:

我已经用 sql 写了一个可行的解决方案,但是你可以看到它非常难看:

      params[:user_skills].each_with_index do |user_skill,index|
         users = users.joins(",user_skills as u#{index}")
                      .where("u#{index}.skill_id= ? AND u#{index}.value >= ?"
                      ,user_skill[0].to_i, user_skill[1].to_i)
      end 

【问题讨论】:

    标签: ruby-on-rails activerecord join postgresql-9.1 squeel


    【解决方案1】:

    在这种情况下,您需要获取用户集的交集。换句话说,您需要将“AND”应用于用户集,而不是where 条件。当您链接where 条件时,您是在“与”条件“与”,这就是为什么您会看到您所看到的结果。

    您可以使用Intersection of two relations 中描述的基本技术在Ruby 或SQL 中执行此操作。为简单起见,我将在此处使用两组来说明这两种情况,并将扩展留给多于两组并留给您。

    首先,让我们定义两个集合:

    set1 = User.joins(:user_skills).where('user_skills.skill_id = 10')
    set2 = User.joins(:user_skills).where('user_skills.skill_id = 11 and user_skills.value >= 2')
    

    注意:您的示例文本表示“值高于 2”,但您的代码表示“值大于或等于 2”。我显然使用了上面的后一种解释。

    如果你想在 Ruby 中取交集,你可以取结果数组的交集,使用 &amp; 运算符,如下所示:

    set1 & set2
    

    在这种情况下,您正在执行两个单独的 SQL 查询并获取结果的交集。

    如果要使用SQL取交集,则需要编写SQL,可以使用this answer from @MikDiet对上述问题描述的技术来完成:

    User.connection.unprepared_statement do
      User.find_by_sql "#{set1.to_sql} INTERSECT #{set2.to_sql}"
    end
    

    在这种情况下,您正在执行单个查询,尽管该查询显然在做“更多工作”。您应该阅读原始答案以了解使用unprepared_statement的原因。

    我会将扩展名留给 squeel 和两个以上的集合,但请注意您可以将 SQL INTERSECT 运算符与两个以上的操作数一起使用,如下所示:

    query1 INTERSECT query2 INTERSECT query3 ...
    

    【讨论】:

    • 非常感谢 peter 提供了非常详细的答案 - 从我上次的编辑中可以看出,使用循环索引为连接生成别名似乎可行,但看起来有点糟糕 - 我认为它可以如果我按照您描述的方式使用 intersect 会更干净,其中 set1 和 set2 是 squeel 筛选器 - 我会尝试一下,并让您知道它是如何进行的!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-14
    • 2021-07-19
    • 1970-01-01
    • 2019-08-27
    • 2019-05-19
    • 1970-01-01
    相关资源
    最近更新 更多