【问题标题】:How to query the results of a query in rails (query the results of a 'DISTINCT ON' with rails & postgres如何在 rails 中查询查询结果(使用 rails & postgres 查询 'DISTINCT ON' 的结果
【发布时间】:2015-10-17 16:19:29
【问题描述】:

短版: 我想查询另一个查询的结果,以便选择更有限的结果集。但是,添加 where 子句会重写第一个查询,而不是处理结果,所以我没有得到我需要的答案。

详情: 我有两个模型,检查和刻度。检查 has_many 记号。

第一个查询使用 DISTINCT ON 并收集所有“检查”和所有相关的分时,但只返回最近的分时。我把它作为模型中的一个范围。

在我的控制器中,

  def checklist
  #Filter the results by scope or return all checks with latest tick
  case params[:filter]
    when "duebylastresult"
      @checks = Check.mostrecenttickonly.duebylastresult
    when "duebydate"
      @checks = Check.mostrecenttickonly.duebydate
    else
      @checks = Check.mostrecenttickonly
    end
  end

在模型中,第一个作用域(工作):

scope :mostrecenttickonly, -> {
includes(:ticks)
.order("checks.id, ticks.created_at DESC")
.select("DISTINCT ON (checks.id) *").references(:ticks)
}

生成以下 SQL:

  Parameters: {"filter"=>""}
  SQL (1.0ms)  SELECT DISTINCT ON (checks.id) *, 
"checks"."id" AS t0_r0, 
"checks"."area" AS t0_r1, "checks"."frequency" AS t0_r2, 
"checks"."showinadvance" AS t0_r3, "checks"."category" AS t0_r4, 
"checks"."title" AS t0_r5, "checks"."description" AS t0_r6, 
"checks"."created_at" AS t0_r7, "checks"."updated_at" AS t0_r8, 
"ticks"."id" AS t1_r0, "ticks"."result" AS t1_r1, 
"ticks"."comments" AS t1_r2, "ticks"."created_at" AS t1_r3, 
"ticks"."updated_at" AS t1_r4, "ticks"."check_id" AS t1_r5 
FROM "checks" LEFT OUTER JOIN "ticks" 
ON "ticks"."check_id" = "checks"."id"  
ORDER BY checks.id, ticks.created_at DESC

得到该结果后,我只想显示值等于或大于 3 的刻度,因此范围:

   scope :duebylastresult, -> { where("ticks.result >= 3") }

生成 SQL

  Parameters: {"filter"=>"duebylastresult"}
  SQL (1.0ms)  SELECT DISTINCT ON (checks.id) *, 
"checks"."id" AS t0_r0, 
"checks"."area" AS t0_r1, "checks"."frequency" AS t0_r2,
"checks"."showinadvance" AS t0_r3, "checks"."category" AS t0_r4, 
"checks"."title" AS t0_r5, "checks"."description" AS t0_r6, 
"checks"."created_at" AS t0_r7, "checks"."updated_at" AS t0_r8, 
"ticks"."id" AS t1_r0, "ticks"."result" AS t1_r1, 
"ticks"."comments" AS t1_r2, "ticks"."created_at" AS t1_r3, 
"ticks"."updated_at" AS t1_r4, "ticks"."check_id" AS t1_r5 
FROM "checks" LEFT OUTER JOIN "ticks" 
ON "ticks"."check_id" = "checks"."id" 
WHERE (ticks.result >= 3)  
ORDER BY checks.id, ticks.created_at DESC

据我所知,WHERE 语句在 DISTINCT ON 子句之前执行,所以我现在有“结果为 >= 3 的最新刻度”,而我正在寻找“最新刻度 THEN”结果是 >= 3'。

希望这是有道理的,并在此先感谢!

编辑 - 我得到什么和我需要什么的例子:

The Data:
Table Checks:
ID: 98 Title: Eire
ID: 99 Title: Land

Table Ticks:
ID: 1 CheckID: 98 Result:1 Date: Jan12
ID: 2 CheckID: 98 Result:5 Date: Feb12
ID: 3 CheckID: 98 Result:1 Date: Mar12
ID: 4 CheckID: 99 Result:4 Date: Apr12

First query returns the most recent result, like;
Check.ID: 98  Tick.ID: 3  Tick.Result: 1 Tick.Date: Mar12
Check.ID: 99  Tick.ID: 4  Tick.Result: 4 Tick.Date: Apr12

Second query currently returns the most recent result where the result is =>3, like;
Check.ID: 98  Tick.ID: 2  Tick.Result: 5 Tick.Date: Feb12
Check.ID: 99  Tick.ID: 4  Tick.Result: 5 Tick.Date: Apr12

When I really want:
Check.ID: 99  Tick.ID: 4  Tick.Result: 5 Tick.Date: Apr12

(ID 98 doesn't show as the last Tick.Result is 1).

【问题讨论】:

  • 您能否举例说明现有查询的结果与所需查询的结果有何不同?
  • 谢谢@RobWise,示例已添加。

标签: ruby-on-rails postgresql


【解决方案1】:

您可以尝试以下方法,看看它是否能让您朝着正确的方向前进:

    scope :just_a_test, -> {
    includes(:ticks)
    .order("checks.id")
    .where("ticks.created_at = (SELECT MAX(ticks.created_at) FROM ticks WHERE ticks.check_id = checks.id)")
    .where("ticks.result >= 3")
    .group("checks.id")
    }

【讨论】:

  • 好吧,我试过你编辑之前的版本,范围:mostrecenttickonly,-> { includes(:ticks) .order("checks.id") .where("ticks.created_at = (SELECT MAX (ticks.created_at) FROM ticks WHERE ticks.check_id = checks.id)") } 这给出了一个错误,所以添加了 ".references(:ticks)".... 看起来一切正常!我会在早上进一步测试这个以及你的新建议,但很可能我会将此标记为答案 - 非常感谢!
  • 当前版本是否给您想要的最终结果?如果是这样,我认为您可以删除第二个 where 子句并将其用于 mostrecenttickonly
  • 感谢@laertiades,已接受答案。我还编辑了您的答案以添加与 .references 一起使用的代码(目前正在等待同行评审)。再次感谢!
【解决方案2】:

我不确定我是否真的理解 :mostrecenttickonly 范围的意义,因为您只是在加载检查。

话虽如此,如果您只想获得最近一次报价大于三的支票,我认为最好的方法是window function

check.rb

...
  scope :duebylastresult, -> {
    find_by_sql(
      'SELECT *
       FROM (SELECT checks.*,
                    ticks.id AS tick_ids,
                    ticks.date AS tick_date,
                    ticks.result AS tick_result,
                    dense_rank() OVER (
                      PARTITION BY checks.id
                      ORDER BY ticks.date DESC
                    ) AS tick_rank
             FROM checks
             LEFT OUTER JOIN ticks ON checks.id = ticks.check_id) AS ranked_ticks
       WHERE tick_rank = 1 AND tick_result >= 3;'
    )
  }
...

基本上,我们只是加入检查表和刻度表中的所有内容,然后添加另一个名为 tick_rank 的属性,该属性根据其 date 与具有相同 @987654326 的其他行对结果集中的每一行进行排名@ 价值。

SQL 的工作方式是在评估 SELECT 字段之前评估谓词(WHERE 子句中的条件),这意味着我们不能在此语句中只写 tick_rank = 1

所以我们必须执行包装结果的额外步骤(我们将其别名为ranked_ticks),然后选择所有内容并将我们想要的谓词应用于这个外部选择语句。 tick_rank 必须是 1,这意味着它是最新的 tick,结果必须 >= 3。


编辑: 因为我经常忘记 SQL 语法,所以我使用我链接的那篇文章作为复习,但是在查看它之后,我认为这会更好一些(基本上只是等待加入 @ 987654334@ 直到分区完成,这样我相信它会减少全扫描):

  scope :duebylastresult, -> {
    find_by_sql(
      'SELECT *
       FROM checks
       LEFT OUTER JOIN 
            (SELECT id AS tick_id,
                    check_id AS check_id,
                    date AS tick_date, 
                    result AS tick_result,
                    dense_rank() OVER (
                      PARTITION BY ticks.check_id
                      ORDER BY ticks.date DESC
                    ) AS tick_rank
             FROM ticks) AS ranked_ticks ON checks.id = ranked_ticks.check_id
       WHERE tick_rank = 1 AND tick_result >= 3;'
    )
  }

【讨论】:

  • 感谢@RobWise 的补充解释,他们帮助我提高了对 sql 的理解。拥有 :mostrecentticksonly 范围的原因是该视图显示了所有内容,然后用户可以选择不同的视图。通过分两步完成每个视图,我更容易理解它们。
猜你喜欢
  • 2016-02-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-08
  • 1970-01-01
  • 1970-01-01
  • 2016-09-11
相关资源
最近更新 更多