Làm cách nào để thể hiện truy vấn KHÔNG IN với ActiveRecord / Rails?


207

Chỉ để cập nhật điều này vì có vẻ như rất nhiều người tìm đến nó, nếu bạn đang sử dụng Rails 4, hãy nhìn vào câu trả lời của Trung Lê` và VinniVidiVicci.

Topic.where.not(forum_id:@forums.map(&:id))

Topic.where(published:true).where.not(forum_id:@forums.map(&:id))

Tôi hy vọng có một giải pháp dễ dàng không liên quan find_by_sql, nếu không thì tôi đoán nó sẽ phải hoạt động.

Tôi tìm thấy bài viết này tham khảo này:

Topic.find(:all, :conditions => { :forum_id => @forums.map(&:id) })

giống như

SELECT * FROM topics WHERE forum_id IN (<@forum ids>)

Tôi tự hỏi nếu có một cách để làm NOT INvới điều đó, như:

SELECT * FROM topics WHERE forum_id NOT IN (<@forum ids>)

3
Là một FYI, Datamapper đã có hỗ trợ cụ thể cho KHÔNG VÀO. Ví dụ:Person.all(:name.not => ['bob','rick','steve'])
Mark Thomas

1
xin lỗi vì không biết gì, nhưng Datamapper là gì? đó là một phần của đường ray 3?
Toby Joiner

2
ánh xạ dữ liệu là một cách khác để lưu trữ dữ liệu, nó thay thế Active Record bằng một cấu trúc khác và sau đó bạn viết các công cụ liên quan đến mô hình của mình như truy vấn, theo cách khác.
Michael Durrant

Câu trả lời:


313

Đường ray 4+:

Article.where.not(title: ['Rails 3', 'Rails 5']) 

Đường ray 3:

Topic.where('id NOT IN (?)', Array.wrap(actions))

Đâu actionslà một mảng với:[1,2,3,4,5]


1
Đây là cách tiếp cận phù hợp với mô hình truy vấn Active Record mới nhất
Nevir

5
@NewAlexandria là đúng, vì vậy bạn phải làm một cái gì đó như Topic.where('id NOT IN (?)', (actions.empty? ? '', actions). Nó vẫn sẽ dừng ở mức 0, nhưng tôi thấy rằng mảng bạn truyền vào thường được tạo bởi bộ lọc sẽ trả về []ít nhất và không bao giờ là không. Tôi khuyên bạn nên kiểm tra Squeel, DSL trên Active Record. Sau đó, bạn có thể làm : Topic.where{id.not_in actions}, không / trống / hoặc cách khác.
danneu

6
@danneu chỉ hoán đổi .empty?cho .blank?và bạn là con số không-proof
colllin

(hành động.empty? '', hành động) bởi @daaneu nên là (hành động.empty ?? '': hành động)
marcel saledit

3
đi cho ký hiệu đường ray 4: Article.where.not (tiêu đề: ['Rails 3', 'Rails 5'])
Tal

152

FYI, Trong Rails 4, bạn có thể sử dụng notcú pháp:

Article.where.not(title: ['Rails 3', 'Rails 5'])

11
cuối cùng Điều gì làm họ mất quá lâu để bao gồm điều đó? :)
Dominik Goltermann

50

Bạn có thể thử một cái gì đó như:

Topic.find(:all, :conditions => ['forum_id not in (?)', @forums.map(&:id)])

Bạn có thể cần phải làm @forums.map(&:id).join(','). Tôi không thể nhớ nếu Rails sẽ lập luận vào danh sách CSV nếu nó có thể đếm được.

Bạn cũng có thể làm điều này:

# in topic.rb
named_scope :not_in_forums, lambda { |forums| { :conditions => ['forum_id not in (?)', forums.select(&:id).join(',')] }

# in your controller 
Topic.not_in_forums(@forums)

50

Sử dụng Arel:

topics=Topic.arel_table
Topic.where(topics[:forum_id].not_in(@forum_ids))

hoặc, nếu được ưu tiên:

topics=Topic.arel_table
Topic.where(topics[:forum_id].in(@forum_ids).not)

và kể từ đường ray 4 trên:

topics=Topic.arel_table
Topic.where.not(topics[:forum_id].in(@forum_ids))

Xin lưu ý rằng cuối cùng bạn không muốn forum_ids là danh sách id, mà là truy vấn con, nếu vậy thì bạn nên làm một cái gì đó như thế này trước khi nhận được chủ đề:

@forum_ids = Forum.where(/*whatever conditions are desirable*/).select(:id)

bằng cách này bạn có được mọi thứ trong một truy vấn duy nhất: đại loại như:

select * from topic 
where forum_id in (select id 
                   from forum 
                   where /*whatever conditions are desirable*/)

Cũng lưu ý rằng cuối cùng bạn không muốn làm điều này, mà là tham gia - những gì có thể hiệu quả hơn.


2
Một sự tham gia thể hiệu quả hơn, nhưng không nhất thiết phải như vậy. Hãy chắc chắn để sử dụng EXPLAIN!
James

20

Để mở rộng câu trả lời @Trung Lê, trong Rails 4, bạn có thể làm như sau:

Topic.where.not(forum_id:@forums.map(&:id))

Và bạn có thể tiến một bước xa hơn. Nếu trước tiên bạn cần lọc chỉ các Chủ đề được xuất bản và sau đó lọc ra các id bạn không muốn, bạn có thể làm điều này:

Topic.where(published:true).where.not(forum_id:@forums.map(&:id))

Rails 4 làm cho nó dễ dàng hơn nhiều!


12

Giải pháp được chấp nhận thất bại nếu @forumstrống. Để khắc phục điều này tôi đã phải làm

Topic.find(:all, :conditions => ['forum_id not in (?)', (@forums.empty? ? '' : @forums.map(&:id))])

Hoặc, nếu sử dụng Rails 3+:

Topic.where( 'forum_id not in (?)', (@forums.empty? ? '' : @forums.map(&:id)) ).all

4

Hầu hết các câu trả lời ở trên sẽ đủ cho bạn nhưng nếu bạn đang thực hiện nhiều hơn các kết hợp vị ngữ và phức tạp như vậy, hãy kiểm tra Squeel . Bạn sẽ có thể làm một cái gì đó như:

Topic.where{{forum_id.not_in => @forums.map(&:id)}}
Topic.where{forum_id.not_in @forums.map(&:id)} 
Topic.where{forum_id << @forums.map(&:id)}

2

Bạn có thể muốn xem qua plugin meta_where của Ernie Miller. Câu lệnh SQL của bạn:

SELECT * FROM topics WHERE forum_id NOT IN (<@forum ids>)

... có thể được thể hiện như thế này:

Topic.where(:forum_id.nin => @forum_ids)

Ryan Bates of Railscasts đã tạo ra một đoạn ghi hình hay giải thích về MetaWhere .

Không chắc đây có phải là thứ bạn đang tìm hay không nhưng trong mắt tôi chắc chắn nó trông đẹp hơn truy vấn SQL nhúng.


2

Bài viết gốc đề cập cụ thể bằng cách sử dụng ID số, ​​nhưng tôi đến đây để tìm cú pháp thực hiện KHÔNG IN với một chuỗi các chuỗi.

ActiveRecord cũng sẽ xử lý việc đó tốt cho bạn:

Thing.where(['state NOT IN (?)', %w{state1 state2}])

1

Những id diễn đàn này có thể được làm việc một cách thực tế không? ví dụ: bạn có thể tìm thấy các diễn đàn này bằng cách nào đó - nếu đó là trường hợp bạn nên làm một cái gì đó như

Topic.all(:joins => "left join forums on (forums.id = topics.forum_id and some_condition)", :conditions => "forums.id is null")

Điều này sẽ hiệu quả hơn so với làm SQL not in


1

Cách này tối ưu hóa cho khả năng đọc, nhưng nó không hiệu quả về mặt truy vấn cơ sở dữ liệu:

# Retrieve all topics, then use array subtraction to
# find the ones not in our list
Topic.all - @forums.map(&:id)

0

Bạn có thể sử dụng sql trong điều kiện của bạn:

Topic.find(:all, :conditions => [ "forum_id NOT IN (?)", @forums.map(&:id)])


0

Khi bạn truy vấn một mảng trống, hãy thêm "<< 0" vào mảng trong khối where để nó không trả về "NULL" và ngắt truy vấn.

Topic.where('id not in (?)',actions << 0)

Nếu hành động có thể là một mảng trống hoặc trống.


1
Cảnh báo: điều này thực sự thêm 0 vào mảng, vì vậy nó không còn trống. Nó cũng có tác dụng phụ là sửa đổi mảng - nguy hiểm gấp đôi nếu bạn sử dụng nó sau này. Tốt hơn nhiều để bọc nó trong if-other và sử dụng Topic.none / all cho các trường hợp cạnh
Ted Pennings

Một cách an toàn hơn là:Topic.where("id NOT IN (?)", actions.presence || [0])
Weston Ganger

0

Đây là một truy vấn "không trong" phức tạp hơn, sử dụng truy vấn con trong rails 4 bằng squeel. Tất nhiên là rất chậm so với sql tương đương, nhưng hey, nó hoạt động.

    scope :translations_not_in_english, ->(calmapp_version_id, language_iso_code){
      join_to_cavs_tls_arr(calmapp_version_id).
      joins_to_tl_arr.
      where{ tl1.iso_code == 'en' }.
      where{ cavtl1.calmapp_version_id == my{calmapp_version_id}}.
      where{ dot_key_code << (Translation.
        join_to_cavs_tls_arr(calmapp_version_id).
        joins_to_tl_arr.    
        where{ tl1.iso_code == my{language_iso_code} }.
        select{ "dot_key_code" }.all)}
    }

Hai phương thức đầu tiên trong phạm vi là các phạm vi khác khai báo các bí danh cavtl1 và tl1. << là toán tử không hoạt động trong squeel.

Hy vọng điều này sẽ giúp được ai đó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.