Rails: Sử dụng lớn hơn / nhỏ hơn với câu lệnh where


142

Tôi đang cố gắng tìm tất cả Người dùng có id lớn hơn 200, nhưng tôi gặp một số rắc rối với cú pháp cụ thể.

User.where(:id > 200) 

User.where("? > 200", :id) 

đều thất bại

Bất kỳ đề xuất?

Câu trả lời:


276

Thử cái này

User.where("id > ?", 200) 

1
Ngoài ra, hãy kiểm tra đá quý Squeel từ Ernie Miller
cpuguy83 04

7
Có bất kỳ lý do để thích sử dụng ?, hơn là nội tuyến 200?
davetapley

20
nó tự động thoát khỏi 200 (người dùng có thể nhập giá trị, nó có thể tránh được các cuộc tấn công SQL SQL)
user1051849

124

Tôi chỉ thử nghiệm điều này trong Rails 4 nhưng có một cách thú vị để sử dụng phạm vi có wherehàm băm để thực hiện hành vi này.

User.where(id: 201..Float::INFINITY)

sẽ tạo SQL

SELECT `users`.* FROM `users`  WHERE (`users`.`id` >= 201)

Điều tương tự có thể được thực hiện cho ít hơn với -Float::INFINITY.

Tôi vừa đăng một câu hỏi tương tự hỏi về việc làm này với ngày ở đây trên SO .

>= đấu với >

Để tránh mọi người phải tìm hiểu kỹ và theo dõi cuộc trò chuyện bình luận ở đây là những điểm nổi bật.

Phương pháp trên chỉ tạo ra một >=truy vấn chứ không phải a >. Có nhiều cách để xử lý sự thay thế này.

Đối với số rời rạc

Bạn có thể sử dụng một number_you_want + 1chiến lược như ở trên nơi tôi quan tâm đến Người dùng id > 200nhưng thực sự tìm kiếm id >= 201. Điều này tốt cho số nguyên và số mà bạn có thể tăng theo một đơn vị quan tâm.

Nếu bạn có số được trích xuất thành một hằng số có tên tốt thì đây có thể là thứ dễ đọc và dễ hiểu nhất trong nháy mắt.

Logic đảo ngược

Chúng ta có thể sử dụng thực tế đó x > y == !(x <= y)và sử dụng chuỗi không phải chuỗi.

User.where.not(id: -Float::INFINITY..200)

tạo ra SQL

SELECT `users`.* FROM `users` WHERE (NOT (`users`.`id` <= 200))

Việc này cần thêm một giây để đọc và suy luận về nhưng sẽ hoạt động đối với các giá trị hoặc cột không rời rạc mà bạn không thể sử dụng + 1chiến lược.

Bàn Arel

Nếu bạn muốn có được ưa thích, bạn có thể sử dụng Arel::Table.

User.where(User.arel_table[:id].gt(200))

sẽ tạo SQL

"SELECT `users`.* FROM `users` WHERE (`users`.`id` > 200)"

Các chi tiết cụ thể như sau:

User.arel_table              #=> an Arel::Table instance for the User model / users table
User.arel_table[:id]         #=> an Arel::Attributes::Attribute for the id column
User.arel_table[:id].gt(200) #=> an Arel::Nodes::GreaterThan which can be passed to `where`

Cách tiếp cận này sẽ giúp bạn có được SQL chính xác mà bạn quan tâm, tuy nhiên không nhiều người sử dụng bảng Arel trực tiếp và có thể thấy nó lộn xộn và / hoặc khó hiểu. Bạn và nhóm của bạn sẽ biết những gì tốt nhất cho bạn.

Tặng kem

Bắt đầu trong Rails 5, bạn cũng có thể làm điều này với ngày!

User.where(created_at: 3.days.ago..DateTime::Infinity.new)

sẽ tạo SQL

SELECT `users`.* FROM `users` WHERE (`users`.`created_at` >= '2018-07-07 17:00:51')

Thưởng đôi

Khi Ruby 2.6 được phát hành (ngày 25 tháng 12 năm 2018), bạn sẽ có thể sử dụng cú pháp phạm vi vô hạn mới! Thay vì 201..Float::INFINITYbạn sẽ chỉ có thể viết 201... Thêm thông tin trong bài viết blog này .


3
Tại sao điều này vượt trội so với câu trả lời được chấp nhận, vì tò mò?
mecampbellsoup

5
Cấp trên là sai lệch. Nói chung, bạn đạt được sự linh hoạt hơn với các truy vấn ARel của mình nếu bạn có thể sử dụng cú pháp băm trên các chuỗi, đó là lý do tại sao nhiều người thích giải pháp này. Tùy thuộc vào dự án / nhóm / tổ chức của bạn, bạn có thể muốn một cái gì đó dễ dàng hơn cho ai đó liếc vào mã để tìm ra câu trả lời được chấp nhận.
Aaron

2
Tôi không tin rằng bạn có thể làm điều đó bằng cách sử dụng các công cụ đối sánh cơ bản where. Đối với >tôi đề nghị sử dụng một >= (number_you_want + 1)cho đơn giản. Nếu bạn thực sự muốn đảm bảo nó chỉ là một >truy vấn, bạn có thể truy cập vào bảng ARel. Mỗi lớp kế thừa từ ActiveRecordcó một arel_tablephương thức getter trả về Arel::Tablecho lớp đó. Các cột trên bảng được truy cập với []phương thức như thế nào User.arel_table[:id]. Điều này trả về một Arel::Attributes::Attributebạn có thể gọi gtvà chuyển vào 200. Điều này có thể được chuyển đến where. ví dụ User.where(User.arel_table[:id].gt(200)).
Aaron

2
@bluehallu bạn có thể cung cấp một ví dụ? Sau đây không làm việc cho tôi User.where(created_at: 3.days.ago..DateTime::Infinity.new).
Aaron

1
Ah! Đây có thể là một thay đổi mới trong Rails 5 mang lại cho bạn hành vi có lợi. Trong Rails 4.2.5 (Ruby 2.2.2), bạn sẽ nhận được một truy vấn với WHERE (users.created_at >= '2016-04-09 14:31:15' AND users.created_at < #<Date::Infinity:0x00>)(đánh dấu ngược xung quanh tên bảng và tên cột được bỏ qua cho định dạng nhận xét SO).
Aaron

22

Cách sử dụng tốt hơn là tạo một phạm vi trong mô hình người dùng where(arel_table[:id].gt(id))


6

Nếu bạn muốn viết một cách trực quan hơn, nó tồn tại một viên ngọc gọi là squeel cho phép bạn viết hướng dẫn của bạn như thế này:

User.where{id > 200}

Lưu ý các ký tự 'cú đúp' {} và idchỉ là một văn bản.

Tất cả những gì bạn phải làm là thêm squeel vào Gemfile của mình:

gem "squeel"

Điều này có thể giảm bớt cuộc sống của bạn rất nhiều khi viết câu lệnh SQL phức tạp trong Ruby.


11
Tôi khuyên bạn nên tránh sử dụng squeel. Lâu dài rất khó để duy trì và đôi khi có hành vi kỳ quặc. Ngoài ra, đó là lỗi với một số phiên bản Active Record nhất định
John Owen Chile

Tôi đã sử dụng squeel trong một số năm và vẫn hài lòng với nó. Nhưng có lẽ đáng để thử một ORM khác, chẳng hạn như phần tiếp theo (<> squeel), có vẻ như hứa hẹn các tính năng hay để thay thế ActiveRecord.
Douglas

5

Arel là bạn của bạn.

User.where (User.arel_table [: id] .gt (200))


4

Một khả năng thú vị khác là ...

User.where("id > :id", id: 100)

Tính năng này cho phép bạn tạo các truy vấn dễ hiểu hơn nếu bạn muốn thay thế ở nhiều nơi, ví dụ ...

User.where("id > :id OR number > :number AND employee_id = :employee", id: 100, number: 102, employee: 1205)

Điều này có nhiều ý nghĩa hơn là có rất nhiều ?truy vấn ...

User.where("id > ? OR number > ? AND employee_id = ?", 100, 102, 1205)

3

Tôi thường gặp vấn đề này với các trường ngày (trong đó các toán tử so sánh rất phổ biến).

Để giải thích thêm về câu trả lời của Mihai, mà tôi tin là một cách tiếp cận vững chắc.

Để các mô hình, bạn có thể thêm phạm vi như thế này:

scope :updated_at_less_than, -> (date_param) { 
  where(arel_table[:updated_at].lt(date_param)) }

... Và sau đó trong bộ điều khiển của bạn hoặc bất cứ nơi nào bạn đang sử dụng mô hình của mình:

result = MyModel.updated_at_less_than('01/01/2017')

... một ví dụ phức tạp hơn với các phép nối trông như thế này:

result = MyParentModel.joins(:my_model).
  merge(MyModel.updated_at_less_than('01/01/2017'))

Một lợi thế lớn của phương pháp này là (a) nó cho phép bạn soạn các truy vấn của mình từ các phạm vi khác nhau và (b) tránh va chạm bí danh khi bạn tham gia vào cùng một bảng hai lần vì arel_table sẽ xử lý phần đó của việc tạo truy vấn.


1

Đường ray 6.1+

Rails 6.1 đã thêm một 'cú pháp' mới cho các toán tử so sánh trong các wheređiều kiện, ví dụ:

Post.where('id >': 9)
Post.where('id >=': 9)
Post.where('id <': 3)
Post.where('id <=': 3)

Vì vậy, truy vấn của bạn có thể được viết lại như sau:

User.where('id >': 200) 

Đây là một liên kết đến PR nơi bạn có thể tìm thấy nhiều ví dụ.


-3

Ngắn hơn:

User.where("id > 200")

8
Tôi giả sử điều này phải là động và người đăng muốn tránh SQL tiêm bằng cách sử dụng các truy vấn ( where("id > ?", 200)cú pháp) được tham số hóa . Điều này không đạt được điều đó.
Matthew Hinea
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.