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?

Respostas:


276

Tente isto

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

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 .


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


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.


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


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)

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.


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.


-3

Mais curta:

User.where("id > 200")

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.