Rails中如何从参数化SQL查询返回ActiveRecord Relation
我有一个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 );
在一个主要返回measurements的API场景中,我需要导出用户筛选的measurements所属的所有video event bundles。我已有通用方法根据用户筛选条件将@measurements设为ActiveRecord Relation,例如:
@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 Relation,在未筛选measurements时数组会非常庞大。由于API是分页的,我需要传递limit/offset参数,因此必须返回ActiveRecord Relation。
我尝试了以下代码但出现语法错误:
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(…)来解决该问题?
完全可以通过ActiveRecord的关联和链式查询来构建ActiveRecord Relation,不用手写原生SQL绕弯路,具体做法如下:
1. 确认模型关联配置正确
先把三个模型的关联关系定义好,这样ActiveRecord才能正确解析关联查询:
# app/models/measurement.rb class Measurement < ApplicationRecord belongs_to :customer has_one :video_measurement, as: :actable, dependent: :destroy end # app/models/video_measurement.rb class VideoMeasurement < ApplicationRecord belongs_to :actable, polymorphic: true has_many :video_event_bundles, foreign_key: :video_measurement_id, dependent: :destroy end # app/models/video_event_bundle.rb class VideoEventBundle < ApplicationRecord belongs_to :video_measurement end
2. 基于现有@measurements构建查询
利用已筛选好的@measurements Relation,直接通过链式join和where条件获取对应的VideoEventBundle:
@video_event_bundles = VideoEventBundle.joins(video_measurement: :actable) .where(actable: { id: @measurements.select(:id), actable_type: 'VideoMeasurement' })
如果觉得嵌套关联写起来别扭,也可以用显式join语句:
@video_event_bundles = VideoEventBundle.joins("JOIN video_measurements ON video_event_bundles.video_measurement_id = video_measurements.id") .joins("JOIN measurements ON video_measurements.id = measurements.actable_id") .where(measurements: { id: @measurements }) .where("measurements.actable_type = 'VideoMeasurement'")
这两种写法都会返回ActiveRecord Relation,你可以直接在后面追加分页参数,比如:
@video_event_bundles = @video_event_bundles.limit(20).offset(40)
3. 之前的错误原因
你之前尝试的代码里,把参数绑定到子查询内部的占位符时语法不对。ActiveRecord的from方法没办法直接处理子查询里的?占位符,而用链式join的方式,ActiveRecord会自动帮你处理参数绑定和SQL拼接,自然就不会出现语法错误了。
内容的提问来源于stack exchange,提问作者slhck

