Rails: Використовуючи більше / менше, ніж у операторі де


142

Я намагаюся знайти всіх користувачів з ідентифікатором, більшим за 200, але у мене виникають проблеми з конкретним синтаксисом.

User.where(:id > 200) 

і

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

обоє не змогли.

Будь-які пропозиції?

Відповіді:


276

Спробуйте це

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

1
Також перегляньте дорогоцінний камінь від Ernie Miller
cpuguy83

7
Чи є якась причина віддавати перевагу використанню ?, а не вкладенню 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 .

>= проти >

Щоб уникнути людей, котрі перекопуються та слідкують за розмовою з коментарями, тут є основні моменти.

Наведений вище метод генерує лише >=запит, а не a >. Існує багато способів впоратися з цією альтернативою.

Для дискретних чисел

Ви можете використовувати 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
Я не вірю, що ви можете це зробити, використовуючи основні відповідники where. Бо >я пропоную використовувати >= (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).
Аарон

22

Краще використання - створення сфери в моделі користувача where(arel_table[:id].gt(id))


6

Якщо ви хочете більш інтуїтивно писати, у вас є дорогоцінний камінь, який називається скрип, який дозволить вам написати свою інструкцію так:

User.where{id > 200}

Зверніть увагу на символи "дужки" {} і idце лише текст.

Все, що вам потрібно зробити - це додати скрип до вашого Gemfile:

gem "squeel"

Це може значно полегшити ваше життя, коли ви пишете складний SQL-заяву в Ruby.


11
Я рекомендую уникати використання шипшини. Тривалий термін важко підтримувати і іноді має дивну поведінку. Крім того, це баггі з деякими версіями Active Record
Джон Оуен Чилі

Я вже кілька років використовую бисквіт і досі задоволений цим. Але, можливо, варто спробувати інший ORM, наприклад, продовження (<> скрип), наприклад, яке здається багатообіцяючим приємними функціями замінити 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

У мене часто виникають проблеми з полями дат (де оператори порівняння дуже поширені).

Детальніше розкрити відповідь Міхай, яка, на мою думку, є надійним підходом.

До моделей ви можете додати такі сфери застосування:

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'))

Величезною перевагою такого підходу є: (а) він дозволяє складати запити з різних областей і (b) уникає зіткнень з псевдонімом, коли ви двічі приєднуєтесь до однієї таблиці, оскільки arel_table буде обробляти цю частину генерації запитів.


1

Рейки 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)синтаксис). Цього не досягти.
Метью Гінея
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.