SQL LEFT JOIN值不在任何连接列中

时间:2015-09-21 16:22:25

标签: sql ruby-on-rails postgresql join left-join

我怀疑这是一个相当普遍的情况,并且可能表明我作为数据库开发人员的无能,但无论如何都要进行......

我有两个表:配置文件 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)

再说一遍,也许这很好,但如果有更好的方式,我会乐意接受它。

2 个答案:

答案 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,以确保最佳效果。