具有2种不同连接条件的Ruby查询

时间:2017-01-06 23:20:03

标签: mysql ruby-on-rails ruby jointable

我想查找具有特定LanguagesUsers记录的所有用户。目前我正在尝试查询连接表,LanguagesUsers用于用户拥有2个相应的languages_id和level

的所有实例

要找到只有一个关联的位置,这可行:

User.joins(:languages_users).where(languages_users: {language_id: 2, level: 5})

如何在查询中包含2个languages_users,它们是真的? (例如,我想要language_id: 2 level: 1language_id: 3 level: 5

我试过这个,但它返回一个错误:

User.joins(:languages_users).where(languages_users: {language_id: 2, level: 5} AND languages_users: {language_id: 1, level: 3})

这个返回一个空数组,即使肯定有2个languages_users符合此条件的用户:

User.joins(:languages_users).where(languages_users: {language_id: 2, level: 5}).where(languages_users: {language_id: 1, level: 3})

这是什么格式? 谢谢!

更新: LanugagesUser是语言和用户之间的连接表,具有language_id,level,user_id

gem 'sqlite3', group: :development
group :production do
    gem 'pg'
    gem 'rails_12factor'
end

更新2:既然两个答案都提出了陈述或陈述,我认为我没有正确地传达这一事实:

两个记录很多。 例如。我想知道用户有什么语言用户(language_id:1,级别:5)和LanguagesUser(language_id:2,级别:1)

6 个答案:

答案 0 :(得分:2)

你有没有尝试过:

User.joins(:languages_users).where(languages_users: [{language_id: 2, level: 5}, {language_id: 1, level: 3}])

编辑: 由于您使用的是ActiveRecord> = 5,因此可以使用.or语法。结果有点冗长,但它应该有效,我相信你可以把它搞定:

User.joins(:languages_users).where(
  languages_users: {language_id: 2, level: 5}).
  or(
    User.joins(:languages_users).where(
      languages_users: {language_id: 1, level: 3}
  )

编辑2: 我我明白你现在要做什么,比我最初想的要复杂,但我认为你需要一个嵌套查询。如果您首先查询languages_users表并按user_id对其进行分组,则可以对其应用HAVING子句以查找具有这两种语言的用户。然后从该查询中选择user_ids,并在users表的简单查询中使用它们。由于您使用的是postgres,因此以下内容可能有效。

languages = [{ language_id: 2, level: 5 }, { language_id: 1, level: 3 }]
User.where(users_with_all_languages(languages)

def users_with_all_languages(languages)
  LanguagesUser.
    select(:user_id).
    group(:user_id).
    having('array_agg(language_id) @> ARRAY[?] AND array_agg(level) @> ARRAY[?]',
      languages.map { |x| x[:language_id] },
      languages.map { |x| x[:level] }
    )
end

或者,更简单地说,如果您对语言数据的格式具有灵活性:

language_ids = [2, 1]
language_levels = [5, 3]
User.where(users_with_all_languages(language_ids, language_levels)

def users_with_all_languages(ids, levels)
  LanguagesUser.
    select(:user_id).
    group(:user_id).
    having('array_agg(language_id) @> ARRAY[?] AND array_agg(level) @> ARRAY[?]', ids, levels)
end

我做了类似的事情,但在连接表上没有两个条件,所以AND两个array_agg条件是我认为唯一的通配符...

答案 1 :(得分:2)

2种解决方案 (注意:rails不支持OR个查询)

<强> 1。简单的SELECT WHERE

使用如下的长行代码:

User.
  joins(:languages_users).
  where('(`languages_users`.`language_id` = ? AND `languages_users`.`level` = ?) ' + 
        'OR (`languages_users`.`language_id` = ? AND `languages_users`.`level` = ?)', 
        2, 1, 5, 3)

<强> 2。 2衬里WHERE SELECT

首先,您准备一个查询以选择languages_users.id s

的列表
languages_users_ids = LanguagesUser.
  select(:id).
  where('(language_id = ? AND level = ?) OR (language_id = ? AND level = ?)', 
        2, 1, 5, 3)

然后在对users表的查询中使用它:

User.joins(:languages_users).where(languages_users: {id: languages_users_ids})

这将向您的数据库服务器生成一个“WHERE SELECT”请求:

SELECT * FROM users JOIN languages_users 
  WHERE languages_users.id IN (
    SELECT id FROM languages_users  
    WHERE (language_id = 2 AND level = 1) OR (language_id = 5 AND level = 3)
    )

答案 2 :(得分:1)

试试这个

User.joins('LEFT OUTER JOIN languages_users lu1 ON lu1.user_id = users.id AND lu1.language_id = 2 AND lu1.level = 5')
    .joins('LEFT OUTER JOIN languages_users lu2 ON lu2.user_id = users.id AND lu2.language_id = 1 AND lu2.level = 3')
    .group('users.id')

我没有设置postgres,所以我在mysql设置中快速测试了这个。

当提出更复杂的查询时,开始考虑使用原始sql(至少对我而言)是好的,然后看看你是否可以将其转换为AR查询。

你想要完成的是这样的原始sql。

SELECT *
FROM users
LEFT OUTER JOIN
  languages_users lu1 ON users.id = lu1.user_id AND lu1.language_id = 2 AND lu1.level = 5
LEFT OUTER JOIN
  languages_users lu2 ON users.id = lu2.user_id AND lu2.language_id = 1 AND lu1.level = 3
GROUP BY
  users.id

这样,您将构建一个这样的表:

users.id | users.* | lu1.user_id | lu1.language_id | lu1.level | lu2.user_id | lu2.language_id | lu2.level
   1        ...            1             2                5            1            1                3

一个where的两个join子句无法正常工作,因为您在一行中设置了languages_users表要满足的两个条件。但它总是只满足其中一个或更少。

修改

所以这在性能方面更进一步。我假设您在languages_users表上有适当的索引(您需要为user_id编制索引,并将language_id and level组合起来。

但我已经测试了类似的情况,相当于language_users的90米行,相当于users表的13米行。我的答案中的查询和子查询答案都需要非常长的时间。

因此,使用子查询,您将在ruby服务器中放入大量负载,因为您将触发单个查询以从两个子查询中获取所有user_id,并使用它们构造最终查询以获取用户记录。

我无法测试联接查询的效果,因为在超时之前它不会返回我。

答案 3 :(得分:1)

让我们一步一步走 第一个条件是language_id: 2, level: 5 所以我们可以只针对这种情况进行查询

users_first_condition = LanguagesUser.where(language_id: 2, level: 5).pluck(:user_id)

然后我们可以对第二个条件{language_id: 1, level: 3}

做同样的事情
users_second_condition = LanguagesUser.where(language_id: 1, level: 3).pluck(:user_id)

由于我们对两个条件都有user_ids,因此我们可以将两个数组相交并运行一个新查询:

User.where(id: users_first_condition & users_second_condition)

如果我们想阻止运行多个查询,我们可以使用子查询,因此,我们可以直接将关系传递给用户的查询,而不是仅从每个条件中获取id:

users_first_condtion = Languagesuser.select(:user_id).where(language_id: 2, level: 5)
users_second_condition = Languageuser.select(:user_id).where(language_id: 1, level: 3)
User.where(id: users_first_condition).where(id: users_second_condition)

答案 4 :(得分:0)

试试这个:

User.
  joins("LEFT OUTER JOIN languages_users AS l1 ON 
          l1.user_id = users.id AND l1.language_id = 1 AND l1.level = 3").
  joins("LEFT OUTER JOIN languages_users AS l2 ON 
          l2.user_id = users.id AND l2.language_id = 2 AND l2.level = 5").
  where("l1.id IS NOT NULL AND l2.id IS NOT NULL")

答案 5 :(得分:-1)

你试过了吗?

User.joins(:languages_users).where(languages_users: {language_id: 2, level: 5}).where(languages_users: {language_id: 1, level: 3})