【问题标题】:Build triple UNION query using Arel (Rails 5)使用 Arel (Rails 5) 构建三重 UNION 查询
【发布时间】:2019-12-11 21:09:33
【问题描述】:

user has_many tasks

我正在尝试创建一个 3 选择 UNION:

Task.from("(#{select1.to_sql} UNION #{select2.to_sql} UNION #{select3.to_sql}) AS tasks")

但是和 Arel 在一起。

我可以轻松地将 Arel 用于 UNION 查询进行 2 次选择:

tasks_t = Task.arel_table
select1 = tasks_t.project('id').where(tasks_t[:id].eq(1)) # SELECT id FROM tasks WHERE tasks.id = 1
select2 = Task.joins(:user).select(:id).where(users: {id: 15}).arel  # SELECT "tasks".id FROM "tasks" INNER JOIN "users" ON "users"."id" = "tasks"."user_id" WHERE "users"."id" = 15
union = select1.union(select2) # SELECT * FROM users WHERE users.id = 1 UNION SELECT * FROM users WHERE users.id = 2

union_with_table_alias = tasks_t.create_table_alias(union, tasks_t.name)
Task.from(union_with_table_alias)
# SELECT "tasks".* FROM ( SELECT id FROM "tasks" WHERE "tasks"."id" = 1 UNION SELECT "tasks"."id" FROM "tasks" INNER JOIN "users" ON "users"."id" = "tasks"."user_id" WHERE "users"."id" = $1 ) "tasks"  [["id", 15]]

#=> Task::ActiveRecord_Relation [#<Task: id: 1>, #<Task: id: 2>]

如何使用三重联合选择来做到这一点?

select3 = tasks_t.project('id').where(tasks_t[:id].eq(3)) # SELECT id FROM tasks WHERE tasks.id = 3

类似:

triple_union = select1.union(select2).union(select3)
triple_union_with_table_alias = tasks_t.create_table_alias(triple_union, tasks_t.name)
Task.from(triple_union_with_table_alias)

应该大致翻译成

Task.from("(#{select1.to_sql} UNION #{select2.to_sql} UNION #{select3.to_sql}) AS tasks")

请注意,上面的行也失败了: Caused by PG::ProtocolViolation: ERROR: bind message supplies 0 parameters, but prepared statement "" requires 1

总结一下: 如何使用Arel 构建三重UNION 查询? select 1 UNION select 2 UNION select 3

谢谢

Ruby 2.5.3Rails 5.2.4Arel 9.0.0

【问题讨论】:

    标签: ruby-on-rails postgresql ruby-on-rails-5 arel


    【解决方案1】:

    您不能在Arel::Nodes::Union 中调用union,因为在方法定义中调用了两个对象的ast。并且union 操作的结果没有响应ast

    def union operation, other = nil
      if other
        node_class = Nodes.const_get("Union#{operation.to_s.capitalize}")
      else
        other = operation
        node_class = Nodes::Union
      end
    
      node_class.new self.ast, other.ast
    end
    

    您可以做的是手动调用 Arel::Nodes::Union,将联合的结果作为这些参数中的任何一个传递:

    Arel::Nodes::Union.new(Arel::Nodes::Union.new(select1, select2), select3)
    

    【讨论】:

    • 1 陷阱-这仅适用于(至少对我而言)Arel 对象-不适用于 AR-因此对于我的示例中的 select2-通过 AR 创建-需要在其上调用 arel所以Task.joins(...).arel从AR对象转换为Arel select_manager对象
    • 那是因为没有在select2上调用arel,它是一个纯粹的Task::ActiveRecord_Relation,与Arel无关。您必须为此创建一个纯 Arel::SelectManager。试试users_t = User.arel_table; Arel::Nodes::Union.new( Arel::Nodes::Union.new( tasks_t.project('id').where(tasks_t[:id].eq(1)), tasks_t.project('id').where(tasks_t[:id].eq(3)) ), tasks_t.join(users_t).on(users_t[:id].eq(tasks_t[:user_id])).project('id').where(users_t[:id].eq(15)) )
    猜你喜欢
    • 2020-03-15
    • 2011-05-31
    • 1970-01-01
    • 2013-10-03
    • 2011-07-01
    • 2012-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多