Группировка ands и ors в AREL

#sql #ruby #arel

#sql #ruby #arel

Вопрос:

Я пытаюсь запросить эквивалент этого фрагмента sql с использованием arel:

 WHERE (("participants"."accepted" = 'f' AND "participants"."contact_id" = 1) 
  OR "participants"."id" IS NULL)
  

Итак, я хочу (accepted amp;amp; contact_id=1) OR NULL

Вот что у меня есть в AREL

 participants[:accepted].eq(false).and(participants[:contact_id].eq(1).
  or(participants[:id].is(nil)
  

Проблема в том, что это генерирует:

 ("participants"."accepted" = 'f' AND "participants"."contact_id" = 1 OR "participants"."id" IS NULL)
  

Обратите внимание на отсутствие круглых скобок вокруг условий my и. Я полагаю, что в соответствии с приоритетом оператора, который я получаю:

 accepted amp;amp; (contact_id=1 OR NULL)
  

Добавление круглых скобок в запрос AREL не влияет. Есть мысли?

Ответ №1:

Вы можете сгенерировать круглые скобки с помощью Arel::Nodes::Grouping .

 participants = Arel::Table.new("participants")

arel = participants.grouping(
  participants[:accepted].eq(false).and(participants[:contact_id].eq(1))
).or(participants[:id].eq(nil))

arel.to_sql # => (("participants"."accepted" = 'f' AND "participants"."contact_id" = 1) OR "participants"."id" IS NULL)
  

Комментарии:

1. Спасибо, .группировка сработала отлично. Вместо participants = Arel::Table.new("participants") я использовал participants = Participant.arel_table

2. Это решение работает элегантно и решает проблему под рукой. Это был бы мой предпочтительный ответ на проблему. Хотя вокруг отличные варианты 🙂

Ответ №2:

Я считаю, что в соответствии с приоритетом оператора

Проблема в том, что AND имеет более высокий приоритет, чем OR. So 1 AND 2 OR 3 эквивалентно (1 AND 2) OR 3 .

В качестве дополнительного примечания: если вы используете оболочку, подобную этой, вы можете написать:

 User.where((User[:created_at] > 3.days.ago) amp; (User[:enabled] == true))
  

Комментарии:

1. Это в значительной степени именно то, что дает вам Arel. Почему это слишком низкоуровневый?

2. Хотя Squeel выглядит великолепно (я не знал об этом). Но называть его менее низкоуровневым, чем Arel, на самом деле неточно — это просто модификация синтаксиса.

3. Учитывая контекст, мне не совсем ясно, что «низкий уровень» предназначен для обозначения библиотеки в целом — похоже, что он больше предназначен для обозначения внутренних компонентов, которые писал Аарон.

4. И нет, «низкий уровень» и «высокий уровень» не являются субъективными, по крайней мере, как относительные термины. Вы сказали, что Squeel был более высокого уровня, чем Arel. Насколько я могу судить, это всего лишь синтаксическая модификация Arel и, следовательно, работает точно на том же уровне. Исключением могут быть фильтры, которые абстрагируют Arel и, следовательно, работают на более высоком уровне.

5. ммм … да, ты прав. На самом деле я пытался перевести LEFT OUTER JOIN participants написанное от руки для использования includes(:participants) . Проблема в том, что у меня есть дополнительные условия для моего объединения. Итак, я надеялся, что смогу просто объединить их в where , но они не кажутся эквивалентными. Необходимо изменить мой вопрос

Ответ №3:

Почему бы не перевернуть их. Должно быть эквивалентно:

 participants[:id].is(nil).or(participants[:accepted].eq(false).and(participants[:contact_id].eq(1))
  

надеюсь, я правильно установил скобки в приведенном выше, но вы понимаете, что я имею в виду…

Комментарии:

1. Я думаю, я ошибался насчет необходимости этого, Токланд прав, приоритет — это не моя проблема, на самом деле мой запрос отличается от того, что мне нужно.

Ответ №4:

Вот метод, который позволяет вам использовать существующие AR-методы, такие как where и, возможно, любые существующие области и т.д. В вашем коде. Таким образом, существующий AR-код легко повторно использовать с Arel по мере необходимости.

 class Participant
  scope :grouped, -> {
    group1 = arel_table.grouping(where(accepted: false, contact_id: 1)).arel.constraints
    group2 = arel_table.grouping(where(id: nil).arel.constraints

    where(group1.or(group2))
  }
end
  

Это работает и в Rails 7, и, возможно, в Rails 6.x также.