Rails: использование больше / меньше чем с выражением where

151

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

User.where(:id > 200) 

а также

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

оба потерпели неудачу.

Какие-либо предложения?

Адам Темплтон
источник

Ответы:

283

Попробуй это

User.where("id > ?", 200) 
RadBrad
источник
1
Также посмотрите Squeel gem от Эрни Миллера
cpuguy83
7
Есть ли причина предпочесть использование ?, а не встраивание 200?
davetapley
22
он автоматически экранирует 200 (если бы пользователь мог ввести значение, это позволяет избежать атак с использованием SQL-инъекций)
user1051849
134

Я тестировал это только в Rails 4, но есть интересный способ использовать диапазон с whereхешем, чтобы получить такое поведение.

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

сгенерирует SQL

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

То же самое можно сделать дешевле, чем с -Float::INFINITY.

Я только что разместил аналогичный вопрос, спрашивая об этом с датами здесь, на SO .

>= против >

Чтобы людям не приходилось копаться и следить за комментариями, вот основные моменты.

Вышеупомянутый метод генерирует только >=запрос, а не файл >. Есть много способов справиться с этой альтернативой.

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

Вы можете использовать number_you_want + 1описанную выше стратегию, когда меня интересуют пользователи, id > 200но я действительно ищу id >= 201. Это нормально для целых и числовых значений, которые можно увеличивать на одну интересующую единицу.

Если у вас есть число, извлеченное в хорошо названную константу, это может быть проще всего для чтения и понимания с первого взгляда.

Обратная логика

Мы можем использовать тот факт, что x > y == !(x <= y)и использовать цепочку where not.

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... Больше информации в этом сообщении блога .

Аарон
источник
1
Этот ответ лучше всего подходит для Rails 4 IMO. Пользуюсь им довольно давно и отлично работает.
Начо Л.
3
Почему это лучше принятого ответа из любопытства?
mecampbellsoup
5
Начальник вводит в заблуждение. В общем, вы достигаете большей гибкости с вашими запросами ARel, если можете использовать синтаксис хеширования вместо строк, поэтому многие предпочли бы это решение. В зависимости от вашего проекта / команды / организации вам может понадобиться что-то, что кому-то будет проще, взглянув на код, чтобы понять, какой ответ принят.
Аарон
2
Я не верю, что вы можете сделать это, используя базовые whereсопоставители. Для простоты >я предлагаю использовать >= (number_you_want + 1). Если вы действительно хотите убедиться, что это всего лишь >запрос, вы можете получить доступ к таблице ARel. Каждый класс, наследующий от, ActiveRecordимеет arel_tableметод получения, который возвращает 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).
Аарон
22

Лучшее использование - создать область видимости в пользовательской модели. where(arel_table[:id].gt(id))

Михай
источник
11

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, где можно найти больше примеров.

Мариан13
источник
Не совсем ясно, будет ли принят этот запрос на перенос. Согласно ( github.com/rails/rails/pull/39613#issuecomment-659553274)[этот комментарий], основная команда должна сначала обсудить проблему.
Фабиан Винклер,
@Fabian Winkler, спасибо за ваше наблюдение. Я слежу за этим запросом на перенос. Если что-то изменится, сразу обновлю этот ответ.
Marian13
7

Арел твой друг:

User.where(User.arel_table[:id].gt(200))
Joegiralt
источник
6

Если вы хотите более интуитивного написания, существует драгоценный камень под названием squeel , который позволит вам написать свою инструкцию следующим образом:

User.where{id > 200}

Обратите внимание на символы "скобки" {} и idэто просто текст.

Все, что вам нужно сделать, это добавить squeel в ваш Gemfile:

gem "squeel"

Это может значительно облегчить вашу жизнь при написании сложных операторов SQL на Ruby.

Дуглас
источник
11
Я рекомендую избегать использования squeel. В долгосрочной перспективе трудно поддерживать и иногда ведет себя странно. Также он содержит ошибки с некоторыми версиями Active Record
Джон Оуэн, Чили,
Я использую 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

У меня часто возникает эта проблема с полями даты (где операторы сравнения очень распространены).

Чтобы подробнее рассказать об ответе Михая, который, я считаю, является надежным подходом.

К моделям можно добавить такие прицелы:

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

Огромным преимуществом этого подхода является (а) он позволяет вам составлять запросы из разных областей и (б) позволяет избежать конфликтов псевдонимов, когда вы дважды присоединяетесь к одной и той же таблице, поскольку arel_table будет обрабатывать эту часть генерации запроса.

Бретт Грин
источник
-3

Короче:

User.where("id > 200")
Бенгала
источник
8
Я предполагаю, что это должно быть динамическим, и что автор хочет избежать SQL-инъекции, используя параметризованные запросы ( where("id > ?", 200)синтаксис). Этого не добиться.
Мэтью