Рельсы, где условие с использованием NOT NIL

359

Используя стиль rails 3, я бы написал противоположность:

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

Я хочу найти, где идентификатор не ноль. Я старался:

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

Но это возвращает:

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

Это определенно не то, что мне нужно, и почти похоже на ошибку в ARel.

SooDesuNe
источник
2
!nilпринимает значение trueв Ruby, и AREL переводит trueк 1в запросе SQL. Таким образом, сгенерированный запрос фактически является тем, о чем вы просили - это не ошибка ARel.
Юваль

Ответы:

510

Канонический способ сделать это с Rails 3:

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

ActiveRecord 4.0 и выше добавляет, where.notчтобы вы могли сделать это:

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

При работе с областями между таблицами я предпочитаю использовать, mergeчтобы мне было проще использовать существующие области.

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

Кроме того, поскольку includesне всегда выбирается стратегия объединения, вам следует использовать и referencesздесь, иначе вы можете получить недопустимый SQL.

Foo.includes(:bar)
   .references(:bar)
   .merge(Bar.where.not(id: nil))
Адам Лассек
источник
1
Последний здесь не работает для меня, нам нужен дополнительный драгоценный камень или плагин для этого? Я получаю: rails undefined method 'not_eq' for :confirmed_at:Symbol..
Тим Баас
3
@Tim Да, драгоценный камень MetaWhere, который я связал выше.
Адам Лассек
3
Мне нравится решение, которое не требует других драгоценных камней :), даже если оно немного уродливо
oreoshake
1
@oreoshake MetaWhere / Squeel хорошо стоит иметь, это всего лишь крошечная грань. Но, конечно, общий случай хорошо знать.
Адам Лассек
1
@BKSpurgeon whereУсловия цепочки - это просто создание AST, оно не попадает в базу данных, пока вы не нажмете на терминальный метод, такой как eachили to_a. Построение запроса не является проблемой производительности; то, что вы запрашиваете из базы данных.
Адам Лассек
251

Это не ошибка в ARel, это ошибка в вашей логике.

Что вы хотите здесь:

Foo.includes(:bar).where(Bar.arel_table[:id].not_eq(nil))
Райан Бигг
источник
2
Мне любопытно, какова логика превращения! Nil в «1»
SooDesuNe
12
По предположению, возвращается! Nil true, что является логическим значением. :id => trueполучает вас id = 1в SQLese.
Zetetic
Это хороший способ избежать написания необработанных фрагментов SQL. Синтаксис не такой лаконичный, как Squeel.
Кельвин
1
Мне не удалось сделать это с sqlite3. sqlite3 хочет увидеть field_name != 'NULL'.
mjnissim
@zetetic Если вы не используете postgres, в этом случае вы получаете id = 't':)
thekingoftruth
36

Для Rails4:

Итак, вам нужно внутреннее соединение, так что вам просто нужно использовать предикат соединений:

  Foo.joins(:bar)

  Select * from Foo Inner Join Bars ...

Но, для записи, если вы хотите условие «НЕ НУЛЬ», просто используйте предикат not:

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

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

Обратите внимание, что этот синтаксис сообщает об устаревании (он говорит о фрагменте SQL строки, но я предполагаю, что условие хеш-функции изменено на строку в синтаксическом анализаторе?), Поэтому обязательно добавьте ссылки в конец:

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

ПРЕДУПРЕЖДЕНИЕ ОБ УСТРАНЕНИИ: Похоже, что вы стремитесь загрузить таблицы (таблицы), на которые есть ссылки в строковом фрагменте SQL. Например:

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

В настоящее время Active Record распознает таблицу в строке и знает, как присоединить таблицу комментариев к запросу, а не загружать комментарии в отдельном запросе. Тем не менее, выполнение этого без написания полноценного парсера SQL по своей сути ошибочно. Поскольку мы не хотим писать анализатор SQL, мы удаляем эту функциональность. Отныне вы должны явно указывать Active Record, когда ссылаетесь на таблицу из строки:

Post.includes(:comments).where("comments.title = 'foo'").references(:comments)
Мэтт Рогиш
источник
1
referencesВызов помог мне!
17