我怀疑这是一个相当普遍的情况,并且可能表明我作为数据库开发人员的无能,但无论如何都要进行......
我有两个表:配置文件和 HiddenProfiles , HiddenProfiles 表有两个相关的外键: profile_id 和 hidden_profile_id ,用于存储配置文件表中的 id 。
您可以想象,用户可以隐藏其他用户(其中他的个人资料ID将是 HiddenProfiles 表中的 profile_id )或他可以被其他用户隐藏(其中他的个人资料ID将放在 hidden_profile_id 列中)。同样,这是一个非常常见的场景。
期望的结果:
我想在“个人档案”和“隐藏列表”表上进行连接(或者说诚实,最有效的查询),以查找给定个人资料未隐藏且未隐藏的所有个人资料。
在我的脑海中,我认为这将非常简单,但我想出的迭代似乎一直错过了问题的一半。最后,我得到了一些看起来像这样的东西:
SELECT "profiles".* FROM "profiles"
LEFT JOIN hidden_profiles hp1 on hp1.profile_id = profiles.id and (hp1.hidden_profile_id = 1)
LEFT JOIN hidden_profiles hp2 on hp2.hidden_profile_id = profiles.id and (hp2.profile_id = 1)
WHERE (hp1.hidden_profile_id is null) AND (hp2.profile_id is null)
不要误会我的意思,这个"有效"但在我心中,我觉得应该有更好的方法。如果事实上没有,我非常乐意接受那些在这个问题上比我更智慧的人的回答。 :)
值得注意的是,这两款RoR模型位于Postgres数据库中,因此我们非常感谢为这些限制量身定制的解决方案。
<小时/> 模型如下:
class Profile < ActiveRecord::Base
...
has_many :hidden_profiles, dependent: :delete_all
scope :not_hidden_to_me, -> (profile) { joins("LEFT JOIN hidden_profiles hp1 on hp1.profile_id = profiles.id and (hp1.hidden_profile_id = #{profile.id})").where("hp1.hidden_profile_id is null") }
scope :not_hidden_by_me, -> (profile) { joins("LEFT JOIN hidden_profiles hp2 on hp2.hidden_profile_id = profiles.id and (hp2.profile_id = #{profile.id})").where("hp2.profile_id is null") }
scope :not_hidden, -> (profile) { self.not_hidden_to_me(profile).not_hidden_by_me(profile) }
...
end
class HiddenProfile < ActiveRecord::Base
belongs_to :profile
belongs_to :hidden_profile, class_name: "Profile"
end
因此,要获取我想要的个人资料,请执行以下操作:
Profile.not_hidden(given_profile)
再说一遍,也许这很好,但如果有更好的方式,我会乐意接受它。
答案 0 :(得分:1)
如果您只想为单个配置文件获取此列表,我将实现一个实例方法,以在ActiveRecord中有效地执行相同的查询。我做的唯一修改是对子查询的并集执行单个连接,并在子查询上应用条件。这应该减少需要加载到内存中的列,并且希望更快(您需要对数据进行基准测试以确保):
class Profile < ActiveRecord::Base
def visible_profiles
Profile.joins("LEFT OUTER JOIN (
SELECT profile_id p_id FROM hidden_profiles WHERE hidden_profile_id = #{id}
UNION ALL
SELECT hidden_profile_id p_id FROM hidden_profiles WHERE profile_id = #{id}
) hp ON hp.p_id = profiles.id").where("hp.p_id IS NULL")
end
end
由于此方法返回ActiveRecord范围,因此可以根据需要链接其他条件:
Profile.find(1).visible_profiles.where("created_at > ?", Time.new(2015,1,1)).order(:name)
答案 1 :(得分:1)
就我个人而言,我从未喜欢过join = null的方法。我发现它反直觉。您要求加入,然后将结果限制为不匹配的记录。
我会更多地接近它
SELECT id FROM profiles p
WHERE
NOT EXISTS
(SELECT * FROM hidden_profiles hp1
WHERE hp1.hidden_profile_id = 1 and hp1.profile_id = p.profile_id)
AND
NOT EXISTS (SELECT * FROM hidden_profiles hp2
WHERE hp2.hidden_profile_id = p.profile_id and hp2.profile_id = 1)
但是你需要运行一些具有真实卷的EXPLAIN,以确保最佳效果。