我想查找具有特定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: 1
和language_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)
答案 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})