0

我正在尝试向我的应用程序添加一个高级搜索选项,用户可以在其中根据来自 3 个不同模型的属性搜索某些链接。

我的应用程序设置User has_many :websitesWebsite has_many :linksLink has_many :stats

我知道如何在 Rails 中使用连接或包含等创建 SQL 查询,我遇到了困难,因为我只想检索每个链接而不是所有链接的最新统计信息 - 我不知道最有效的方法来做到这一点.

例如,假设一个用户有 2 个网站,每个网站有 10 个链接,每个链接有 100 个统计信息,总共 2,022 个对象,但我只想搜索 42 个对象(每个链接只有 1 个统计信息)。

一旦我在数据库查询中只获得了这 42 个对象,我就可以添加.where("attribute like ?", user_input)并返回正确的链接。

更新

我尝试将以下内容添加到我的 Link 模型中:

has_many :stats, dependent: :destroy
has_many :one_stat, class_name: "Stat", order: "id ASC", limit: 1

但这似乎不起作用,例如,如果我这样做:

@links = Link.includes(:one_stat).all

@links.each do |l|
  puts l.one_stat.size
end

我没有得到1, 1, 1...所有统计数据的数量:125, 40, 76...

我可以使用限制选项来获得我想要的结果还是不能这样工作?

第二次更新

我已经根据 Erez 的建议更新了我的代码,但仍然无法正常工作:

has_one :latest_stat, class_name: "Stat", order: "id ASC"

@links = Link.includes(:latest_stat)

@links.each do |l|
  puts l.latest_stat.indexed
end

=> true
=> true
=> true
=> false
=> true
=> true
=> true

Link.includes(:latest_stat).where("stats.indexed = ?", false).count
=> 6

Link.includes(:latest_stat).where("stats.indexed = ?", true).count
=> 7

它应该返回 1 和 6,但它仍在检查所有统计信息,而不仅仅是最新的。

4

3 回答 3

2

有时,您必须突破 AR 抽象并启动您的 SQL。只是一点点。

假设您有非常简单的关系:Website has_many :linksLink belongs_to :websitehas_many :stats、 和Stat belongs_to :link。任何地方都没有非规范化。现在,您想要构建一个查询,查找所有链接,以及每个链接的最新统计信息,但仅针对具有某些属性的统计信息(或者它可能是具有某些属性的网站或具有某些属性的链接)。

未经测试,但类似于:

Website
  .includes(:links => :stats)
  .where("stats.indexed" => true)
  .where("stats.id = (select max(stats2.id) 
     from stats stats2 where stats2.link_id = links.id)")

最后一位子选择作为每个链接一部分的统计信息并找到最大 id。然后它会过滤掉与该最大 ID 不匹配的统计信息(从顶部的连接中)。查询返回网站,每个网站都有一些链接,每个链接在其stats集合中只有一个统计信息。

一些额外的信息

我最初是根据窗口函数来写这个答案的,结果证明这有点矫枉过正,但我​​认为无论如何我都应该在这里介绍它,因为,好吧,很有趣。您会注意到,我们上面使用的聚合函数技巧之所以有效,是因为我们正在根据 ID 确定要使用的统计信息,这正是我们需要用来过滤连接中的统计信息的属性。但是,假设您只想要按 ID 以外的某些标准排名的第一个统计信息,例如number_of_clicks; 该技巧将不再起作用,因为聚合失去了对 ID 的跟踪。这就是窗口函数的用武之地。

同样,完全未经测试:

Website
  .includes(:links => :stats)
  .where("stats.indexed" => true)
  .where(                                 
     "(stats.id, 1) in (
       select id, row_number() 
       over (partition by stats2.id order by stats2.number_of_clicks DESC)
       from stat stats2 where stats2.link_id = links.id
     )"
   )

最后一个where子选择与每个链接匹配的统计数据并按number_of_clicks升序对其进行排序,然后该in部分将其与连接中的统计数据匹配。请注意,窗口查询不能移植到其他数据库平台。您也可以使用此技术来解决您提出的原始问题(只需交换stats2.idstats2.number_of_clicks;可以想象它可以表现得更好,并且被这篇博文提倡。

于 2013-01-23T08:02:49.193 回答
1

我会试试这个:

has_one :latest_stat, class_name: "Stat", order: "id ASC"

@links = Link.includes(:latest_stat)

@links.each do |l|
  puts l.latest_stat
end

请注意,您不能打印latest_stat.size,因为它是 stat 对象本身而不是关系。

于 2013-01-21T11:25:23.223 回答
0

这是你要找的吗?

@user.websites.map { |site| site.links.map { |link| link.stats.last } }.flatten

对于给定的用户,这将返回一个数组,其中包含该用户网站上链接的最后统计信息。

于 2013-01-23T04:31:57.263 回答