Rails: Menggunakan lebih dari / kurang dari dengan pernyataan di mana

142

Saya mencoba menemukan semua Pengguna dengan id lebih dari 200, tetapi saya mengalami beberapa masalah dengan sintaksis tertentu.

User.where(:id > 200) 

dan

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

keduanya gagal.

Ada saran?

Adam Templeton
sumber

Jawaban:

276

Coba ini

User.where("id > ?", 200) 
RadBrad
sumber
1
Lihat juga permata Squeel dari Ernie Miller
cpuguy83
7
Apakah ada alasan untuk lebih suka menggunakan ?, daripada memasukkan 200?
davetapley
20
itu secara otomatis lolos dari 200 (apakah mungkin bagi pengguna untuk memasukkan nilai, itu menghindari kemungkinan serangan injeksi SQL)
user1051849
124

Saya hanya menguji ini di Rails 4 tetapi ada cara yang menarik untuk menggunakan rentang dengan wherehash untuk mendapatkan perilaku ini.

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

akan menghasilkan SQL

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

Hal yang sama dapat dilakukan dengan kurang dari dengan -Float::INFINITY.

Saya baru saja memposting pertanyaan serupa bertanya tentang melakukan ini dengan tanggal di SO .

>= vs. >

Untuk menghindari orang harus menggali dan mengikuti percakapan komentar di sini adalah yang utama.

Metode di atas hanya menghasilkan >=kueri dan bukan a >. Ada banyak cara untuk menangani alternatif ini.

Untuk angka diskrit

Anda dapat menggunakan number_you_want + 1strategi seperti di atas di mana saya tertarik dengan Pengguna id > 200tetapi sebenarnya mencari id >= 201. Ini bagus untuk bilangan bulat dan angka di mana Anda dapat menambah dengan satu unit bunga.

Jika Anda memiliki nomor yang diekstraksi ke dalam konstanta yang dinamai dengan baik, ini mungkin yang paling mudah untuk dibaca dan dipahami sekilas.

Logika terbalik

Kita bisa menggunakan fakta itu x > y == !(x <= y)dan menggunakan rantai di mana tidak.

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

yang menghasilkan SQL

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

Ini membutuhkan beberapa detik tambahan untuk membaca dan alasan tentang tetapi akan bekerja untuk nilai atau kolom non diskrit di mana Anda tidak dapat menggunakan + 1strategi.

Meja Arel

Jika Anda ingin menjadi mewah, Anda dapat menggunakan Arel::Table.

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

akan menghasilkan SQL

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

Spesifikasinya adalah sebagai berikut:

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`

Pendekatan ini akan memberi Anda SQL yang tepat yang Anda minati, tetapi tidak banyak orang yang menggunakan tabel Arel secara langsung dan dapat menemukannya berantakan dan / atau membingungkan. Anda dan tim Anda akan tahu apa yang terbaik untuk Anda.

Bonus

Mulai di Rails 5 Anda juga dapat melakukan ini dengan tanggal!

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

akan menghasilkan SQL

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

Bonus ganda

Setelah Ruby 2.6 dirilis (25 Desember 2018), Anda dapat menggunakan sintaks rentang tak terbatas baru! Alih-alih, 201..Float::INFINITYAnda hanya bisa menulis 201... Info lebih lanjut di posting blog ini .

Harun
sumber
3
Mengapa ini lebih unggul dari jawaban yang diterima, karena penasaran?
mecampbellsoup
5
Superior menyesatkan. Secara umum Anda mencapai lebih banyak fleksibilitas dengan kueri ARel Anda jika Anda dapat menggunakan sintaks hash atas string, itulah sebabnya banyak orang lebih suka solusi ini. Bergantung pada proyek / tim / organisasi Anda, Anda mungkin menginginkan sesuatu yang lebih mudah bagi seseorang untuk melirik kode untuk mencari tahu, yang jawabannya diterima.
Aaron
2
Saya tidak percaya Anda bisa melakukannya dengan menggunakan wherepencocokan dasar . Karena >saya sarankan menggunakan >= (number_you_want + 1)untuk kesederhanaan. Jika Anda benar-benar ingin memastikan itu hanya >kueri, Anda dapat mengakses tabel ARel. Setiap kelas yang mewarisi dari ActiveRecordmemiliki arel_tablemetode pengambil yang mengembalikan Arel::Tableuntuk kelas itu. Kolom pada tabel diakses dengan []metode seperti User.arel_table[:id]. Ini mengembalikan Arel::Attributes::AttributeAnda dapat memanggil gtdan masuk 200. Ini kemudian dapat diteruskan ke where. mis User.where(User.arel_table[:id].gt(200)).
Aaron
2
@bluehallu dapatkah Anda memberikan contoh? Berikut ini tidak berfungsi untuk saya User.where(created_at: 3.days.ago..DateTime::Infinity.new).
Aaron
1
Ah! Kemungkinan perubahan baru dalam Rails 5 yang memberi Anda perilaku yang menguntungkan. Di Rails 4.2.5 (Ruby 2.2.2) Anda akan mendapatkan kueri dengan WHERE (users.created_at >= '2016-04-09 14:31:15' AND users.created_at < #<Date::Infinity:0x00>)(kutu belakang di sekitar nama tabel dan kolom dihilangkan untuk pemformatan komentar SO).
Aaron
22

Penggunaan yang lebih baik adalah membuat ruang lingkup dalam model pengguna where(arel_table[:id].gt(id))

Mihai
sumber
6

Jika Anda ingin tulisan yang lebih intuitif, ada permata yang disebut squeel yang memungkinkan Anda menulis instruksi seperti ini:

User.where{id > 200}

Perhatikan karakter 'penjepit' {} dan idhanya berupa teks.

Yang harus Anda lakukan adalah menambahkan squeel ke Gemfile Anda:

gem "squeel"

Ini mungkin memudahkan hidup Anda saat menulis pernyataan SQL yang kompleks di Ruby.

Douglas
sumber
11
Saya sarankan hindari menggunakan squeel. Jangka panjang sulit dipertahankan dan terkadang memiliki perilaku aneh. Juga bermasalah dengan versi Rekaman Aktif tertentu
John Owen Chile
Saya telah menggunakan squeel selama beberapa tahun dan masih senang dengannya. Tapi Mungkin ada baiknya mencoba ORM lain, seperti sekuel (<> squeel) misalnya, yang tampaknya menjanjikan fitur bagus untuk menggantikan ActiveRecord.
Douglas
5

Arel adalah temanmu.

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

joegiralt
sumber
4

Kemungkinan mewah lainnya adalah ...

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

Fitur ini memungkinkan Anda untuk membuat kueri yang lebih komprehensif jika Anda ingin mengganti di banyak tempat, misalnya ...

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

Ini memiliki arti lebih daripada memiliki banyak ?pada permintaan ...

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

Saya sering mengalami masalah ini dengan bidang tanggal (di mana operator pembanding sangat umum).

Untuk menjelaskan lebih lanjut tentang jawaban Mihai, yang saya percaya merupakan pendekatan yang solid.

Untuk model, Anda dapat menambahkan cakupan seperti ini:

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

... dan kemudian di controller Anda, atau di mana pun Anda menggunakan model Anda:

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

... contoh yang lebih kompleks dengan gabungan terlihat seperti ini:

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

Keuntungan yang sangat besar dari pendekatan ini adalah (a) memungkinkan Anda menyusun pertanyaan Anda dari lingkup yang berbeda dan (b) menghindari collision alias ketika Anda bergabung ke tabel yang sama dua kali karena arel_table akan menangani bagian dari generasi permintaan tersebut.

Brett Green
sumber
1

Rails 6.1+

Rails 6.1 menambahkan 'sintaks' baru untuk operator perbandingan dalam wherekondisi, misalnya:

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

Jadi permintaan Anda dapat ditulis ulang sebagai berikut:

User.where('id >': 200) 

Berikut ini tautan ke PR tempat Anda dapat menemukan lebih banyak contoh.

Marian13
sumber
-3

Singkat:

User.where("id > 200")
Bengala
sumber
8
Saya menganggap ini harus dinamis, dan bahwa poster ingin menghindari injeksi SQL dengan menggunakan query parameterized ( where("id > ?", 200)sintaks). Ini tidak mencapai itu.
Matthew Hinea