2010-05-24 40 views
19

Tôi đang cố gắng lồng truy vấn SELECT trong Arel và/hoặc bản ghi hoạt động trong Rails 3 để tạo câu lệnh SQL sau.Truy vấn lồng nhau trong Arel

SELECT sorted.* FROM (SELECT * FROM points ORDER BY points.timestamp DESC) AS sorted GROUP BY sorted.client_id 

Một bí danh cho các subquery có thể được tạo ra bằng cách làm

points = Table(:points) 
sorted = points.order('timestamp DESC').alias 

nhưng sau đó tôi bị mắc kẹt như làm thế nào để vượt qua nó vào truy vấn cha mẹ (viết tắt gọi #to_sql, mà âm thanh khá xấu xí) .

Làm thế nào để bạn sử dụng câu lệnh SELECT làm truy vấn phụ trong Arel (hoặc Active Record) để thực hiện điều này? Có thể có một cách hoàn toàn khác để thực hiện truy vấn này không sử dụng truy vấn lồng nhau?

Trả lời

8

Câu hỏi đặt ra là tại sao bạn cần "truy vấn lồng nhau"? Chúng tôi không cần phải sử dụng "truy vấn lồng nhau" này là suy nghĩ trong suy nghĩ của SQL không quan hệ đại số. Với đại số quan hệ chúng ta lấy được các mối quan hệ và sử dụng kết quả của một mối quan hệ như đầu vào khác nên sau đây sẽ giữ đúng:

points = Table(:points, {:as => 'sorted'}) # rename in the options hash 
final_points = points.order('timestamp DESC').group(:client_id, :timestamp).project(:client_id, :timestamp) 

Tốt nhất là nếu chúng ta rời khỏi đổi tên để Arel, trừ khi thật cần thiết.

Tại đây phép chiếu của client_id VÀ dấu thời gian là rất quan trọng vì chúng tôi không thể chiếu tất cả các tên miền từ quan hệ (tức là được sắp xếp. *). Bạn phải cụ thể dự án tất cả các tên miền sẽ được sử dụng trong hoạt động nhóm cho mối quan hệ. Lý do là không có giá trị cho * đó sẽ là đại diện rõ ràng của một client_id được nhóm lại. Ví dụ nói rằng bạn có bảng sau

client_id | score 
---------------------- 
    4  | 27 
    3  | 35 
    2  | 22 
    4  | 69 

đây nếu bạn nhóm bạn không thể thực hiện một dự báo về lĩnh vực điểm vì giá trị có thể thể là 27 hoặc 69 nhưng bạn có thể chiếu một sum (điểm)

Bạn chỉ có thể dự án các thuộc tính miền có các giá trị duy nhất cho nhóm (thường là các hàm tổng hợp như sum, max, min). Với truy vấn của bạn, nó sẽ không quan trọng nếu các điểm được sắp xếp theo dấu thời gian vì cuối cùng chúng sẽ được nhóm theo client_id. thứ tự dấu thời gian không liên quan vì không có dấu thời gian duy nhất có thể đại diện cho một nhóm.

Vui lòng cho tôi biết cách tôi có thể giúp bạn với Arel. Ngoài ra, tôi đã làm việc trên một chuỗi học tập để mọi người sử dụng Arel ở cốt lõi của nó. Đầu tiên của bộ truyện là http://Innovative-Studios.com/#pilot Tôi có thể nói rằng bạn đang bắt đầu biết làm thế nào kể từ khi bạn sử dụng Bảng (: điểm) thay vì Điểm mô hình ActiveRecord.

+0

Cảm ơn bạn đã trả lời chi tiết. "thứ tự dấu thời gian không liên quan vì không có dấu thời gian duy nhất có thể đại diện cho một nhóm". Bạn đúng; Tôi thấy những gì bạn đang nói. Nó xuất hiện MySQL hoạt động xung quanh sự không thống nhất này bằng cách trả về hàng đầu tiên của nhóm client_id, đó là những gì tôi đang nhắm tới. Tôi thấy bây giờ đây không phải là hành vi tôi nên dựa vào. Mục tiêu của tôi là trả lại điểm gần đây nhất cho tất cả các client_ids, nghĩa là một điểm duy nhất có dấu thời gian tối đa cho mỗi nhóm khách hàng. Điều quan trọng là phải làm trong một truy vấn bởi vì nó sẽ được thăm dò thường xuyên. – Schrockwell

+0

Chúng tôi sẽ cần sử dụng một số chức năng tổng hợp. Nếu chúng ta tự hỏi "Chúng ta đang cố gắng làm gì?" Câu trả lời sẽ là tìm ngày gần đây nhất hoặc "tối đa" để chúng tôi vượt qua tối đa (dấu thời gian) trong sql. Điều này sẽ tương ứng với Arel :: Attribute :: Expression :: Maximum có thể được gọi với cú pháp đường trên Arel :: Attribute như được sắp xếp [: timestamp] .maximum(). Có một báo trước. Đảm bảo rằng bạn thêm dấu thời gian vào nhóm hoạt động #group ('client_id, dấu thời gian') hoặc toàn bộ kịch bản nhóm sẽ bị lỗi. Tôi biết hàm tổng hợp MAX hoạt động vào các ngày trong Postgres và tôi cũng chắc chắn trong MySQL. – Snuggs

+1

Thứ nhất, sắp xếp và sắp xếp không phải là một phần của đại số quan hệ. Arel định nghĩa nó. Thứ hai, có hay không truy vấn phụ là một phần của đại số quan hệ là không liên quan. Về mặt khái niệm, kết quả của một lệnh SELECT không hiển thị cho đến khi mệnh đề WHERE thực thi. Do đó không phải tất cả các cơ sở dữ liệu (ví dụ Postgres) cho phép các bí danh cột trong mệnh đề WHERE và thay vào đó phụ thuộc vào các truy vấn phụ. Nếu Arel không thể xử lý các truy vấn con thì các tên trong mệnh đề WHERE không thể được đặt bí danh. Điều này có thể lộn xộn khi bạn không thể phụ thuộc vào Arel để tạo ra tên. –

7

Mặc dù tôi không nghĩ rằng vấn đề này cần truy vấn lồng nhau, như Snuggs được đề cập. Đối với những người cần truy vấn lồng nhau. Đây là những gì tôi đã làm việc cho đến nay, không tuyệt vời nhưng nó hoạt động:

class App < ActiveRecord::Base 
    has_many :downloads 

    def self.not_owned_by_users(user_ids) 
    where(arel_table[:id].not_in( 
     Arel::SqlLiteral.new(Download.from_users(user_ids).select(:app_id).to_sql))) 
    end 
end 

class Download < ActiveRecord::Base 
    belongs_to :app 
    belongs_to :user 

    def self.from_users(user_ids) 
    where(arel_table[:user_id].in user_ids) 
    end 

end 

class User < ActiveRecord::Base 
    has_many :downloads 
end 

App.not_owned_by_users([1,2,3]).to_sql #=> 
# SELECT `apps`.* FROM `apps` 
# WHERE (`apps`.`id` NOT IN (
# SELECT app_id FROM `downloads` WHERE (`downloads`.`user_id` IN (1, 2, 3)))) 
# 
+0

Giải pháp tinh khiết nhất, lừa là 'Arel :: SqlLiteral' ở đó –

+0

Chỉnh sửa nhỏ, thay vì sử dụng '' 'Arel :: SqlLiteral''', chính xác sẽ' '' Arel :: Nodes :: SqlLiteral''' – jonathanccalixto

23

Đây là cách tiếp cận của tôi đối với bảng tạm thời và Arel. Nó sử dụng Arel # từ phương thức truyền vào trong truy vấn bên trong với Arel # to_sql.

inner_query = YourModel.where(:stuff => "foo") 
outer_query = YourModel.scoped # cheating, need an ActiveRelation 
outer_query = outer_query.from(Arel.sql("(#{inner_query.to_sql}) as results")). 
          select("*") 

Bây giờ bạn có thể làm một số điều tốt đẹp với outer_query, paginate, select, group, v.v ...

inner_query ->

select * from your_models where stuff='foo' 

outer_query ->

select * from (select * from your_models where stuff='foo') as results; 
+1

Bạn có thể cũng nhận được một outer_query mà không cần phải chỉ định một mô hình giả hoặc tên bảng.Hai dòng cuối cùng ở trên có thể được thay thế bằng dòng này, đó là những gì "từ" đang gọi anyways: outer_query = Arel :: SelectManager.new (Arel :: Table.engine, Arel.sql ("(# { inner_query.to_sql}) dưới dạng kết quả ")) – DSimon

+1

Đây là huyền thoại Mr.Todd – beck03076

6
Point. 
from(Point.order(Point.arel_table[:timestamp].desc).as("sorted")). 
select("sorted.*"). 
group("sorted.client_id") 
1

Để làm điều này trong "tinh khiết" Arel, điều này đã làm việc cho tôi:

points = Arel::Table.new('points') 
sorted = Arel::Table.new('points', as: 'sorted') 
query = sorted.from(points.order('timestamp desc').project('*')).project(sorted[Arel.star]).group(sorted[:client_id]) 
query.to_sql 

Dĩ nhiên , trong trường hợp của bạn, các điểm và sắp xếp sẽ được lấy ra và đuôi ored từ mô hình điểm như trái ngược với sản xuất như trên.

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