Rails: Używanie większe niż / mniejsze niż z instrukcją where


142

Próbuję znaleźć wszystkich użytkowników o identyfikatorze większym niż 200, ale mam problem z określoną składnią.

User.where(:id > 200) 

i

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

obaj zawiedli.

Jakieś sugestie?

Odpowiedzi:


276

Spróbuj tego

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

1
Sprawdź także klejnot Squeel od Erniego Millera
cpuguy83

7
Czy jest jakiś powód, aby preferować używanie ?zamiast wstawiania 200?
davetapley

20
automatycznie wymyka się z 200 (gdyby użytkownik mógł wprowadzić wartość, unika możliwości ataków SQL injection)
user1051849

124

Testowałem to tylko w Railsach 4, ale jest ciekawy sposób na użycie zakresu z wherehashem, aby uzyskać takie zachowanie.

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

wygeneruje SQL

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

To samo można zrobić za mniej niż za pomocą -Float::INFINITY.

Właśnie opublikowałem podobne pytanie z pytaniem o zrobienie tego z datami tutaj na SO .

>= vs >

Aby ludzie nie musieli przekopywać się i śledzić rozmowy z komentarzami, oto najważniejsze informacje.

Powyższa metoda generuje tylko >=zapytanie, a nie plik >. Istnieje wiele sposobów radzenia sobie z tą alternatywą.

Dla liczb dyskretnych

Możesz użyć number_you_want + 1strategii takiej jak powyżej, w której jestem zainteresowany użytkownikami, id > 200ale faktycznie ich szukam id >= 201. Jest to dobre w przypadku liczb całkowitych i liczb, w przypadku których można zwiększyć o jedną jednostkę zainteresowania.

Jeśli masz wyodrębnioną liczbę do dobrze nazwanej stałej, może to być najłatwiejsze do odczytania i zrozumienia na pierwszy rzut oka.

Logika odwrócona

Możemy wykorzystać fakt, że x > y == !(x <= y)i użyć łańcucha gdzie nie.

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

który generuje SQL

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

Przeczytanie i uzasadnienie zajmie dodatkową sekundę, ale będzie działać dla wartości niedyskretnych lub kolumn, w których nie można użyć + 1strategii.

Stół Arel

Jeśli chcesz uzyskać fantazję, możesz skorzystać z Arel::Table.

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

wygeneruje SQL

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

Szczegóły są następujące:

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`

To podejście pozwoli Ci uzyskać dokładnie ten SQL, który Cię interesuje, jednak niewiele osób używa bezpośrednio tabeli Arel i może uznać ją za nieporządną i / lub mylącą. Ty i Twój zespół będziecie wiedzieć, co jest dla Ciebie najlepsze.

Premia

Począwszy od Rails 5 możesz to również zrobić z datami!

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

wygeneruje SQL

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

Podwójny bonus

Po wydaniu Ruby 2.6 (25 grudnia 2018 r.) Będziesz mógł używać nowej składni nieskończonego zakresu! Zamiast tego 201..Float::INFINITYbędziesz mógł po prostu pisać 201... Więcej informacji w tym poście na blogu .


3
Dlaczego z ciekawości jest to lepsze od zaakceptowanej odpowiedzi?
mecampbellsoup

5
Przełożony wprowadza w błąd. Ogólnie rzecz biorąc, możesz osiągnąć większą elastyczność w zapytaniach ARel, jeśli jesteś w stanie użyć składni skrótu zamiast ciągów, dlatego wielu wolałoby to rozwiązanie. W zależności od projektu / zespołu / organizacji możesz chcieć czegoś, co jest łatwiejsze dla kogoś, kto spojrzy na kod, aby dowiedzieć się, jaka jest akceptowana odpowiedź.
Aaron

2
Nie wierzę, że możesz to zrobić za pomocą podstawowych wheredopasowań. Bo >proponuję użyć >= (number_you_want + 1)dla uproszczenia. Jeśli naprawdę chcesz mieć pewność, że jest to tylko >zapytanie, możesz uzyskać dostęp do tabeli ARel. Każda klasa dziedzicząca po ActiveRecordma arel_tablemetodę pobierającą, która zwraca wartość Arel::Tabledla tej klasy. Dostęp do kolumn w tabeli uzyskuje się za pomocą []metody User.arel_table[:id]. W ten sposób Arel::Attributes::Attributemożesz zadzwonić gti przekazać 200. Można to następnie przekazać where. np User.where(User.arel_table[:id].gt(200)).
Aaron

2
@bluehallu czy możesz podać przykład? Poniższe nie działają dla mnie User.where(created_at: 3.days.ago..DateTime::Infinity.new).
Aaron

1
Ach! Prawdopodobnie jest to nowa zmiana w Rails 5, która zapewnia korzystne zachowanie. W Railsach 4.2.5 (Ruby 2.2.2) otrzymasz zapytanie z WHERE (users.created_at >= '2016-04-09 14:31:15' AND users.created_at < #<Date::Infinity:0x00>)(wsteczne tiki wokół nazw tabel i kolumn pominięte przy formatowaniu komentarzy SO).
Aaron

22

Lepszym zastosowaniem jest utworzenie zakresu w modelu użytkownika where(arel_table[:id].gt(id))


6

Jeśli chcesz bardziej intuicyjnego pisania, istnieje klejnot zwany squeel , który pozwoli ci napisać instrukcje w następujący sposób:

User.where{id > 200}

Zwróć uwagę na znaki „nawiasów klamrowych” {} i idbędące tylko tekstem.

Wszystko, co musisz zrobić, to dodać squeel do swojego Gemfile:

gem "squeel"

Może to znacznie ułatwić życie podczas pisania złożonych instrukcji SQL w języku Ruby.


11
Zalecam unikać używania squeela. Długotrwałe jest trudne do utrzymania i czasami ma dziwne zachowanie. Jest również wadliwy z niektórymi wersjami Active Record
John Owen Chile

Używam squeela od kilku lat i nadal jestem z niego zadowolony. Ale może warto wypróbować inny ORM, na przykład sequel (<> squeel), który wydaje się obiecującym ładnymi funkcjami, które zastąpią ActiveRecord.
Douglas


4

Inną ciekawą możliwością jest ...

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

Ta funkcja pozwala tworzyć bardziej zrozumiałe zapytania, jeśli chcesz zamienić w wielu miejscach, na przykład ...

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

Ma to większe znaczenie niż dużo ?w zapytaniu ...

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

3

Często mam ten problem z polami dat (gdzie operatory porównania są bardzo powszechne).

Aby rozwinąć dalej odpowiedź Mihai, która moim zdaniem jest solidnym podejściem.

Do modeli możesz dodać takie zakresy:

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

... a następnie w kontrolerze lub gdziekolwiek używasz swojego modelu:

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

... bardziej złożony przykład z łączeniami wygląda następująco:

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

Ogromną zaletą tego podejścia jest (a) możliwość komponowania zapytań z różnych zakresów oraz (b) uniknięcie kolizji aliasów przy dwukrotnym dołączaniu do tej samej tabeli, ponieważ arel_table będzie obsługiwać tę część generowania zapytania.


1

Rails 6.1+

Railsy 6.1 dodały nową „składnię” dla operatorów porównania w wherewarunkach, na przykład:

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

Twoje zapytanie można więc przepisać w następujący sposób:

User.where('id >': 200) 

Oto link do PR, gdzie można znaleźć więcej przykładów.


-3

Krótszy:

User.where("id > 200")

8
Zakładam, że ma to być dynamiczne, a plakat chce uniknąć wstrzykiwania SQL za pomocą sparametryzowanych zapytań ( where("id > ?", 200)składnia). To nie pozwala na osiągnięcie tego.
Matthew Hinea,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.