提问人:slhck 提问时间:11/9/2023 最后编辑:slhck 更新时间:11/9/2023 访问量:40
使用参数化查询从原始 SQL 查询返回 ActiveRecord 关系以加快响应速度
Return ActiveRecord Relation from a raw SQL query using parametrized queries to speed up response
问:
我有一个名为 Rails 的模型,它包含单个事件。这通过外键与模型相关联,并且模型本身是 的可能的多态子模型之一。VideoEventBundle
VideoMeasurement
VideoMeasurement
Measurement
SQL 架构:
CREATE TABLE public.video_event_bundles (
id bigint NOT NULL,
video_measurement_id bigint,
events jsonb DEFAULT '[]'::jsonb
);
CREATE TABLE public.video_measurements (
id bigint NOT NULL,
);
CREATE TABLE public.measurements (
id bigint NOT NULL,
customer_id bigint NOT NULL,
actable_type character varying,
actable_id bigint
);
现在,在主要返回测量值的 API 上下文中,我还想导出属于用户选择/过滤的测量值的所有视频事件包。为此,我有一个通用方法,该方法根据用户的过滤器设置为ActiveRecord关系。例如:@measurements
@measurements = Measurement.where(customer_id: params[:customer_id])
为了获取实际的事件包,我曾经有这个查询:
VideoEventBundle.find_by_sql([
"SELECT video_event_bundles.*
FROM video_event_bundles
JOIN video_measurements ON video_event_bundles.video_measurement_id = video_measurements.id
JOIN measurements ON video_measurements.id = measurements.actable_id
WHERE measurements.actable_type = 'VideoMeasurement'
AND measurements.id IN (?)
",
@measurements.select(:id)
])
问题在于,这返回的是对象数组,而不是 ActiveRecord 关系。问题在于,在未经滤波的测量的情况下,该阵列将是巨大的。由于 API 是分页的,因此我需要能够将 limit/offset 传递给该方法,因此,我必须实际返回 ActiveRecord 关系。
现在,我在这里看到了多个与从原始SQL查询返回它有关的问题:
- Rails - 如何将find_by_sql(数组)的输出转换为ActiveRecord关系?
- 如何在 Rails 中链接原始 SQL 查询,或者如何从 Rails 中的原始 SQL 查询返回ActiveRecord_Relation?
- find_by_sql返回 activeRecords 关系的内容
但是我无法设法使用该参数来实际替换 ID:(?)
sql = <<-SQL
SELECT video_event_bundles.*
FROM video_event_bundles
JOIN video_measurements ON video_event_bundles.video_measurement_id = video_measurements.id
JOIN measurements ON video_measurements.id = measurements.actable_id
AND measurements.id IN (?)
SQL
VideoEventBundle.select("*").from("(#{sql}) AS video_event_bundles", @measurements.select(:id))
这会引发错误:
ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: syntax error at or near ")"
LINE 5: AND measurements.id IN (?)
我想我无法通过开始查询来解决这个问题?@measurements.join(…)
答:
2赞
engineersmnky
11/9/2023
#1
应该可以只使用:
VideoEventBundle
.joins(video_measurement: :measurement)
.where(measurement: { id: @measurements.select(:id) })
如果没有,我们绝对可以使用 AR 的内置机制和一点点 Arel 来完成您需要的 SQL:
measurement_table = Measurement.arel_table
vm_table = VideoMeasurement.arel_table
measurement_join = measurement_table.join(vm_table)
.on(vm_table[:id].eq(measurement_table[:actable_id])
.and(measurement_table[:actable_type].eq('VideoMeasurement')))
VideoEventBundle
.joins(:video_measurement)
.joins(measurement_join)
.where(measurements: {id: @measurements.select(:id)})
这将导致
SELECT
video_event_bundles.*
FROM
video_event_bundles
INNER JOIN video_measurements
ON video_event_bundles.video_measurement_id = video_measurements.id
INNER JOIN measurements
ON video_measurements.id = measurements.actable_id
AND measurements.actable_type = 'VideoMeasurement'
WHERE
measurements.id IN (
SELECT
measurements.id
FROM
measurements
WHERE
measurements.customer_id = 1234
)
评论
0赞
slhck
11/9/2023
你的第一个建议给出了错误:——因为它(即 1:1 的关系)。当我把它改成时,我得到了正确的回应!因此,我现在要做的是,首先对 进行筛选和分页,然后使用该查询仅检索匹配的事件包。Can't join 'VideoEventBundle' to association named 'video_measurements'; perhaps you misspelled it?
belongs_to :video_measurement
VideoEventBundle.joins(video_measurement: :measurement).where(measurement: {id: @measurements.select(:id)})
@measurements
0赞
engineersmnky
11/9/2023
@slhck我会对整个查询的结果进行分页,因为这就是您返回的内容。此外,我不确定您的应用程序是如何构建的,但是在技术上没有必要进行预过滤,因为测量表是此查询的一部分。相反,您可以直接通过 WHERE 子句传递所有这些条件。例如 这将避免对子查询的需要。Measurement
IN()
where(measurement: { customer_id: params[:customer_id]})
0赞
slhck
11/10/2023
如果我把它放在 where 子句中,我想我将不得不复制代码;大多数其他路由都基于 作为主类。因此,我设置了十几个可能的过滤器/查询来获取,例如:等等。我不认为当现在测量是连接表时,我不能通用地做到这一点?measurement
@measurements
@measurements = @measurements.where(…) if params[:customer_id]
评论
ERROR: syntax error at or near "?"