Rails: Usando maior que / menos que com uma instrução where

142

Estou tentando encontrar todos os usuários com um ID maior que 200, mas estou tendo problemas com a sintaxe específica.

User.where(:id > 200) 

e

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

ambos falharam.

Alguma sugestão?

Adam Templeton
fonte

Respostas:

276

Tente isto

User.where("id > ?", 200) 
RadBrad
fonte
1
Também confira squeel gem de Ernie Miller
cpuguy83
7
Existe alguma razão para preferir usar ?, em vez de incluir o 200?
precisa saber é o seguinte
20
-lo automaticamente escapa a 200 (se fosse possível para que o usuário insira o valor, evita a possibilidade de ataques de injeção SQL)
user1051849
124

Eu só testei isso no Rails 4, mas há uma maneira interessante de usar um intervalo com um wherehash para obter esse comportamento.

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

irá gerar o SQL

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

O mesmo pode ser feito por menos do que com -Float::INFINITY.

Acabei de postar uma pergunta semelhante sobre como fazer isso com datas aqui no SO .

>= vs >

Para evitar que as pessoas tenham que pesquisar e acompanhar a conversa de comentários, aqui estão os destaques.

O método acima gera apenas uma >=consulta e não a >. Existem muitas maneiras de lidar com essa alternativa.

Para números discretos

Você pode usar uma number_you_want + 1estratégia como a acima, onde me interesso por usuários, id > 200mas na verdade procuro id >= 201. Isso é bom para números inteiros e números em que você pode incrementar por uma única unidade de interesse.

Se você tiver o número extraído em uma constante bem nomeada, isso pode ser o mais fácil de ler e entender rapidamente.

Lógica invertida

Podemos usar o fato de que x > y == !(x <= y)e usar a cadeia where not.

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

que gera o SQL

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

Isso leva um segundo extra para ler e refletir, mas funcionará para valores ou colunas não discretos, nos quais você não pode usar a + 1estratégia.

Mesa Arel

Se você quiser se divertir, pode usar o Arel::Table.

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

irá gerar o SQL

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

Os detalhes são os seguintes:

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`

Essa abordagem fornece o SQL exato em que você está interessado, mas poucas pessoas usam a tabela Arel diretamente e podem achar que é confusa e / ou confusa. Você e sua equipe saberão o que é melhor para você.

Bônus

A partir do Rails 5, você também pode fazer isso com datas!

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

irá gerar o SQL

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

Bônus Duplo

Depois que o Ruby 2.6 for lançado (25 de dezembro de 2018), você poderá usar a nova sintaxe de alcance infinito! Em vez de 201..Float::INFINITYvocê será capaz de escrever 201... Mais informações nesta postagem do blog .

Aaron
fonte
3
Por que isso é superior à resposta aceita, por curiosidade?
mecampbellsoup
5
Superior é enganoso. Em geral, você obtém mais flexibilidade com as consultas do ARel, se é capaz de usar a sintaxe de hash sobre as strings, e é por isso que muitos preferem essa solução. Dependendo do seu projeto / equipe / organização, você pode querer algo mais fácil para alguém que olha o código, qual é a resposta aceita.
Aaron
2
Não acredito que você possa fazer isso usando os wherefósforos básicos . Pois >eu sugiro usar um >= (number_you_want + 1)para simplicidade. Se você realmente deseja garantir que seja apenas uma >consulta, pode acessar a tabela ARel. Toda classe que herda de ActiveRecordpossui um arel_tablemétodo getter que retorna o valor Arel::Tabledessa classe. As colunas na tabela são acessadas com o []método like User.arel_table[:id]. Isso retorna e Arel::Attributes::Attributevocê pode ligar gte transmitir 200. Isso pode ser passado para where. por exemplo User.where(User.arel_table[:id].gt(200)).
Aaron
2
@bluehallu você pode dar um exemplo? O seguinte não está funcionando para mim User.where(created_at: 3.days.ago..DateTime::Infinity.new).
Aaron
1
Ah! Provavelmente, uma nova mudança no Rails 5 está dando a você o comportamento benéfico. No Rails 4.2.5 (Ruby 2.2.2), você obtém uma consulta com WHERE (users.created_at >= '2016-04-09 14:31:15' AND users.created_at < #<Date::Infinity:0x00>)(marcações em volta dos nomes de tabelas e colunas omitidos para formatação de comentários de SO).
Aaron
22

Um uso melhor é criar um escopo no modelo de usuário where(arel_table[:id].gt(id))

Mihai
fonte
6

Se você deseja uma escrita mais intuitiva, existe uma gema chamada squeel que permitirá que você escreva suas instruções assim:

User.where{id > 200}

Observe os caracteres 'chave' {} e idsendo apenas um texto.

Tudo o que você precisa fazer é adicionar um squeel ao seu Gemfile:

gem "squeel"

Isso pode facilitar muito sua vida ao escrever instruções SQL complexas em Ruby.

Douglas
fonte
11
Eu recomendo evitar o uso de rodo. A longo prazo é difícil de manter e às vezes tem um comportamento estranho. Também é buggy com certas versões do Active Record
John Owen Chile
Eu uso o squeel há alguns anos e ainda estou feliz com isso. Mas talvez valha a pena tentar outro ORM, como sequela (<> squeel), por exemplo, que parece promissores recursos agradáveis ​​para substituir o ActiveRecord.
Douglas
5

Arel é seu amigo.

User.where (tabela.arel_usuário [: id] .gt (200))

joegiralt
fonte
4

Outra possibilidade chique é ...

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

Esse recurso permite criar consultas mais compreensíveis se você deseja substituir em vários locais, por exemplo ...

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

Isso tem mais significado do que ter muito ?na consulta ...

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

Muitas vezes tenho esse problema com os campos de data (onde os operadores de comparação são muito comuns).

Para aprofundar a resposta de Mihai, que acredito ser uma abordagem sólida.

Para os modelos, você pode adicionar escopos como este:

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

... e depois no seu controlador, ou onde quer que você esteja usando o seu modelo:

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

... um exemplo mais complexo com junções se parece com isso:

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

Uma grande vantagem dessa abordagem é (a) permitir compor suas consultas de diferentes escopos e (b) evitar colisões de alias quando você ingressar na mesma tabela duas vezes, já que o arel_table tratará essa parte da geração de consultas.

Brett Green
fonte
1

Rails 6.1+

O Rails 6.1 adicionou uma nova 'sintaxe' para operadores de comparação em wherecondições, por exemplo:

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

Portanto, sua consulta pode ser reescrita da seguinte maneira:

User.where('id >': 200) 

Aqui está um link para o PR, onde você pode encontrar mais exemplos.

Marian13
fonte
-3

Mais curta:

User.where("id > 200")
Bengala
fonte
8
Suponho que isso deva ser dinâmico e que o pôster queira evitar a injeção de SQL usando consultas parametrizadas ( where("id > ?", 200)sintaxe). Isso não consegue isso.
Matthew Hinea