我有一个名为
VideoEventBundle
的 Rails 模型,其中包含各个事件。这通过外键与 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 上下文中,我还想导出属于用户已选择/过滤的测量值的所有视频事件包。为此,我有一个通用方法,根据用户的过滤器将
@measurements
设置为 ActiveRecord 关系。例如:
@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 是分页的,我需要能够将限制/偏移量传递给该方法,因此,我必须实际返回一个 ActiveRecord 关系。
现在我在这里看到了多个与从原始 SQL 查询返回相关的问题:
但我无法设法使用
(?)
参数来实际替换 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(…)
开始查询来解决这个问题?
应该可以只使用:
VideoEventBundle
.joins(video_measurement: :measurement)
.where(measurement: { id: @measurements.select(:id) })
这里,关联都是 1:1,因此是单数连接名称。
如果没有,我们绝对可以使用 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_measurements)
.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
)