【问题标题】:Applying a different order to postgres "DISTINCT ON" using rails使用rails对postgres“DISTINCT ON”应用不同的顺序
【发布时间】:2012-11-07 21:49:48
【问题描述】:

我有一个 Rails 应用程序:

user has_many :projects

user has_many :tasks, :through => :projects

project has_many :tasks

每个任务都有一个里程碑日期。

要显示我正在使用的下一个里程碑日期的项目详细信息表:

@projects = current_user.tasks.joins(:project).select("distinct on (projects.id) projects.*, tasks.*").reorder("projects.id, tasks.milestone ASC")

这很好用。

我现在希望能够对表格列进行排序。

根据 Postgres DISTINCT ON 不可排序,您必须将其包装在另一个 select 语句中,即 SELECT * FROM (SELECT DISTINCT ON....) ORDER BY column_3

我确实认为被排序的列可以根据需要放入 SQL 中,即(按项目名称 DESC 排序):

@projects = current_user.tasks.joins(:project).select("distinct on (projects.name) projects.*, tasks.*").reorder("projects.name DESC, tasks.milestone ASC")

这可行,但我也希望能够按里程碑订购,但那样不行。

谁能告诉我如何转换我的 rails 查询以便它可以按任何列排序?

更新


我想我的问题只是如何将 activerecord 查询包装在周围的 SELECTORDER BY 中?

我想我已经成功地使用了:

inner_query = current_user.tasks.select("distinct on (projects.id) projects.*, tasks.*").reorder("projects.id, tasks.milestone ASC").to_sql
@projects = Task.paginate_by_sql("select * from (#{inner_query}) as user_projects order by user_projects.name", :page => params[:page])

这是最好的方法还是有人能想到更好的方法? - find/paginate_by_sql 似乎是一种解决方法,我更愿意留在 activerecord 查询的范围内。

谢谢

【问题讨论】:

  • 我对 Rails 很陌生,但是在进行最终查询之前,您是否尝试过将 @foo.group(:id) 用于项目表?
  • @StanM,通过使用 DISTINCT ON,它消除了对 group() 的需要,并消除了输入除 milestone 之外的所有列名的需要。我考虑过使用first_value,但我不知道如何使用它,我看到了这个帖子stackoverflow.com/a/7630564/1116573

标签: ruby-on-rails-3 activerecord sql-order-by distinct-on


【解决方案1】:

您正在尝试获取一组项目,但您从current_user.tasks 开始。

为什么不从保证不同项目的current_user.projects 开始呢?

@projects = current_user.projects.includes(:tasks).order("projects.name, tasks.milestone")

替代答案

@projects = current_user.projects.joins(:tasks).select('projects.*, min(tasks.milestone) as next_milestone').order('projects.name').group('projects.id')
@projects.each{|p| puts "#{p.name} #{p.next_milestone}"}

这将为每个项目提供一行,计算出的最小 tasks.milestone 值,可通过 next_milestone 在项目行结果中访问。没有额外的任务记录,只是下一个里程碑日期。

【讨论】:

  • 这可能会起作用,除非每个项目都有多个任务并且每个任务都有一个里程碑日期。我需要表格来显示下一个里程碑日期,我认为如果不对每一行/记录进行单独的数据库查找,您的建议是不允许的。
  • 不,您对每行/记录的单独数据库查找是错误的。 includes(:tasks) 保证您在一个查询中获得所有项目的所有任务。但是,我明白你所说的需要下一个里程碑日期......让我考虑一下。您希望避免使用任何昂贵的 ruby​​(或其他查询)来确定哪个任务具有下一个里程碑日期...
  • 我刚刚测试了我的方法并且它有效。 includes(:tasks)order("tasks.milestone") 的组合意味着您将 project.tasks 按里程碑顺序加载到内存中。这意味着您可以可靠地请求 project.tasks.first 以获取具有最早里程碑的任务或 project.tasks.last 以获取具有最新里程碑的任务。
  • 嗨,卡洛斯·德鲁,谢谢。我刚刚试了一下,但后来我意识到我还需要重新排序表格的能力。如果我想使用以下参数重新编排表格,您能否告知您的.order() 将如何变化#{sort_column} #{sort_direction} 其中sort_column 可以是projects.nametasks.milestoneprojects.description(正在显示另一列)。谢谢。
  • 我给你的是一个有效的 ActiveRecord 关系,这意味着你可以附加任何进一步的 ActiveRecord 分页逻辑,例如 will_paginate:github.com/mislav/will_paginate
【解决方案2】:

在用户控制器中:

inner_query = current_user.tasks.next.to_sql
@projects = Task.paginate_by_sql("select * from (#{inner_query}) as user_projects order by user_projects.#{sort_column} #{sort_direction}", :page => params[:page])

在任务模型中:

scope :next, select("distinct on (projects.id) projects.*, tasks.*").reorder("projects.id, tasks.milestone ASC")

这种方式利用 postgres 的强大功能仅返回必要的记录,从而使记录集更小且更易于使用,但代价是 RoR 代码看起来不像 Carlos Drew 的那样吸引人或可读建议。

【讨论】:

  • 老实说,我不希望在我的项目中使用该代码,因为它对我来说不可读或不可维护。我也和其他程序员一起工作,我不想让他们理解或依赖它。我发现@projects = Task.etc. 是特别糟糕的代码。项目不是任务。如果这是我能找到的唯一方法来做我想做的事,我会怀疑我解决问题的方法是否正确,并尝试重新考虑这个问题。
  • 例如,如果您发现项目真正关心知道下一个里程碑日期,我可能会找到一种方法将该值直接缓存在项目对象,而不是依赖于复杂的、多部分的、不可读的查询。
  • 但是,最后,我没有发现在项目中加载许多任务但不使用它们的问题是一个真正的问题。
  • 我添加了一个替代解决方案,它根本不返回任何任务,而只是返回下一个任务里程碑。
【解决方案3】:

回答这个问题:

我想我的问题只是如何将 activerecord 查询包装在周围的 SELECT 和 ORDER B 中

从 ActiveRecord 4.0.2 开始,现在有 <model>.from

使用您的模型的示例:

inner_query = Project.joins(:tasks).select("DISTINCT ON (projects.id), *") // SELECT DISTINCT ON (projects.id) FROM projects INNER JOIN tasks ON tasks.project_id = projects.id;

你可以用from:

sorted_query = Project.from(inner_query, :projects).order(:name)

【讨论】:

  • 这个答案是赢家!它使 ActiveRecord::Relation 保持活力,因此我的分页仍在使用此解决方案。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-11-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多