Рейки, в яких умова, використовуючи NOT NIL


359

Використовуючи стиль рейки 3, як би я написав протилежне:

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

Я хочу знайти, де id НЕ нульовий. Я намагався:

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

Але це повертається:

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

Це точно не те, що мені потрібно, і майже здається, що помилка в ARel.


2
!nilоцінює trueв Ruby, а ARel переводить trueна 1SQL-запит. Отже, згенерований запит насправді те, про що ви просили - це не помилка ARel.
17:25

Відповіді:


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

12
За здогадкою,! Nil повертається true, що є логічним. :id => trueотримує вас id = 1у SQLese.
зететик

Це хороший спосіб уникнути написання необроблених фрагментів sql. Синтаксис не такий стислий, як Squeel.
Кельвін

1
Мені це не вдалося зробити з sqlite3. sqlite3 хоче бачити field_name != 'NULL'.
mjnissim

@zetetic Якщо ви не використовуєте postgres, тоді ви отримуєте id = 't':)
thekingoftruth

36

Для рейок4:

Отже, те, що ви хочете, - це внутрішнє приєднання, тому вам слід просто використовувати предикат приєднання:

  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)

ПОПЕРЕДЖЕННЯ ПОПЕРЕДЖЕННЯ. Схоже, що ви нетерплячі завантажуєте таблицю (таблицю) (одна з: ....), на які посилається фрагмент 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Виклик допоміг мені!
theblang


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.