我有model profile.rb
以下关联
class User < ActiveRecord::Base
has_one :profile
end
class Profile < ActiveRecord::Base
has_many :skills
belongs_to :user
end
我有model skills.rb
以下关联
class Skill < ActiveRecord::Base
belongs_to :profile
end
我在技能表中有以下条目
id: name: profile_id:
====================================================
1 accounting 1
2 martial arts 2
3 law 1
4 accounting 2
5 journalist 3
6 administration 1
依此类推,我怎样才能查询所有的个人资料,比方说,“会计”&amp;考虑到上述重新编码,“管理”技能将是id为1的概要。到目前为止,我已经尝试过以下
Profile.includes(:skills).where(skills: {name: ["accounting" , "administration"]} )
但不是查找包含id 1
的个人资料 - 它会让我[ 1, 2 ]
,因为ID为2的个人资料会保留"accounting" skills
并且它在数据库中执行"IN" operation
注意:我正在使用postgresql
并且问题不仅仅是描述的配置文件的特定ID(我仅用作示例) - 原始问题是获取包含这两个的所有配置文件提到的技能。
我的activerecord join会在postgres中触发以下查询
SELECT FROM "profiles" LEFT OUTER JOIN "skills" ON "skills"."profile_id" = "profiles"."id" WHERE "skills"."name" IN ('Accounting', 'Administration')
在下面Vijay Agrawal的答案是我已经在我的应用程序中已经有的东西,他和我的,查询使用IN
通配符导致配置文件ID包含任何技能,而我的问题是获取包含两种技能的配置文件ID。我确信必须有一种方法可以用原始问题中列出的相同查询方式来解决这个问题,我很想知道这种方式。我希望我能得到更多帮助 - 谢谢
为了清楚起见,我想在与配置文件模型具有has_many关系的模型中查询具有多种技能的所有配置文件 - 使用Profile
作为主要表而不是skills
使用个人资料作为主要表的原因是,在分页中,我不想从相关表中获取所有技能,例如20_000或更多行,然后根据profile.state
列进行过滤。相反,任何人都希望只选择符合5 records
的{{1}}并匹配技能而不检索数千条不相关的记录,然后再过滤它们。
答案 0 :(得分:7)
您应该这样做以获得具有会计和管理技能的所有profile_id
:
Skill.where(name: ["accounting", "administration"]).group(:profile_id).having("count('id') = 2").pluck(:profile_id)
如果您需要个人资料详细信息,可以将此查询放在Profile
的{{1}}的where子句中。
请注意查询中的数字id
,它是在where子句中使用的数组的长度。在这种情况下2
UPDATE ::
根据更新的问题说明,您可以使用["accounting", "administration"].length
而不是pluck
添加子查询,以确保它在一个查询中发生。
select
您可以控制排序,分页和其他where子句。在编辑问题中没有看到任何关注的问题。
更新2 ::
另一种使配置文件与两种技能相交的方法(可能效率低于上述解决方案):
Profile.where(id: Skill.where(name: ["accounting", "administration"]).group(:profile_id).having("count('id') = 2").select(:profile_id))
答案 1 :(得分:1)
Profile.includes(:skills).where("skills.name" => %w(accounting administration))
有关详情,请参阅finding through ActiveRecord associations。
<强>更新强>
如果这对您不起作用,那么您可能没有正确配置数据库和模型,因为在全新的Rails应用程序中,这可以按预期工作。
class CreateProfiles < ActiveRecord::Migration[5.1]
def change
create_table :profiles do |t|
t.timestamps
end
end
end
class CreateSkills < ActiveRecord::Migration[5.1]
def change
create_table :skills do |t|
t.string :name
t.integer :profile_id
t.timestamps
end
end
end
class Profile < ApplicationRecord
has_many :skills
end
class Skill < ApplicationRecord
belongs_to :profile
end
Profile.create
Profile.create
Skill.create(name: 'foo', profile_id: 1)
Skill.create(name: 'bar', profile_id: 1)
Skill.create(name: 'baz', profile_id: 2)
Profile.includes(:skills).where("skills.name" => %w(foo))
SQL (0.3ms) SELECT DISTINCT "profiles"."id" FROM "profiles" LEFT OUTER JOIN "skills" ON "skills"."profile_id" = "profiles"."id" WHERE "skills"."name" = 'foo' LIMIT ? [["LIMIT", 11]]
SQL (0.1ms) SELECT "profiles"."id" AS t0_r0, "profiles"."created_at" AS t0_r1, "profiles"."updated_at" AS t0_r2, "skills"."id" AS t1_r0, "skills"."name" AS t1_r1, "skills"."profile_id" AS t1_r2, "skills"."created_at" AS t1_r3, "skills"."updated_at" AS t1_r4 FROM "profiles" LEFT OUTER JOIN "skills" ON "skills"."profile_id" = "profiles"."id" WHERE "skills"."name" = 'foo' AND "profiles"."id" = 1
=> #<ActiveRecord::Relation [#<Profile id: 1, created_at: "2017-07-28 21:52:56", updated_at: "2017-07-28 21:52:56">]>
Profile.includes(:skills).where("skills.name" => %w(bar))
SQL (0.3ms) SELECT DISTINCT "profiles"."id" FROM "profiles" LEFT OUTER JOIN "skills" ON "skills"."profile_id" = "profiles"."id" WHERE "skills"."name" = 'bar' LIMIT ? [["LIMIT", 11]]
SQL (0.1ms) SELECT "profiles"."id" AS t0_r0, "profiles"."created_at" AS t0_r1, "profiles"."updated_at" AS t0_r2, "skills"."id" AS t1_r0, "skills"."name" AS t1_r1, "skills"."profile_id" AS t1_r2, "skills"."created_at" AS t1_r3, "skills"."updated_at" AS t1_r4 FROM "profiles" LEFT OUTER JOIN "skills" ON "skills"."profile_id" = "profiles"."id" WHERE "skills"."name" = 'bar' AND "profiles"."id" = 1
=> #<ActiveRecord::Relation [#<Profile id: 1, created_at: "2017-07-28 21:52:56", updated_at: "2017-07-28 21:52:56">]>
Profile.includes(:skills).where("skills.name" => %w(baz))
SQL (0.3ms) SELECT DISTINCT "profiles"."id" FROM "profiles" LEFT OUTER JOIN "skills" ON "skills"."profile_id" = "profiles"."id" WHERE "skills"."name" = 'baz' LIMIT ? [["LIMIT", 11]]
SQL (0.1ms) SELECT "profiles"."id" AS t0_r0, "profiles"."created_at" AS t0_r1, "profiles"."updated_at" AS t0_r2, "skills"."id" AS t1_r0, "skills"."name" AS t1_r1, "skills"."profile_id" AS t1_r2, "skills"."created_at" AS t1_r3, "skills"."updated_at" AS t1_r4 FROM "profiles" LEFT OUTER JOIN "skills" ON "skills"."profile_id" = "profiles"."id" WHERE "skills"."name" = 'baz' AND "profiles"."id" = 2
=> #<ActiveRecord::Relation [#<Profile id: 2, created_at: "2017-07-28 21:53:34", updated_at: "2017-07-28 21:53:34">]>
更新2
因为你后来改变了你的问题而忽略答案是一种糟糕的形式。
您应该将模型关系从has_many
和belongs_to
更改为has_and_belongs_to_many
。这将允许您每次停止记录新技能;如果某人添加了技能administration
,然后又有人添加了该技能,则无需创建新技能。您只需重复使用现有技能并将其与多个配置文件相关联:
class Profile < ApplicationRecord
has_and_belongs_to_many :skills
end
class Skill < ApplicationRecord
has_and_belongs_to_many :profiles
end
添加一个具有唯一索引的连接表(因此每个配置文件只能拥有一次技能一次):
class Join < ActiveRecord::Migration[5.1]
def change
create_table :profiles_skills, id: false do |t|
t.belongs_to :profile, index: true
t.belongs_to :skill, index: true
t.index ["profile_id", "skill_id"], name: "index_profiles_skills_on_profile_id_skill_id", unique: true, using: :btree
end
end
end
创建模型:
Profile.create
Profile.create
Skill.create(name: 'foo')
Skill.create(name: 'bar')
Skill.create(name: 'baz')
Profile.first.skills << Skill.first
Profile.first.skills << Skill.second
Profile.second.skills << Skill.second
Profile.second.skills << Skill.third
然后运行查询以返回第一个配置文件:
skills = %w(foo bar).uniq
Profile.includes(:skills).where('skills.name' => skills).group(:id).having("count(skills.id) >= #{skills.size}")
SQL (0.4ms) SELECT DISTINCT "profiles"."id" FROM "profiles" LEFT OUTER JOIN "profiles_skills" ON "profiles_skills"."profile_id" = "profiles"."id" LEFT OUTER JOIN "skills" ON "skills"."id" = "profiles_skills"."skill_id" WHERE "skills"."name" IN ('foo', 'bar') GROUP BY "profiles"."id" HAVING (count(skills.id) = 2) LIMIT ? [["LIMIT", 11]]
SQL (0.2ms) SELECT "profiles"."id" AS t0_r0, "profiles"."created_at" AS t0_r1, "profiles"."updated_at" AS t0_r2, "skills"."id" AS t1_r0, "skills"."name" AS t1_r1, "skills"."profile_id" AS t1_r2, "skills"."created_at" AS t1_r3, "skills"."updated_at" AS t1_r4 FROM "profiles" LEFT OUTER JOIN "profiles_skills" ON "profiles_skills"."profile_id" = "profiles"."id" LEFT OUTER JOIN "skills" ON "skills"."id" = "profiles_skills"."skill_id" WHERE "skills"."name" IN ('foo', 'bar') AND "profiles"."id" = 1 GROUP BY "profiles"."id" HAVING (count(skills.id) = 2)
=> #<ActiveRecord::Relation [#<Profile id: 1, created_at: "2017-07-28 21:52:56", updated_at: "2017-07-28 21:52:56">]>
通过其他测试确认:
应返回两个配置文件:
skills = %w(bar).uniq
Profile.includes(:skills).where('skills.name' => skills).group(:id).having("count(skills.id) >= #{skills.size}")
SQL (0.4ms) SELECT DISTINCT "profiles"."id" FROM "profiles" LEFT OUTER JOIN "profiles_skills" ON "profiles_skills"."profile_id" = "profiles"."id" LEFT OUTER JOIN "skills" ON "skills"."id" = "profiles_skills"."skill_id" WHERE "skills"."name" = 'bar' GROUP BY "profiles"."id" HAVING (count(skills.id) >= 1) LIMIT ? [["LIMIT", 11]]
SQL (0.3ms) SELECT "profiles"."id" AS t0_r0, "profiles"."created_at" AS t0_r1, "profiles"."updated_at" AS t0_r2, "skills"."id" AS t1_r0, "skills"."name" AS t1_r1, "skills"."profile_id" AS t1_r2, "skills"."created_at" AS t1_r3, "skills"."updated_at" AS t1_r4 FROM "profiles" LEFT OUTER JOIN "profiles_skills" ON "profiles_skills"."profile_id" = "profiles"."id" LEFT OUTER JOIN "skills" ON "skills"."id" = "profiles_skills"."skill_id" WHERE "skills"."name" = 'bar' AND "profiles"."id" IN (1, 2) GROUP BY "profiles"."id" HAVING (count(skills.id) >= 1)
=> #<ActiveRecord::Relation [#<Profile id: 1, created_at: "2017-07-28 21:52:56", updated_at: "2017-07-28 21:52:56">, #<Profile id: 2, created_at: "2017-07-28 21:53:34", updated_at: "2017-07-28 21:53:34">]>
应仅返回第二个配置文件:
skills = %w(bar baz).uniq
SQL (0.3ms) SELECT DISTINCT "profiles"."id" FROM "profiles" LEFT OUTER JOIN "profiles_skills" ON "profiles_skills"."profile_id" = "profiles"."id" LEFT OUTER JOIN "skills" ON "skills"."id" = "profiles_skills"."skill_id" WHERE "skills"."name" IN ('bar', 'baz') GROUP BY "profiles"."id" HAVING (count(skills.id) >= 2) LIMIT ? [["LIMIT", 11]]
SQL (0.2ms) SELECT "profiles"."id" AS t0_r0, "profiles"."created_at" AS t0_r1, "profiles"."updated_at" AS t0_r2, "skills"."id" AS t1_r0, "skills"."name" AS t1_r1, "skills"."profile_id" AS t1_r2, "skills"."created_at" AS t1_r3, "skills"."updated_at" AS t1_r4 FROM "profiles" LEFT OUTER JOIN "profiles_skills" ON "profiles_skills"."profile_id" = "profiles"."id" LEFT OUTER JOIN "skills" ON "skills"."id" = "profiles_skills"."skill_id" WHERE "skills"."name" IN ('bar', 'baz') AND "profiles"."id" = 2 GROUP BY "profiles"."id" HAVING (count(skills.id) >= 2)
=> #<ActiveRecord::Relation [#<Profile id: 2, created_at: "2017-07-28 21:53:34", updated_at: "2017-07-28 21:53:34">]>
不应返回任何个人资料:
skills = %w(foo baz).uniq
Profile.includes(:skills).where('skills.name' => skills).group(:id).having("count(skills.id) >= #{skills.size}")
SQL (0.3ms) SELECT DISTINCT "profiles"."id" FROM "profiles" LEFT OUTER JOIN "profiles_skills" ON "profiles_skills"."profile_id" = "profiles"."id" LEFT OUTER JOIN "skills" ON "skills"."id" = "profiles_skills"."skill_id" WHERE "skills"."name" IN ('foo', 'baz') GROUP BY "profiles"."id" HAVING (count(skills.id) >= 2) LIMIT ? [["LIMIT", 11]]
=> #<ActiveRecord::Relation []>
答案 2 :(得分:1)
>>> my_list = [1,0,2,0,3,0,4,0]
>>> my_index = my_list.index(3)
>>> del my_list[my_index:my_index+2]
>>> my_list
[1, 0, 2, 0, 4, 0]
依赖解决方案:
PostgreSQL
它可以工作,但更改数据库结构以加快速度是有意义的。有两种选择:
where_clause = <<~SQL
ARRAY(
SELECT name FROM skills WHERE profile_id = profiles.id
) @> ARRAY[?]
SQL
Profile.where(where_clause, %w[skill1 skill2])
方式增加了一致性(has_and_belongs_to_many
表变成字典)和使用索引的能力skills
作为skills
的{{1}} | array
列 - 按索引添加快速搜索,不需要子选择或加入。答案 3 :(得分:1)
我会将EXISTS
与correlated sub-query一起使用,如下所示:
required_skills = %w{accounting administration}
q = Profile.where("1=1")
required_skills.each do |sk|
q = q.where(<<-EOQ, sk)
EXISTS (SELECT 1
FROM skills s
WHERE s.profile_id = profiles.id
AND s.name = ?)
EOQ
end
this similar question还有其他一些想法,但我认为在您的情况下,多个EXISTS
条款是最简单且最有可能最快的。
(顺便说一下,在Rails 4+中,你可以从Profile.all
而不是Profile.where("1=1")
开始,因为all
会返回Relation
,但在过去它曾经返回一个数组。)
答案 4 :(得分:-1)
以下查询的问题
Profile.includes(:skills).where(skills: { name: ["accounting" , "administration"] } )
是它使用IN
运算符创建查询,例如IN ('Accounting', 'Administration')
现在按照SQA标准,它将匹配所有匹配任何值的记录,而不是匹配数组中的所有值。
这是最简单的解决方案
skills = ["accounting" , "administration"]
Profile.includes(:skills).where(skills: { name: skills }).group(:profile_id).having("count(*) = #{skills.length}")
P.S。这假设您将至少拥有一项技能。根据您的用例调整having
条件