【问题标题】:Self-join on a table with ActiveRecord使用 ActiveRecord 在表上自联接
【发布时间】:2010-09-04 11:15:05
【问题描述】:

我有一个名为 Name 的 ActiveRecord,其中包含各种 Languages 中的名称。

class Name < ActiveRecord::Base
  belongs_to :language

class Language < ActiveRecord::Base
  has_many :names

用一种语言查找名字很容易:

Language.find(1).names.find(whatever)

但我需要找到语言 1 和语言 2 具有相同名称的匹配对。在 SQL 中,这需要一个简单的自连接:

SELECT n1.id,n2.id FROM names AS n1, names AS n2
  WHERE n1.language_id=1 AND n2.language_id=2
    AND n1.normalized=n2.normalized AND n1.id != n2.id;

如何使用 ActiveRecord 进行这样的查询?请注意,我需要查找名称对(= 匹配的两边),而不仅仅是语言 1 中恰好与某些内容匹配的名称列表。

对于奖励积分,请将 n1.normalized=n2.normalized 替换为 n1.normalized LIKE n2.normalized,因为该字段可能包含 SQL 通配符。

我也对以不同方式对数据进行建模的想法持开放态度,但如果可以的话,我更愿意避免为每种语言使用单独的表格。

【问题讨论】:

    标签: ruby-on-rails activerecord mysql self-join


    【解决方案1】:

    试试这个:

    ids = [1,2]
    Name.all(:select    => "names.id, n2.id AS id2",
             :joins     => "JOIN names AS n2 
                                  ON n2.normalized = names.normalized AND 
                                     n2.language_id != names.language_id AND
                                     n2.language_id IN (%s)" % ids.join(','),
             :conditions => ["names.language_id IN (?)", ids]
    ).each do |name|
      p "id1 : #{name.id}"
      p "id2 : #{name.id2}"
    end
    

    PS:确保你清理了传递给连接条件的参数。

    【讨论】:

    • 嗯,这确实有效(在修正了一个小错字之后,应该是 :joins =&gt; "JOIN names as...),但它只返回语言 1 中的 Name 对象(加上 id2)。获取语​​言 2 中名称的对象需要为每次匹配调用 Name.find(name.id2),这会对性能造成很大影响。有什么办法吗?
    • 好的,它返回两种语言中所有匹配项的列表(在添加 AND names.language_id != n2.language_id 以过滤掉自匹配项之后),但它是一个慢得多的查询,它返回一个巨大的列表而不是对列表 - 我仍然需要使用 Name.find(name.id2) 来找出名称的匹配对。
    • 列表中返回了多少行?理想情况下,这应该返回一行(假设您没有相同键的重复条目)。您想在第二次发现中获得什么数据?您可以更新选择列表以从 names 表中添加您需要的任何字段。我已经更新了答案。也许这一次它会起作用。
    【解决方案2】:

    听起来您可能想在 Language 和 Name 之间使用多对多关系,而不是 has_many/belongs_to。

    >> Language.create(:name => 'English')
     => #<Language id: 3, name: "English", created_at: "2010-09-04 19:15:11", updated_at: "2010-09-04 19:15:11"> 
    >> Language.create(:name => 'French')
     => #<Language id: 4, name: "French", created_at: "2010-09-04 19:15:13", updated_at: "2010-09-04 19:15:13"> 
    >> Language.first.names << Name.find_or_create_by_name('Dave')
     => [#<Name id: 3, name: "Dave", language_id: 3, created_at: "2010-09-04 19:16:50", updated_at: "2010-09-04 19:16:50">] 
    >> Language.last.names << Name.find_or_create_by_name('Dave')
     => [#<Name id: 3, name: "Dave", language_id: 4, created_at: "2010-09-04 19:16:50", updated_at: "2010-09-04 19:16:50">]
    >> Language.first.names.first.languages.map(&:name)
     => ["English", "French"] 
    

    这种额外的标准化水平应该会使您尝试做的事情更容易。

    【讨论】:

    • 哦,有趣。问题是,例如。芬兰语 'Joni' 和希伯来语 'Yoni' 实际上是具有不同属性(原始脚本中的拼写等)的不同名称,它们恰好具有相同的规范化名称字段,而不仅仅是一个名称。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多