求与指定SQL等效的高性能纯Ruby ActiveRecord实现方案
纯ActiveRecord替代硬编码SQL实现特定查询
需求与现有问题
需要实现的功能:查询关联特定shop_id的购物车(按created_at倒序取第1001-2000条)的客户的不重复城市,原SQL逻辑如下:
SELECT DISTINCT customers.city FROM customers INNER JOIN ( SELECT c_id FROM carts WHERE shop_id = #{`shop_id`} ORDER BY created_at DESC LIMIT 1000 OFFSET 1000 ) AS filtered_carts ON customers.id = filtered_carts.c_id;
当前采用的ActiveRecord写法包含硬编码SQL片段,存在模型变更(如迁移改表名、字段名)时的兼容风险:
customers = Customer .select(:city) .distinct .joins(" INNER JOIN ( SELECT c_id FROM carts WHERE shop_id = #{`shop_id`} ORDER BY created_at DESC LIMIT 1000 OFFSET 1000 ) AS filtered_carts ON customers.id = filtered_carts.c_id ")
纯ActiveRecord实现方案
方案1:基于子查询对象拼接(直观易读)
先通过ActiveRecord构建子查询,再将其SQL片段嵌入join语句,避免硬编码表名、字段名:
# 构建购物车子查询:筛选目标shop_id的第1001-2000条记录(按created_at倒序) filtered_carts_subquery = Cart.select(:c_id) .where(shop_id: shop_id) .order(created_at: :desc) .limit(1000) .offset(1000) # 关联子查询获取客户不重复城市 customers = Customer.select(:city) .distinct .joins("INNER JOIN (#{filtered_carts_subquery.to_sql}) AS filtered_carts ON customers.id = filtered_carts.c_id")
方案2:纯Arel构建关联(无任何SQL字符串)
利用Arel完全通过Ruby方法构建关联逻辑,彻底避免硬编码:
# 构建购物车子查询的Arel对象 cart_subquery = Cart.select(:c_id) .where(shop_id: shop_id) .order(created_at: :desc) .limit(1000) .offset(1000) .arel # 通过Arel构建Inner Join并执行查询 customers = Customer.select(:city) .distinct .joins( Customer.arel_table.join(cart_subquery, Arel::Nodes::InnerJoin) .on(Customer.arel_table[:id].eq(cart_subquery[:c_id])) .join_sources )
方案优势
两种方案均依赖ActiveRecord模型定义动态生成SQL,后续若修改Cart模型的表名、c_id字段名,或Customer模型的主键,查询会自动适配,无需手动修改SQL片段,解决了硬编码带来的兼容问题。
内容的提问来源于stack exchange,提问作者Da V
相关产品推荐
相关产品推荐

