如何从Active Record查询结果创建Arel::Table?
问题
我有A、B两张数据表,已经基于两表关联创建了一个复杂的Active::Record::Relation对象,通过ActiveRecord::Base.connection.exec_query joined.to_sql能正确执行查询,返回需要的A.id、A.title、b.num字段。
我想把这个关联查询的结果作为Arel::Table传入后续程序,但执行at_j=joined.arel_table时,生成的Arel表是基于原数据表A的,包含A的全部字段而非仅选中的字段,而且不包含B表的字段。
我尝试从筛选后的表创建Arel表:
filtered=A.select(:id,:title) at_f=filtered.arel_table
但at_f还是包含A表的c1、c2、c3等全部字段,不是仅id和title。
我知道可以用at_f=A.arel_table.project(:id,:title)实现,但这个方法返回的是Arel::SelectManager,而我需要的是Arel::Table。
另外,我不想直接用Arel构建查询,因为需要通过_select!和joins!修改传入的A表对象。我试过at_f=Arel::Table.new(filtered.to_sql)但失败了,有没有可行的方案?
附生成关联查询对象的代码:
A._select!(:id,:title,'b.num') bf=B.where(c1: 'x',c2: 'y') num=bf.select('id_2 AS A_id, COUNT(id_2) AS num').group(:id_2) A.joins!("LEFT OUTER JOIN (#{num.to_sql}) b ON A.id = b.A_id")
生成的SQL语句:
# A.to_sql: SELECT `A`.`id`, `A`.`title`, `b`.`num` FROM `A` LEFT OUTER JOIN (SELECT id_2 AS A_id, COUNT(id_2) AS num FROM `B` WHERE `B`.`c1` = 'x' AND `B`.`c2` = 'y' GROUP BY `B`.`id_2`) b ON A.id = b.A_id
解决方案
方法1:将查询结果包装为临时Arel表
Arel::Table本质映射物理表或视图,无法直接从ActiveRecord::Relation生成,但可以把现有查询结果当作临时表来创建Arel对象:
# 获取已构建好的查询SQL query_sql = A.to_sql # 创建命名临时表的Arel对象 joined_table = Arel::Table.new('joined_result') # 后续使用时,将临时表嵌套进新查询的FROM子句 new_query = Arel::SelectManager.new(joined_table) new_query.project(joined_table[:id], joined_table[:title], joined_table[:num]) .where(joined_table[:num].gt(0)) # 最终生成的SQL会自动嵌套原查询: # SELECT joined_result.id, joined_result.title, joined_result.num FROM (你的原查询SQL) joined_result WHERE joined_result.num > 0
方法2:手动定义Arel表的字段集合
如果不需要嵌套查询,只是需要一个仅包含指定字段的Arel::Table对象,可以手动覆盖默认字段:
# 基于原A表创建Arel对象 custom_arel_table = A.arel_table # 手动设置可用字段,只保留需要的id、title和关联的num custom_arel_table.columns = [:id, :title, :num].map { |col| Arel::Attribute.new(custom_arel_table, col) } # 后续使用时,仅能访问指定字段 custom_arel_table[:num] # 正常访问 custom_arel_table[:c1] # 报错,字段已被移除
方法3:用数据库视图持久化查询结构
如果该关联查询会被多次使用,可先创建数据库视图,再基于视图生成Arel表:
-- 创建视图 CREATE VIEW joined_A_B AS SELECT `A`.`id`, `A`.`title`, `b`.`num` FROM `A` LEFT OUTER JOIN (SELECT id_2 AS A_id, COUNT(id_2) AS num FROM `B` WHERE `B`.`c1` = 'x' AND `B`.`c2` = 'y' GROUP BY `B`.`id_2`) b ON A.id = b.A_id;
在Rails中创建对应模型(仅关联视图):
class JoinedAB < ApplicationRecord self.table_name = 'joined_A_B' end # 直接获取符合需求的Arel表 joined_arel_table = JoinedAB.arel_table
内容的提问来源于stack exchange,提问作者Francisco Herrerias-Azcue

