如何通过其中一个属性找到关联的表最旧记录过滤?

Posted

技术标签:

【中文标题】如何通过其中一个属性找到关联的表最旧记录过滤?【英文标题】:How can I find an associated table oldest record filtering by one of it's attributes? 【发布时间】:2022-01-02 01:09:50 【问题描述】:

我有 Subscription 的模型 has_many Versions。

Version 有一个 statusplan_idauthorized_at 日期。

Subscription 所做的任何更改都来自Version 修改更新它的父Subscription

目标是找到每个订阅的Version 最旧的authorized_at 日期WHERE versions.plan_idsubscriptions.plan_id 相同(换句话说,我需要Version 的授权日期,其中plan_id 更改为当前的Subscriptionplan_id)。

这是我想出的查询。我在聚合函数语法中遇到错误:

syntax error at or near "MIN" LINE 3: MIN (authorized_at) from versions ^

查询:

select subscriptions.id,
MIN (authorized_at) from versions
where versions.plan_id = subscriptions.plan_id
) as current_version
from subscriptions
join versions on subscriptions.id = versions.subscription_id
where versions.status = 'processed'

我也不确定是否应该按plan_id 对版本进行分组,然后从每个组中进行选择。我有点迷路了。

【问题讨论】:

【参考方案1】:

您可以使用DISTINCT ON 过滤掉行,并为每个订阅保留一个——根据ORDER BY 子句,每个组的第一个。

例如:

select distinct on (s.id) s.id, v.authorized_at
from subscription s
join versions v on v.subscription_id = s.id and v.plan_id = s.plan_id
where v.status = 'processed'
order by s.id, v.authorized_at

【讨论】:

谢谢。该查询似乎在控制台中工作,但是当我尝试在我的视图中将它用作模型范围时,我得到ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: syntax error at or near "WHERE" LINE 6: ...s.id, v.authorized_at WHERE "sub... @pinkfloyd90 我不是 ActiveRecord 方面的专家,但它可能不起作用,因为视图可能无法更新。【参考方案2】:

以下代码将为您提供versions,其中版本的plan_id 等于订阅的plan_id

@versions = Version.joins("LEFT JOIN subscriptions ON subscriptions.plan_id = versions.plan_id")

按版本的status过滤记录

@versions = Version.joins("LEFT JOIN subscriptions ON subscriptions.plan_id = versions.plan_id").where(status: "processed")

按版本的status 过滤记录,并按authorized_at 升序排列。

@versions = Version.joins("LEFT JOIN subscriptions ON subscriptions.plan_id = versions.plan_id").where(status: "processed").order(:authorized_at)

按版本的status 过滤记录并按authorized_at 降序排列。

@versions = Version.joins("LEFT JOIN subscriptions ON subscriptions.plan_id = versions.plan_id").where(status: "processed").order(authorized_at: :desc)

希望这对你有用!

【讨论】:

我认为您需要加入subscriptions.id = versions.subscription_id,否则您将在该结果中获得不属于该订阅的版本。您甚至还没有触及选择聚合问题的核心。【参考方案3】:

您可以使用lateral subquery,最好将其描述为SQL 中的foreach 循环。它们是从单个相关记录中选择列甚至从一组相关记录中聚合的一种非常高效的方法。

对于订阅中的每一行,数据库将从authorized_at 排序的版本中选择一行:

SELECT "subscriptions".*,
       "latest_version"."authorized_at" AS current_version,
       "latest_version"."id" AS current_version_id -- could be very useful
FROM   "subscriptions" 
LATERAL
  (
     SELECT   "versions"."authorized_at", "versions"."id"
     FROM     "versions"
     WHERE    "versions"."subscription_id" = "subscriptions"."id" -- lateral reference
     AND      "versions"."plan_id" = "subscriptions"."plan_id"
     AND      "versions"."status" = 'processed'
     ORDER BY "versions"."authorized_at" ASC
     LIMIT 1
  ) latest_version ON TRUE

可以使用 SQL 字符串或 Arel 在 ActiveRecord 中创建横向连接:

class Subscription < ApplicationRecord
  # Performs a lateral join and selects the 
  # authorized_at of the latest version 
  def self.with_current_version
    lateral = Version.arel_table.then do |v|
      v.project(
        v[:authorized_at],
        v[:id] # optional
      ).where(
        v[:subscription_id].eq(arel_table[:id])
          .and(v[:plan_id].eq(arel_table[:plan_id]) )
          .and(v[:status].eq('processed'))
      )
      .order(v[:authorized_at].asc)
      .take(1) # limit 1
      .lateral('latest_version ON TRUE')
    end
    lv = Arel::Table.new(:latest_version) # just a table alias
    select(
      *where(nil).arel.projections, # selects everything previously selected
      lv[:authorized_at].as("current_version"),
      lv[:id].as("current_version_id") # optional
    ).joins(lateral.to_sql)
  end
end

如果您只想选择 idcurrent_version 列,您应该考虑使用 pluck 而不是选择未正确水合的数据库模型。

【讨论】:

以上是关于如何通过其中一个属性找到关联的表最旧记录过滤?的主要内容,如果未能解决你的问题,请参考以下文章

Spark数据分析-记录关联问题

如何根据数据库中的表自动生成实体类

arcgis中属性表怎么关联之后保留多条记录

Sequelize - 如何提取具有 1 个关联记录的记录并按关联记录属性排序

详解亿级大数据表的几种建立分区表的方式

推荐算法之模型协同过滤(1)-关联规则