如何通过其中一个属性找到关联的表最旧记录过滤?
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
Version
s。
Version
有一个 status
、plan_id
和 authorized_at
日期。
对Subscription
所做的任何更改都来自Version
修改更新它的父Subscription
。
目标是找到每个订阅的Version
最旧的authorized_at
日期WHERE
versions.plan_id
与subscriptions.plan_id
相同(换句话说,我需要Version
的授权日期,其中plan_id
更改为当前的Subscription
的plan_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
如果您只想选择 id
和 current_version
列,您应该考虑使用 pluck 而不是选择未正确水合的数据库模型。
【讨论】:
以上是关于如何通过其中一个属性找到关联的表最旧记录过滤?的主要内容,如果未能解决你的问题,请参考以下文章