【问题标题】:Can't define :joins conditions in has_many relationship?无法在 has_many 关系中定义 :joins 条件?
【发布时间】:2009-04-25 09:13:03
【问题描述】:

我有一个关系表:

create_table "animal_friends", :force => true do |t|
    t.integer  "animal_id"
    t.integer  "animal_friend_id"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.integer  "status_id",        :default => 1
  end

将动物与其他动物联系起来。在 SQL 中检索关联的最佳方法是:

SELECT animals.* 
from animals join animal_friends as af 
  on animals.id = 
    case when af.animal_id = #{id} then af.animal_friend_id else af.animal_id end 
WHERE #{id} in (af.animal_id, af.animal_friend_id)

而且我找不到在rails 中创建适当的has_many 关系的方法。显然,没有办法为 has_many 提供加入条件。

我目前正在使用 finder_sql :

has_many :friends, :class_name => "Animal", :finder_sql => 'SELECT animals.* from animals join animal_friends as af on animals.id = case when af.animal_id = #{id} then af.animal_friend_id else af.animal_id end ' +
 'WHERE #{id} in (af.animal_id, af.animal_friend_id) and status_id = #{Status::CONFIRMED.id}'  

但是这种方法有一个很大的缺点,就是破坏了 activerecord 魔法。例如:

@animal.friends.first 

将无限制地执行finder_sql,获取数千行,然后获取数组的第一个(并失去几个宝贵的秒 / req)。

我猜这是 AR 中缺少的功能,但我想先确定一下 :) 谢谢

【问题讨论】:

    标签: activerecord join has-many conditional-statements


    【解决方案1】:

    您可以使用视图在数据库级别解决此问题,无论如何这都是正确的方法。

    CREATE VIEW with_inverse_animal_friends (
      SELECT id,
             animal_id,
             animal_friend_id,
             created_at,
             updated_at,
             status_id
        FROM animal_friends
       UNION
      SELECT id,
             animal_friend_id AS animal_id,
             animal_id AS animal_friend_id,
             created_at,
             updated_at,
             status_id
        FROM animal_friends
    )
    

    如果您不想让有两种关系的朋友重复输入,您可以这样做:

    CREATE VIEW unique_animal_friends (
      SELECT MIN(id), animal_id, animal_friend_id, MIN(created_at), MAX(updated_at), MIN(status_id)
        FROM
       (SELECT id,
               animal_id,
               animal_friend_id,
               created_at,
               updated_at,
               status_id
          FROM animal_friends
         UNION
        SELECT id,
               animal_friend_id AS animal_id,
               animal_id AS animal_friend_id,
               created_at,
               updated_at,
               status_id
          FROM animal_friends) AS all_animal_friends
      GROUP BY animal_id, animal_friend_id
    )
    

    您需要一种方法来决定使用哪个 status_id,以防有两个冲突的状态。我选择了 MIN(status_id) 但这可能不是你想要的。

    在 Rails 中,您现在可以这样做:

    class Animal < ActiveRecord::Base
      has_many :unique_animal_friends
      has_many :friends, :through => :unique_animal_friends
    end
    
    class UniqueAnimalFriend < ActiveRecord::Base
      belongs_to :animal
      belongs_to :friend, :class_name => "Animal"
    end
    

    这是我的想法,未经测试。此外,您可能需要一些插件来处理 rails 中的视图(例如“redhillonrails-core”)。

    【讨论】:

    • 谢谢,我喜欢这个主意!我会尽快测试并通知您。
    • 抱歉,在回复此问题之前耽搁了很长时间,我终于有时间测试您的解决方案,它就像一个魅力,开箱即用。
    【解决方案2】:

    有一个插件可以满足您的需求。

    有一篇关于它的帖子here。

    还有一个替代方案here。

    两者都允许您执行加入条件并且只是使用延迟初始化。 所以你可以使用动态条件。我觉得前者更漂亮,但如果你不想安装插件,可以使用后者。

    【讨论】:

      【解决方案3】:

      在活动记录中创建多对多关系的两种方法是 has_and_belongs_to_many 和 has_many :through。 This site 批评两者之间的差异。您不必使用这些方法编写任何 SQL。

      【讨论】:

      • 嗨,谢谢你的链接!无论如何,habtm 不允许任何类型的连接条件 :( 因此,我必须使用 :conditions 进行连接,并且由于我有超过 100.000 行(〜 400.000),我希望有一个真正优化的 SQL 查询.使用habtm,我只能得到一半的结果,因为我也在寻找foreign_key和association_foreign_key中的关系。谢谢
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-06
      • 1970-01-01
      相关资源
      最近更新 更多