2009-06-20 19 views
6

Một phần lý do tại sao tôi yêu Rails là tôi ghét SQL - tôi nghĩ nó giống như một ngôn ngữ lắp ráp nên được thao tác với các công cụ cấp cao hơn như ActiveRecord. Tôi dường như đã đạt đến giới hạn của cách tiếp cận này, tuy nhiên, và tôi ra khỏi chiều sâu của tôi với SQL.Truy vấn đường ray phức tạp - công đoàn? chọn phụ? Tôi vẫn có thể sử dụng named_scope?

Tôi có một mô hình phức tạp với nhiều hồ sơ phụ. Tôi cũng có một bộ 30-40 tên_scopes thực hiện logic nghiệp vụ từ khách hàng. Những phạm vi này bị xích lại với nhau theo điều kiện, đó là lý do tại sao tôi có những phạm vi joins_ để các kết nối không bị che khuất.

Tôi có một vài trong số chúng không hoạt động đúng hoặc ít nhất là cách khách hàng muốn chúng hoạt động. Đây là một ý tưởng sơ bộ về cấu trúc mô hình, với một vài phạm vi được đặt tên (không phải tất cả những gì cần thiết cho ví dụ) minh họa cách tiếp cận của tôi và chỉ ra các vấn đề của tôi. (xin vui lòng tha thứ cho bất kỳ lỗi cú pháp)

class Man < ActiveRecord::Base 
    has_many :wives 

    named_scope :has_wife_named  lambda { |n| { :conditions => { :wives => {:name => n}}}} 
    named_scope :has_young_wife_named lambda { |n| { :conditions => { :wives => {:name => n, :age => 0..30}}}} 
    named_scope :has_yw_named_v2  lambda { |n| { :conditions => ["wives.name = ? AND wives.age <= 30", n]}} 
    named_scope :joins_wives   :joins => :wives 

    named_scope :has_red_cat   :conditions => { :cats => {:color => 'red'}}   
    named_scope :has_cat_of_color  lambda { |c| { :conditions => { :cats => {:color => c}}}} 
    named_scope :has_7yo_cat   :conditions => { :cats => {:age => 7}} 
    named_scope :has_cat_of_age  lambda { |a| { :conditions => { :cats => {:age => a}}}} 
    named_scope :has_cat_older_than lambda { |a| { :conditions => ["cats.age > ?", a] }} 
    named_scope :has_cat_younger_than lambda { |a| { :conditions => ["cats.age < ?", a] }} 
    named_scope :has_cat_fatter_than lambda { |w| { :conditions => ["cats.weight > ?", w] } } 
    named_scope :joins_wives_cats  :joins => {:wives => :cats} 
end 

class Wife < ActiveRecord::Base 
    belongs_to :man 
    has_many :cats 
end 

class Cat < ActiveRecord::Base 
    belongs_to :wife 
end 
  1. Tôi có thể tìm thấy những người đàn ông có vợ có con mèo mà có màu đỏ VÀ bảy tuổi

    @men = Man.has_red_cat.has_7yo_cat.joins_wives_cats.scoped({:select => 'DISTINCT men'}) 
    

    Và tôi thậm chí có thể tìm thấy những người đàn ông có vợ có mèo có trọng lượng trên 20 kg và trên 6 tuổi

    @men = Man.has_cat_fatter_than(20).has_cat_older_than(5).joins_wives_cats.scoped({:select => 'DISTINCT men'}) 
    

    Nhưng đó không phải những gì tôi muốn. Tôi muốn tìm những người đàn ông có vợ có ít nhất một con mèo màu đỏ và một con mèo bảy tuổi, không cần phải là cùng một con mèo, hoặc để tìm những người đàn ông có vợ có ít nhất một con mèo trên một trọng lượng nhất định và một con mèo lớn hơn một độ tuổi nhất định.
    (trong ví dụ tiếp theo, xin vui lòng chịu sự hiện diện của các hợp joins_DISTINCT)

  2. Tôi có thể tìm thấy những người đàn ông với người vợ tên là Esther

    @men = Man.has_wife_named('Esther') 
    

    Tôi thậm chí có thể tìm thấy những người đàn ông với người vợ tên là Esther, Ruth HOẶC Ada (ngọt!)

    @men = Man.has_wife_named(['Esther', 'Ruth', 'Ada']) 
    

    nhưng tôi muốn tìm người đàn ông có vợ tên Esther AND Ruth AND Ada.

  3. Ha ha, chỉ đùa thôi, trên thực tế, tôi cần điều này: Tôi có thể tìm thấy những người đàn ông với vợ dưới 30 tuổi tên là Esther

    @men = Man.has_young_wife_named('Esther') 
    

    tìm người đàn ông với người vợ trẻ tên là Esther, Ruth hoặc Ada

    @men = Man.has_young_wife_named(['Esther', 'Ruth', 'Ada']) 
    

    nhưng như trên tôi muốn tìm người đàn ông có vợ nhỏ tên là Esther AND Ruth AND Ada. May mắn thay, mức tối thiểu là cố định trong trường hợp này, nhưng nó sẽ là tốt đẹp để xác định tuổi tối thiểu là tốt.

  4. là có một cách để kiểm tra sự bất bình đẳng với một cú pháp băm, hay bạn luôn luôn phải trở lại :conditions => ["", n] - lưu ý sự khác biệt giữa has_young_wife_namedhas_yw_named_v2 - Tôi thích là người đầu tiên tốt hơn, nhưng phạm vi chỉ hoạt động cho hữu hạn giá trị. Nếu bạn đang tìm kiếm một người vợ cũ, tôi đoán bạn có thể sử dụng a..100 nhưng sau đó khi một người vợ bước sang tuổi 101, cô ấy bỏ tìm kiếm. (hmm.cô ấy có thể nấu ăn không? j/k)

  5. có cách nào để sử dụng phạm vi trong phạm vi không? Tôi rất thích nếu :has_red_cat có thể sử dụng :has_cat_of_color bằng cách nào đó hoặc nếu có cách nào đó để sử dụng phạm vi từ bản ghi con trong phụ huynh, vì vậy tôi có thể đặt phạm vi liên quan đến mèo vào mô hình Wife.

tôi thực sự không muốn làm điều này trong SQL thẳng mà không sử dụng named_scope, trừ khi có cái gì khác thực sự đẹp hơn - gợi ý cho các plugins và không có điều gì đánh giá rất cao, hoặc hướng vào các loại SQL tôi sẽ cần phải học hỏi. Một người bạn đã đề xuất rằng UNION hoặc các tìm kiếm phụ sẽ hoạt động ở đây, nhưng những người này dường như không được thảo luận nhiều trong bối cảnh của Rails. Tôi chưa biết gì về quan điểm - liệu chúng có hữu ích không? Có cách nào để làm cho họ vui vẻ không?

Cảm ơn bạn!

Như tôi đã đi đến St Ives
tôi gặp một người đàn ông với bảy vợ
Mỗi vợ đã bảy bao tải
Mỗi bao có bảy mèo
Mỗi mèo có bảy bộ dụng cụ
Kits, mèo, bao tải, vợ
Bao nhiêu người sẽ đến St Ives?

+1

Đó là câu hỏi thực sự thú vị trong tóm tắt. Và tôi hiểu rằng mô hình của bạn và tên phạm vi có nguồn gốc từ một vần điệu trẻ. Nhưng nó cho phép. Vợ: thuộc về Man? Nghiêm túc? –

+0

vâng, tôi đã nghĩ về điều đó khi tôi viết nó, nhưng bài thơ đã xuất hiện trong đầu tôi và tôi không thể loại bỏ nó. –

+0

Tôi muốn tìm một cách tốt để làm 'UNION' trong phạm vi được đặt tên trong AR. Tôi biết bạn có thể làm chúng trong SQLAlchemy. – mikelikespie

Trả lời

2

Vâng, tôi đã có kết quả tuyệt vời với named_scope s như thế này:

named_scope :has_cat_older_than lambda { |a| { :conditions => ["men.id in (select man_id from wives where wives.id in (select wife_id from cats where age > ?))", a] } } 

named_scope :has_young_wife_named lambda { |n| { :conditions => ["men.id in (select man_id from wives where name = ? and age < 30)", n] } } 

bây giờ tôi có thể làm thành công

Member.has_cat_older_than(6).has_young_wife_named('Miriam').has_young_wife_named('Vashti') 

và có được những gì tôi m mong đợi. Những phạm vi này không yêu cầu sử dụng các kết nối và chúng dường như hoạt động tốt với các kết nối được tạo kiểu khác.

w00t!

Bình luận được gợi ý về việc đây có phải là cách hiệu quả để thực hiện việc này hay không, hoặc nếu có cách 'rails-y' hơn. Một số cách để bao gồm phạm vi từ một mô hình khác dưới dạng phân đoạn truy vấn con sql có thể hữu ích ...

0

Bạn đã sử dụng giải pháp gốc nhất cho Rails. SQL thẳng sẽ có cùng hiệu suất nên không có lý do gì để sử dụng nó.

+0

Tôi không tìm kiếm hiệu suất cao hơn - Tôi đang tìm kiếm truy vấn hoạt động bình thường. Tôi muốn biết cách cấu trúc đúng truy vấn của tôi trong Rails, hoặc cách viết SQL để thực hiện nó. –

2

Tôi đã sử dụng construct_finder_sql để hoàn thành việc chọn lựa một tên_một trong tên khác. Nó có thể không dành cho tất cả mọi người, nhưng việc sử dụng nó cho phép chúng tôi DÙNG một vài tên_loại mà chúng tôi đã sử dụng cho các báo cáo.

Man.has_cat_older_than(6).send(:construct_finder_sql,{}) 

Hãy thử điều đó trong tập lệnh/bảng điều khiển của bạn.

+0

Tôi mất 7 tháng để cuối cùng hiểu điều này, và nó thực sự rất tiện dụng. Tôi nghĩ rằng nó sẽ giải quyết vấn đề ban đầu của tôi, và nó chỉ cởi một chút rắc rối tương tự. Pro-tip: 'send (: construct_finder_sql, {: chọn => 'men.id'})) ' để biến một phạm vi thành một chuỗi, do đó bạn loại trừ hồ sơ với, ví dụ: ' named_scope: excluding_sql, lambda {| sql | {: conditions => "men.id NOT IN (# {sql})"}} ' như sau: ' Man.complex_scope_one.exclude_sql (Man.complex_scope_two.send (: construct_finder_sql, {: select => 'men .id '}))) ' điều này sẽ loại trừ complex_scope_two khỏi complex_scope_one Cảm ơn bạn! –

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