You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 16:32:49