sql - Rails : complex search on 3 models, 只返回最新的 - 如何做到这一点?

标签 sql ruby-on-rails postgresql ruby-on-rails-3.2

我正在尝试向我的应用程序添加一个高级搜索选项,用户可以在其中根据 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) 并返回正确的链接。

更新

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

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,但它仍在检查所有统计数据,而不仅仅是最新的。

最佳答案

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

假设您有非常简单的关系:Website has_many :linksLink belongs_to :websitehas_many :statsStat 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 集合中只有一个统计信息。

一些额外的信息

我最初是根据 window functions 写这个答案的,结果证明这是矫枉过正,但我​​认为无论如何我都应该在这里介绍它,因为,很好,很有趣。您会注意到,我们上面使用的聚合函数技巧之所以有效,是因为我们根据其 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.id 换成 stats2.number_of_clicks);可以想象它可以表现得更好,并且由这个 blog post 提倡.

关于sql - Rails : complex search on 3 models, 只返回最新的 - 如何做到这一点?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14422460/

相关文章:

ruby-on-rails - 事件记录 :has_many associations and === operator

sql - PostgreSQL 查询中的条件语句

mysql - 搜索逻辑分片

php - 行之间的时间差

sql - postgres 9.3 windows 的空密码登录失败

mysql - 无法在 Linux (CentOS) 上安装 "mysql2"gem

javascript - Hasura Graphql 查询运算符的神秘定义

mySQL 按功能分组显示缺少数据

sql - 如何查看 Oracle 数据库中 VPD 添加的谓词?

ruby-on-rails - Rails has_and_belongs_to_many ActiveRecord::UnknownPrimaryKey