如何替代已废弃postgres_ext的from_cte方法构建带CTE的查询?
Got it, let's walk through how to replace the deprecated from_cte method from postgres_ext with native ActiveRecord/Arel tools—since modern Rails has solid built-in support for CTEs now, you won't need that third-party gem anymore.
核心思路:用ActiveRecord原生with方法定义CTE
Starting with Rails 6+, ActiveRecord added native support for CTEs via the with method. This lets you define your complex CTE (with DISTINCT, JOIN, etc.) first, then build your final relation on top of it—all in a single query, which keeps count/sort operations working as expected.
Step 1: Build your base CTE query
First, create the complex query you need for your CTE (with all the DISTINCT, joins, filters you require):
# 示例:包含DISTINCT和JOIN的CTE查询 base_cte = User.select("DISTINCT users.id, users.name, orders.total") .joins(:orders) .where(orders: { status: "completed" })
Step 2: 定义CTE并构建最终关联关系
用with方法注册你的CTE,然后在它之上构建rel2关联。你可以像操作普通ActiveRecord关联一样,继续链式调用join、排序、count等方法:
# 定义CTE(给它起个有意义的名字,比如"active_users_with_orders") cte_relation = User.with(active_users_with_orders: base_cte) # 从CTE出发构建rel2,按需添加关联、排序 rel2 = cte_relation.from("active_users_with_orders") .joins(:profile) # 可以用模型已定义的关联,也可以用原生SQL join .order("active_users_with_orders.created_at DESC") # 正常调用count——ActiveRecord会自动处理CTE逻辑 rel2.count # 更精准的统计(比如去重ID的数量) rel2.select("COUNT(DISTINCT active_users_with_orders.id)").first["count"]
针对旧版Rails(<6):用Arel手动构建CTE
如果你还在使用不支持with方法的旧版Rails,可以用Arel(ActiveRecord底层的SQL构建器)直接创建CTE。虽然写法更繁琐,但同样可靠:
# 获取Arel表引用 users_table = User.arel_table orders_table = Order.arel_table # 用Arel构建CTE查询 cte_query = users_table.project(users_table[:id].distinct, users_table[:name], orders_table[:total]) .join(orders_table) .on(users_table[:id].eq(orders_table[:user_id])) .where(orders_table[:status].eq("completed")) # 创建CTE节点 cte = Arel::Nodes::As.new(Arel::Nodes::SqlLiteral.new("active_users_with_orders"), cte_query) # 基于CTE构建主查询 main_query = users_table.project(Arel.star) .with(cte) .from("active_users_with_orders") .join(Profile.arel_table) .on(users_table[:id].eq(Profile.arel_table[:user_id])) .order(users_table[:created_at].desc) # 转换为ActiveRecord Relation rel2 = User.from(main_query)
关键注意事项
- CTE命名: 给CTE起唯一、清晰的名字,避免SQL冲突。
- SQL注入: 使用原生SQL字符串写join/where条件时,一定要 sanitize 输入,或者优先用Arel方法构建安全的条件。
- 关联兼容性: 如果用模型已有关联配合CTE查询,要确保关联字段和CTE的列名匹配(因为你查询的是CTE,不是原表)。
这种方案能把所有逻辑放在单个查询里,支持count、排序等操作,和你之前简化场景下的rel2表现一致,还摆脱了对废弃gem的依赖。
内容的提问来源于stack exchange,提问作者svoop

