Szyny, w których warunek jest użyty NIE NIL

359

Używając stylu szyn 3, jak napisałbym przeciwieństwo:

Foo.includes(:bar).where(:bars=>{:id=>nil})

Chcę dowiedzieć się, gdzie identyfikator NIE jest zerowy. Próbowałem:

Foo.includes(:bar).where(:bars=>{:id=>!nil}).to_sql

Ale to zwraca:

=> "SELECT     \"foos\".* FROM       \"foos\"  WHERE  (\"bars\".\"id\" = 1)"

To zdecydowanie nie jest to, czego potrzebuję i prawie wydaje się błędem w ARel.

SooDesuNe
źródło
2
!nilZwraca truew Ruby i AREL przekłada truesię 1w zapytaniu SQL. Tak więc wygenerowane zapytanie jest tym, o co prosiłeś - nie był to błąd ARel.
yuval

Odpowiedzi:

510

Kanoniczny sposób, aby to zrobić za pomocą Rails 3:

Foo.includes(:bar).where("bars.id IS NOT NULL")

ActiveRecord 4.0 i wyżej dodaje where.not, abyś mógł to zrobić:

Foo.includes(:bar).where.not('bars.id' => nil)
Foo.includes(:bar).where.not(bars: { id: nil })

Podczas pracy z zakresami między tabelami wolę korzystać z dźwigni, mergeaby łatwiej korzystać z istniejących zakresów.

Foo.includes(:bar).merge(Bar.where.not(id: nil))

Ponadto, ponieważ includesnie zawsze wybiera strategię łączenia, należy również użyć referencestutaj, w przeciwnym razie może dojść do nieprawidłowego SQL.

Foo.includes(:bar)
   .references(:bar)
   .merge(Bar.where.not(id: nil))
Adam Lassek
źródło
1
Ostatni tutaj nie działa dla mnie, czy potrzebujemy do tego dodatkowego klejnotu lub wtyczki? Dostaję: rails undefined method 'not_eq' for :confirmed_at:Symbol..
Tim Baas,
3
@Tim Tak, klejnot MetaWhere, który podłączyłem powyżej.
Adam Lassek
3
Podoba mi się rozwiązanie, które nie wymaga innych klejnotów :) nawet jeśli jest trochę brzydkie
oreoshake
1
@oreoshake MetaWhere / Squeel są warte posiadania, to tylko mały aspekt. Ale oczywiście ogólny przypadek jest dobrze znany.
Adam Lassek
1
@BKSpurgeon whereWarunki łączenia są po prostu budowaniem AST, nie trafiają do bazy danych, dopóki nie trafisz na terminalową metodę, taką jak eachlub to_a. Tworzenie zapytania nie ma wpływu na wydajność; o to prosisz z bazy danych.
Adam Lassek,
251

To nie jest błąd w ARel, to błąd w twojej logice.

Tutaj chcesz:

Foo.includes(:bar).where(Bar.arel_table[:id].not_eq(nil))
Ryan Bigg
źródło
2
Jestem ciekawy, jaka jest logika zamiany!
Zero
12
Domyślnie, zwraca! Nil true, co jest wartością logiczną. :id => truedostanie cię id = 1w SQLese.
zetetic
Jest to dobry sposób na uniknięcie pisania surowych fragmentów sql. Składnia nie jest jednak tak zwięzła jak Squeel.
Kelvin
1
Nie udało mi się tego zrobić z sqlite3. sqlite3 chce zobaczyć field_name != 'NULL'.
mjnissim
@zetetic O ile nie używasz postgresu, w takim przypadku dostajesz id = 't':)
thekingoftruth
36

Dla Rails4:

Więc to, czego chcesz, to połączenie wewnętrzne, więc naprawdę powinieneś po prostu użyć predykatu złączeń:

  Foo.joins(:bar)

  Select * from Foo Inner Join Bars ...

Ale, dla przypomnienia, jeśli chcesz warunek „NOT NULL”, po prostu użyj predykatu:

Foo.includes(:bar).where.not(bars: {id: nil})

Select * from Foo Left Outer Join Bars on .. WHERE bars.id IS NOT NULL

Zauważ, że ta składnia zgłasza wycofanie (mówi o fragmencie kodu SQL, ale myślę, że warunek skrótu został zmieniony na string w parserze?), Więc pamiętaj, aby dodać odwołania na końcu:

Foo.includes(:bar).where.not(bars: {id: nil}).references(:bar)

OSTRZEŻENIE DOTYCZĄCE DEPRECACJI: Wygląda na to, że chętnie ładujesz tabele (jeden z: ....), do których odwołuje się ciąg kodu SQL. Na przykład:

Post.includes(:comments).where("comments.title = 'foo'")

Obecnie Active Record rozpoznaje tabelę w ciągu i wie, DOŁĄCZYĆ tabelę komentarzy do zapytania, zamiast ładować komentarze w oddzielnym zapytaniu. Jednak robienie tego bez pisania pełnego parsera SQL jest z natury wadliwe. Ponieważ nie chcemy pisać analizatora składni SQL, usuwamy tę funkcję. Odtąd musisz jawnie informować Active Record, gdy odwołujesz się do tabeli z ciągu:

Post.includes(:comments).where("comments.title = 'foo'").references(:comments)
Matt Rogish
źródło
1
referencesRozmowa pomogła mi!
theblang
27

Nie jestem pewien, czy to jest pomocne, ale to, co działało dla mnie w Rails 4

Foo.where.not(bar: nil)
Raed Tulefat
źródło
1
To najlepsza odpowiedź tutaj. - 2017 robots.thoughtbot.com/activerecords-wherenot
dezman