Rails: ใช้มากกว่า / น้อยกว่าด้วยคำสั่ง where


142

ฉันพยายามค้นหาผู้ใช้ทั้งหมดที่มี ID มากกว่า 200 แต่ฉันมีปัญหากับไวยากรณ์เฉพาะบางอย่าง

User.where(:id > 200) 

และ

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

มีทั้งล้มเหลว

ข้อเสนอแนะใด ๆ

คำตอบ:


276

ลองสิ่งนี้

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

1
ตรวจสอบ Squeel gem จาก Ernie Miller
cpuguy83

7
มีเหตุผลใดที่จะชอบการใช้?มากกว่าการใส่ inline 200หรือไม่?
davetapley

20
มันจะหนีออกมาโดยอัตโนมัติ 200 (เป็นไปได้สำหรับผู้ใช้เพื่อป้อนค่ามันหลีกเลี่ยงความเป็นไปได้ของการโจมตีการฉีด SQL)
user1051849

124

ฉันเพิ่งทดสอบสิ่งนี้ใน Rails 4 แต่มีวิธีที่น่าสนใจในการใช้ช่วงที่มีwhereแฮชเพื่อให้ได้พฤติกรรมนี้

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

จะสร้าง SQL

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

-Float::INFINITYเช่นเดียวกับที่สามารถทำได้น้อยกว่าด้วย

ฉันเพียงแค่โพสต์คำถามที่คล้ายกันถามเกี่ยวกับการทำเช่นนี้กับวันที่นี่ใน SO

>= VS >

เพื่อหลีกเลี่ยงคนที่ต้องขุดและติดตามการสนทนาความคิดเห็นที่นี่เป็นไฮไลท์

วิธีการดังกล่าวเพียง แต่สร้าง>=แบบสอบถามและไม่ >มีหลายวิธีในการจัดการทางเลือกนี้

สำหรับตัวเลขที่ไม่ต่อเนื่อง

คุณสามารถใช้number_you_want + 1กลยุทธ์เช่นด้านบนที่ฉันสนใจผู้ใช้ด้วยid > 200แต่มองหาจริงid >= 201ๆ นี่เป็นเรื่องปกติสำหรับจำนวนเต็มและตัวเลขที่คุณสามารถเพิ่มได้ด้วยหน่วยความสนใจเดียว

หากคุณมีการแยกตัวเลขเป็นค่าคงที่ที่มีชื่ออย่างดีนี่อาจเป็นวิธีที่ง่ายที่สุดในการอ่านและทำความเข้าใจได้อย่างรวดเร็ว

ตรรกะกลับหัว

เราสามารถใช้ความจริงนั้นx > y == !(x <= y)และใช้สิ่งที่ไม่ใช่โซ่

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

ซึ่งสร้าง SQL

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

สิ่งนี้ใช้เวลาสองวินาทีในการอ่านและเหตุผล แต่จะใช้ได้กับค่าที่ไม่ต่อเนื่องหรือคอลัมน์ที่คุณไม่สามารถใช้+ 1กลยุทธ์ได้

โต๊ะอาเรล

Arel::Tableหากคุณต้องการที่จะได้รับแฟนซีคุณสามารถทำให้การใช้งานของ

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

จะสร้าง SQL

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

ข้อมูลเฉพาะมีดังนี้:

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`

วิธีการนี้จะทำให้คุณได้รับSQL ที่แน่นอนที่คุณสนใจ แต่มีคนจำนวนไม่มากที่ใช้ตาราง Arel โดยตรงและอาจพบว่ามันยุ่งและ / หรือสับสน คุณและทีมของคุณจะรู้ว่าอะไรดีที่สุดสำหรับคุณ

โบนัส

เริ่มต้นใน Rails 5 คุณสามารถทำได้ด้วยการนัดเดท!

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

จะสร้าง SQL

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

โบนัสสองเท่า

เมื่อ Ruby 2.6 เปิดตัว (25 ธันวาคม 2018) คุณจะสามารถใช้ไวยากรณ์ช่วงอนันต์ใหม่ได้! แทนที่จะเป็น201..Float::INFINITYคุณจะสามารถเขียน201..ได้ ข้อมูลเพิ่มเติมในโพสต์บล็อกนี้


3
ทำไมสิ่งนี้ถึงเหนือกว่าคำตอบที่ได้รับการยอมรับจากความอยากรู้?
mecampbellsoup

5
ซูพีเรียทำให้เข้าใจผิด โดยทั่วไปแล้วคุณจะมีความยืดหยุ่นมากขึ้นกับการสอบถาม ARel ของคุณหากคุณสามารถใช้ไวยากรณ์แฮชกับสตริงซึ่งเป็นสาเหตุที่หลายคนต้องการโซลูชันนี้ ขึ้นอยู่กับโครงการ / ทีม / องค์กรของคุณคุณอาจต้องการบางสิ่งที่ง่ายกว่าสำหรับใครบางคนที่จ้องมองโค้ดเพื่อหาคำตอบซึ่งเป็นคำตอบที่ยอมรับได้
แอรอน

2
ผมไม่เชื่อว่าคุณสามารถทำได้โดยใช้พื้นฐานwherematchers สำหรับ>ฉันขอแนะนำให้ใช้>= (number_you_want + 1)สำหรับความเรียบง่าย หากคุณต้องการให้แน่ใจว่าเป็นเพียง>แบบสอบถามคุณสามารถเข้าถึงตาราง ARel ทุกคลาสที่สืบทอดมาActiveRecordจะมีarel_tableเมธอด getter ซึ่งส่งกลับค่าArel::Tableสำหรับคลาสนั้น คอลัมน์บนโต๊ะจะเข้าถึงได้ด้วยวิธีการเช่น[] User.arel_table[:id]สิ่งนี้จะคืนค่าที่Arel::Attributes::Attributeคุณสามารถโทรหาgtและส่ง200ต่อได้ whereนี้สามารถผ่านไปแล้ว User.where(User.arel_table[:id].gt(200))เช่น
แอรอน

2
@bluehallu คุณสามารถให้ตัวอย่างได้หรือไม่ User.where(created_at: 3.days.ago..DateTime::Infinity.new)ต่อไปนี้จะไม่ทำงานสำหรับฉัน
แอรอน

1
อา! เป็นไปได้ว่าการเปลี่ยนแปลงใหม่ใน Rails 5 จะทำให้คุณมีพฤติกรรมที่เป็นประโยชน์ ใน Rails 4.2.5 (Ruby 2.2.2) คุณจะได้รับข้อความค้นหาด้วยWHERE (users.created_at >= '2016-04-09 14:31:15' AND users.created_at < #<Date::Infinity:0x00>)(ย้อนหลังเห็บรอบชื่อตารางและคอลัมน์ที่ละเว้นการจัดรูปแบบความคิดเห็น SO)
แอรอน


6

หากคุณต้องการการเขียนที่เข้าใจง่ายยิ่งขึ้นมันมีอัญมณีที่เรียกว่าsqueelที่จะช่วยให้คุณเขียนคำสั่งของคุณเช่นนี้:

User.where{id > 200}

สังเกตว่าอักขระ 'วงเล็บปีกกา' {} และidเป็นเพียงข้อความ

สิ่งที่คุณต้องทำคือเพิ่ม squeel ให้กับ Gemfile ของคุณ:

gem "squeel"

สิ่งนี้อาจทำให้ชีวิตคุณง่ายขึ้นมากเมื่อเขียนคำสั่ง SQL ที่ซับซ้อนใน Ruby


11
ฉันแนะนำให้หลีกเลี่ยงการใช้ squeel ระยะยาวยากที่จะรักษาและบางครั้งก็มีพฤติกรรมแปลก ๆ นอกจากนี้ยังมีข้อผิดพลาดบางอย่างกับรุ่นของ Active Record
John Owen Chile

ฉันใช้ squeel มานานหลายปีและยังคงมีความสุขกับมัน แต่มันอาจคุ้มค่าที่จะลองใช้ ORM อื่นเช่น sequel (<> squeel) เป็นต้นซึ่งดูเหมือนว่าจะมีฟีเจอร์ที่ดีที่จะมาแทนที่ ActiveRecord
ดักลาส


4

ความเป็นไปได้ที่น่าสนใจอีกอย่างคือ ...

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

คุณลักษณะนี้ช่วยให้คุณสามารถสร้างคิวรีที่เข้าใจได้มากขึ้นหากคุณต้องการแทนที่ในหลาย ๆ ที่เช่น ...

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

สิ่งนี้มีความหมายมากกว่า?การสืบค้นจำนวนมาก...

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

3

ฉันมักจะมีปัญหานี้กับเขตข้อมูลวันที่ (ที่ตัวดำเนินการเปรียบเทียบทั่วไปมาก)

เพื่ออธิบายเพิ่มเติมเกี่ยวกับคำตอบของ Mihai ซึ่งฉันเชื่อว่าเป็นแนวทางที่มั่นคง

สำหรับโมเดลคุณสามารถเพิ่มขอบเขตดังนี้:

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

... และจากนั้นในตัวควบคุมของคุณหรือที่ใดก็ตามที่คุณกำลังใช้โมเดลของคุณ:

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

... ตัวอย่างที่ซับซ้อนยิ่งขึ้นด้วยการรวมเป็นแบบนี้:

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

ข้อได้เปรียบอย่างมากของวิธีนี้คือ (a) ช่วยให้คุณสามารถเขียนแบบสอบถามจากขอบเขตที่แตกต่างกันและ (b) หลีกเลี่ยงการชนกันของชื่อแทนเมื่อคุณเข้าร่วมในตารางเดียวกันสองครั้งเนื่องจาก arel_table จะจัดการส่วนของการสร้างแบบสอบถาม


1

Rails 6.1+

Rails 6.1 เพิ่ม 'ไวยากรณ์' ใหม่สำหรับการเปรียบเทียบโอเปอเรเตอร์ในwhereเงื่อนไขตัวอย่างเช่น

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

ดังนั้นแบบสอบถามของคุณสามารถเขียนใหม่ได้ดังนี้:

User.where('id >': 200) 

นี่คือลิงค์ไปยัง PRที่คุณสามารถค้นหาตัวอย่างเพิ่มเติมได้


-3

สั้น:

User.where("id > 200")

8
ฉันคิดว่าสิ่งนี้ต้องเป็นแบบไดนามิกและโปสเตอร์ต้องการหลีกเลี่ยงการฉีด SQL โดยใช้การสืบค้นwhere("id > ?", 200)แบบใช้พารามิเตอร์ ( ไวยากรณ์) สิ่งนี้ไม่ประสบความสำเร็จ
Matthew Hinea
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.