2011-11-08 25 views
14

Tôi cần có thể chuỗi số lượng tùy ý các lựa chọn phụ với UNION bằng cách sử dụng ActiveRelation.Làm cách nào để viết chuỗi UNION với ActiveRelation?

Tôi hơi bối rối bởi việc triển khai AREL về điều này, vì dường như giả sử UNION là một hoạt động nhị phân.

Tuy nhiên:

(select_statement_a) UNION (select_statement_b) UNION (select_statement_c) 

là SQL hợp lệ. Điều này có thể thực hiện mà không làm thay thế chuỗi khó chịu không?

Trả lời

11

Bạn có thể làm tốt hơn một chút so với những gì Adam Lassek đã đề xuất mặc dù anh ấy đi đúng hướng. Tôi vừa giải quyết một vấn đề tương tự khi cố gắng lấy danh sách bạn bè từ một mô hình mạng xã hội. Bạn bè có thể được mua tự động theo nhiều cách khác nhau nhưng tôi muốn có một phương thức truy vấn thân thiện với ActiveRelation có thể xử lý chuỗi tiếp theo. Vì vậy, tôi có

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 

lợi dụng hỗ trợ truy vấn con của Squeel. SQL được tạo ra là

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))) 

Một mô hình thay thế mà bạn cần có một số biến của các thành phần được thể hiện với một sửa đổi nhỏ vào mã ở trên

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 

Và đây là một rough đoán như các giải pháp cho các câu hỏi chính xác OP của

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 
+0

Tuy nhiên, đây là trường hợp hơi khác. Tôi cần để có thể chuỗi một số tùy ý của các lựa chọn với nhau. Vì vậy, tôi vẫn sẽ phải sử dụng đến nội suy chuỗi bất kể tôi có sử dụng 'UNION' hay' OR' hay không. Trừ khi bạn có thể đề xuất một cách để thực hiện điều đó với Squeel/AREL? –

+0

Điều đó có thể được thực hiện. Nó chỉ đòi hỏi một chút ma thuật. Để tôi nghi vê no. Có lẽ có thể được thực hiện với Enumerable tiêm. Tôi sẽ cung cấp cho nó một đi và cập nhật câu trả lời nếu nó hoạt động. – bradgonesurfing

+0

Ok. Tôi đã thực hiện một hàm tương đương để đặt các thành phần vào một mảng đầu tiên và sau đó kết hợp các thành phần đó.Chỉ cần nhớ rằng SQueel chỉ đơn giản là ruby ​​với một số phương pháp thiếu phép thuật. Bạn có thể làm ruby ​​bình thường bên trong khối và xây dựng truy vấn động. – bradgonesurfing

4

Do cách khách truy cập AREL tạo ra các công đoàn, tôi đã gặp lỗi SQL khi sử dụng Arel::Nodes::Union. Hình như nội suy chuỗi kiểu cũ là cách duy nhất để làm việc này.

Tôi có một mô hình Shift và tôi muốn có một bộ sưu tập các ca làm việc trong một phạm vi ngày nhất định, giới hạn trong năm ca mỗi ngày. Đây là một phương pháp học trên mô hình 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 

(Tôi đang sử dụng Squeel ngoài ActiveRecord)

Có phải nhờ đến chuỗi suy là gây phiền nhiễu, nhưng ít nhất các thông số do người dùng cung cấp là được khử trùng đúng cách. Dĩ nhiên tôi sẽ đánh giá cao các đề xuất để làm sạch hơn.

1

có một cách để làm cho công việc này bằng 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)))) 
+1

Tôi không nghĩ rằng bạn đã hiểu câu hỏi. Đó là một UNION duy nhất giữa hai truy vấn, hoạt động tốt. Tôi cần để có thể chuỗi UNIONs với nhau để chiều dài tùy ý. Có thể các ngữ nghĩa UNION khác nhau giữa các triển khai SQL - trong trường hợp này tôi đang sử dụng PostgreSQL. –

+0

Tôi đã làm, đây là gần nhất tôi đến để thực hiện một liên minh bằng cách sử dụng arel. –

+0

Dù sao, bạn có thể nhận được một Arel :: Node bằng cách thực hiện 'Domain.where (attr => value) .union (Domain.where (attr2 => value))' có thể bạn có thể sau đó 'to_sql' phương pháp và vượt qua nó để 'find_by_sql' –

3

Tôi thích Squeel. Nhưng đừng dùng nó. Vì vậy, tôi đã đến giải pháp này (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))) 
+0

Dường như họ đã thêm' Arel :: Nodes :: UnionAll' cho các công đoàn không nhị phân. Tôi sẽ xem lại điều này khi tôi có thể sử dụng Arel 4. –

+0

Cảm ơn rất nhiều! Tôi vẫn đang tìm kiếm điều này. –

Các vấn đề liên quan