Active Records加入条件:如何使用尖叫的sifters加入具有不同条件的同一个表?

时间:2013-12-22 13:07:54

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

我有两个activerecords类,User和UserSkill。 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的用户技能为10,并且还有一个user_skill,其skill_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 

1 个答案:

答案 0 :(得分:0)

这是您需要使用多组用户的情况。换句话说,您需要将“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中使用交集,你可以使用&运算符获取结果数组的交集,如下所示:

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的原因。

除非要注意您可以使用SQL INTERSECT运算符和两个以上的操作数,否则我会将扩展名留给squeel和两个以上的set:

query1 INTERSECT query2 INTERSECT query3 ...