2010-11-23 3 views
307

Использование рельсам 3 стиля как бы я написать противоположное:Rails, где условие с использованием NOT NULL

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.

+0

'! Nil' оценивает' true' в Ruby, а ARel переводит 'true' в' 1' в SQL-запрос. Таким образом, сгенерированный запрос - это то, о чем вы просили, - это не ошибка AREL. – yuval

ответ

432

канонический способ сделать это с 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)) 

я больше не рекомендую использовать Squeel для этого, так как не всегда есть полный рабочий день сопровождающего, и это зависит от частных API, которые вызывают частые изменения ломки.

+1

Последний здесь не работает для меня, нужен ли нам дополнительный драгоценный камень или плагин для этого? Я получаю: 'rails undefined method 'not_eq' для: confirm_at: Symbol' .. –

+3

@Tim Да, MetaWhere gem я связал выше. –

+3

Мне нравится решение, которое не требует других драгоценных камней :), даже если оно немного уродливое – oreoshake

241

Это не ошибка в 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

+31

Это, ИМО, превосходит принятый ответ. – thekingoftruth

36

Для Rails4:

Итак, что вы, желающих это внутреннее соединение, так что вы действительно должны просто использовать стыки предикат:

Foo.joins(:bar) 

    Select * from Foo Inner Join Bars ... 

Но, для записи, если вы хотите условие «NOT NULL», просто используйте не предикат:

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) 

DEPRECATION WARNING: похоже, что вы загружаете таблицу (ы) (один из: ....), на которые ссылается строковый фрагмент SQL. Например:

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

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

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

Мне помог звонок «ссылки»! – theblang

10

Не уверен в этом, но это то, что сработало для меня в Rails 4

Foo.where.not(bar: nil) 
+0

Это лучший ответ здесь. - 2017 https://robots.thoughtbot.com/activerecords-wherenot – dezman