如何使用 ActiveRelation 编写 UNION 链?

发布于 2024-12-14 05:47:46 字数 289 浏览 5 评论 0原文

我需要能够使用 ActiveRelation 将任意数量的子选择与 UNION 链接起来。

我对 ARel 的实现有点困惑,因为它似乎假设 UNION 是一个二进制操作。

但是:

( select_statement_a ) UNION ( select_statement_b ) UNION ( select_statement_c )

是有效的 SQL。这是否可以在不进行令人讨厌的字符串替换的情况下实现?

I need to be able to chain an arbitrary number of sub-selects with UNION using ActiveRelation.

I'm a little confused by the ARel implementation of this, since it seems to assume UNION is a binary operation.

However:

( select_statement_a ) UNION ( select_statement_b ) UNION ( select_statement_c )

is valid SQL. Is this possible without doing nasty string-substitution?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

爱你不解释 2024-12-21 05:47:46

尽管亚当·拉塞克(Adam Lassek)走在正确的轨道上,但你可以做得比他提出的更好一点。我刚刚解决了一个类似的问题,试图从社交网络模型中获取朋友列表。可以通过各种方式自动获取好友,但我希望有一个 ActiveRelation 友好的查询方法来处理进一步的链接。所以我有

class User
    has_many :events_as_owner, :class_name => "Event", :inverse_of => :owner, :foreign_key => :owner_id, :dependent => :destroy
    has_many :events_as_guest, :through => :invitations, :source => :event

      def friends


        friends_as_guests = User.joins{events_as_guest}.where{events_as_guest.owner_id==my{id}}
        friends_as_hosts  = User.joins{events_as_owner}.joins{invitations}.where{invitations.user_id==my{id}}

        User.where do
          (id.in friends_as_guests.select{id}
          ) | 
          (id.in friends_as_hosts.select{id}
          )
        end
       end

end

它利用 Squeels 子查询支持。生成的 SQL 是

SELECT "users".* 
FROM   "users" 
WHERE  (( "users"."id" IN (SELECT "users"."id" 
                           FROM   "users" 
                                  INNER JOIN "invitations" 
                                    ON "invitations"."user_id" = "users"."id" 
                                  INNER JOIN "events" 
                                    ON "events"."id" = "invitations"."event_id" 
                           WHERE  "events"."owner_id" = 87) 
           OR "users"."id" IN (SELECT "users"."id" 
                               FROM   "users" 
                                      INNER JOIN "events" 
                                        ON "events"."owner_id" = "users"."id" 
                                      INNER JOIN "invitations" 
                                        ON "invitations"."user_id" = 
                                           "users"."id" 
                               WHERE  "invitations"."user_id" = 87) )) 

一种替代模式,您需要可变数量的组件,通过对上述代码进行轻微修改来演示

  def friends


    friends_as_guests = User.joins{events_as_guest}.where{events_as_guest.owner_id==my{id}}
    friends_as_hosts  = User.joins{events_as_owner}.joins{invitations}.where{invitations.user_id==my{id}}

    components = [friends_as_guests, friends_as_hosts]

    User.where do
      components = components.map { |c| id.in c.select{id} }
      components.inject do |s, i|
        s | i
      end
    end


  end

,这里是对 OP 确切问题的解决方案的粗略猜测

class Shift < ActiveRecord::Base
  def self.limit_per_day(options = {})
    options[:start]   ||= Date.today
    options[:stop]    ||= Date.today.next_month
    options[:per_day] ||= 5

    queries = (options[:start]..options[:stop]).map do |day|

      where{|s| s.scheduled_start >= day}.
      where{|s| s.scheduled_start < day.tomorrow}.
      limit(options[:per_day])

    end

    where do
      queries.map { |c| id.in c.select{id} }.inject do |s, i|
        s | i
      end
    end
  end
end

You can do a bit better than what Adam Lassek has proposed though he is on the right track. I've just solved a similar problem trying to get a friends list from a social network model. Friends can be aquired automatically in various ways but I would like to have an ActiveRelation friendly query method that can handle further chaining. So I have

class User
    has_many :events_as_owner, :class_name => "Event", :inverse_of => :owner, :foreign_key => :owner_id, :dependent => :destroy
    has_many :events_as_guest, :through => :invitations, :source => :event

      def friends


        friends_as_guests = User.joins{events_as_guest}.where{events_as_guest.owner_id==my{id}}
        friends_as_hosts  = User.joins{events_as_owner}.joins{invitations}.where{invitations.user_id==my{id}}

        User.where do
          (id.in friends_as_guests.select{id}
          ) | 
          (id.in friends_as_hosts.select{id}
          )
        end
       end

end

which takes advantage of Squeels subquery support. Generated SQL is

SELECT "users".* 
FROM   "users" 
WHERE  (( "users"."id" IN (SELECT "users"."id" 
                           FROM   "users" 
                                  INNER JOIN "invitations" 
                                    ON "invitations"."user_id" = "users"."id" 
                                  INNER JOIN "events" 
                                    ON "events"."id" = "invitations"."event_id" 
                           WHERE  "events"."owner_id" = 87) 
           OR "users"."id" IN (SELECT "users"."id" 
                               FROM   "users" 
                                      INNER JOIN "events" 
                                        ON "events"."owner_id" = "users"."id" 
                                      INNER JOIN "invitations" 
                                        ON "invitations"."user_id" = 
                                           "users"."id" 
                               WHERE  "invitations"."user_id" = 87) )) 

An alternative pattern where you need a variable number of components is demonstrated with a slight modification to the above code

  def friends


    friends_as_guests = User.joins{events_as_guest}.where{events_as_guest.owner_id==my{id}}
    friends_as_hosts  = User.joins{events_as_owner}.joins{invitations}.where{invitations.user_id==my{id}}

    components = [friends_as_guests, friends_as_hosts]

    User.where do
      components = components.map { |c| id.in c.select{id} }
      components.inject do |s, i|
        s | i
      end
    end


  end

And here is a rough guess as to the solution for the OP's exact question

class Shift < ActiveRecord::Base
  def self.limit_per_day(options = {})
    options[:start]   ||= Date.today
    options[:stop]    ||= Date.today.next_month
    options[:per_day] ||= 5

    queries = (options[:start]..options[:stop]).map do |day|

      where{|s| s.scheduled_start >= day}.
      where{|s| s.scheduled_start < day.tomorrow}.
      limit(options[:per_day])

    end

    where do
      queries.map { |c| id.in c.select{id} }.inject do |s, i|
        s | i
      end
    end
  end
end
霓裳挽歌倾城醉 2024-12-21 05:47:46

由于 ARel 访问者生成联合的方式,我在使用 Arel::Nodes::Union 时不断收到 SQL 错误。看起来老式的字符串插值是实现此功能的唯一方法。

我有一个轮班模型,我想获取给定日期范围内的轮班集合,每天仅限五个班次。这是 Shift 模型上的一个类方法:(

def limit_per_day(options = {})
  options[:start]   ||= Date.today
  options[:stop]    ||= Date.today.next_month
  options[:per_day] ||= 5

  queries = (options[:start]..options[:stop]).map do |day|

    select{id}.
    where{|s| s.scheduled_start >= day}.
    where{|s| s.scheduled_start < day.tomorrow}.
    limit(options[:per_day])

  end.map{|q| "( #{ q.to_sql } )" }

  where %{"shifts"."id" in ( #{queries.join(' UNION ')} )}
end

ActiveRecord 之外,我还使用 Squeel

除了 诉诸字符串插值很烦人,但至少用户提供的参数得到了正确的清理。我当然会感谢让这个更干净的建议。

Because of the way the ARel visitor was generating the unions, I kept getting SQL errors while using Arel::Nodes::Union. Looks like old-fashioned string interpolation was the only way to get this working.

I have a Shift model, and I want to get a collection of shifts for a given date range, limited to five shifts per day. This is a class method on the Shift model:

def limit_per_day(options = {})
  options[:start]   ||= Date.today
  options[:stop]    ||= Date.today.next_month
  options[:per_day] ||= 5

  queries = (options[:start]..options[:stop]).map do |day|

    select{id}.
    where{|s| s.scheduled_start >= day}.
    where{|s| s.scheduled_start < day.tomorrow}.
    limit(options[:per_day])

  end.map{|q| "( #{ q.to_sql } )" }

  where %{"shifts"."id" in ( #{queries.join(' UNION ')} )}
end

(I am using Squeel in addition to ActiveRecord)

Having to resort to string-interpolation is annoying, but at least the user-provided parameters are being sanitized correctly. I would of course appreciate suggestions to make this cleaner.

左耳近心 2024-12-21 05:47:46

我喜欢斯奎尔。但不要使用它。所以我找到了这个解决方案(Arel 4.0.2)

def build_union(left, right)
  if right.length > 1
    Arel::Nodes::UnionAll.new(left, build_union(right[0], right[1..-1]))
  else
    Arel::Nodes::UnionAll.new(left, right[0])
  end
end

managers = [select_manager_1, select_manager_2, select_manager_3]
build_union(managers[0], managers[1..-1]).to_sql
# => ( (SELECT table1.* from table1)
#    UNION ALL
#    ( (SELECT table2.* from table2)
#    UNION ALL
#    (SELECT table3.* from table3) ) )

I like Squeel. But don't use it. So I came to this solution (Arel 4.0.2)

def build_union(left, right)
  if right.length > 1
    Arel::Nodes::UnionAll.new(left, build_union(right[0], right[1..-1]))
  else
    Arel::Nodes::UnionAll.new(left, right[0])
  end
end

managers = [select_manager_1, select_manager_2, select_manager_3]
build_union(managers[0], managers[1..-1]).to_sql
# => ( (SELECT table1.* from table1)
#    UNION ALL
#    ( (SELECT table2.* from table2)
#    UNION ALL
#    (SELECT table3.* from table3) ) )
轻许诺言 2024-12-21 05:47:46

有一种方法可以使用 arel 来完成这项工作:

tc=TestColumn.arel_table
return TestColumn.where(tc[:id]
           .in(TestColumn.select(:id)
                         .where(:attr1=>true)
                         .union(TestColumn.select(:id)
                                          .select(:id)
                                          .where(:attr2=>true))))

There's a way to make this work using arel:

tc=TestColumn.arel_table
return TestColumn.where(tc[:id]
           .in(TestColumn.select(:id)
                         .where(:attr1=>true)
                         .union(TestColumn.select(:id)
                                          .select(:id)
                                          .where(:attr2=>true))))
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文