И имеет более высокий приоритет, чем ИЛИ в SQL, поэтому ваш where
фактически анализируется следующим образом:
(
companies.spam = false
and deals.deleted_at is null
and deals.spam = false
and slots.state = 1
)
or slots.begin_at <= :time
Например (немного урезан для краткости):
mysql> select 1 = 2 and 3 = 4 or 5 = 5;
+---+
| 1 |
+---+
mysql> select (1 = 2 and 3 = 4) or 5 = 5;
+---+
| 1 |
+---+
mysql> select 1 = 2 and (3 = 4 or 5 = 5);
+---+
| 0 |
+---+
Кроме того, вы можете захотеть использовать заполнитель вместо литерала false
в SQL, что должно упростить задачу, если вы хотите переключать базы данных (но, конечно, переносимость баз данных в значительной степени миф, так что это всего лишь совет);Вы также можете просто использовать not
в SQL.Кроме того, с использованием метода класса является предпочтительным способом приема аргументов для областей действия .Использование scoped
вместо self
также является хорошей идеей, если другие области уже находятся в игре, но если вы используете метод класса, вам не нужно об этом заботиться.
Если мы исправим группировку вваш SQL с некоторыми круглыми скобками, используйте заполнитель для false
и переключитесь на метод класса:
def self.expired(within)
select('distinct companies.*').
latest(within).
joins(:user => { :deals => :slots }).
where(%q{
not companies.spam
and not deals.spam
and deals.deleted_at is null
and (slots.state = 1 or slots.begin_at <= :time)
}, :time => Time.zone.now + SLOT_EXPIRY_MARGIN.minutes)
end
Вы также можете написать это так, если вы предпочитаете маленькие двоичные объекты SQL вместо одного большого:
def self.expired(within)
select('distinct companies.*').
latest(within).
joins(:user => { :deals => :slots }).
where('not companies.spam').
where('not deals.spam').
where('deals.deleted_at is null').
where('slots.state = 1 or slots.begin_at <= :time', :time => Time.zone.now + SLOT_EXPIRY_MARGIN.minutes)
end
Этот вопрос также аккуратно обходит вашу проблему с «отсутствующими скобками».
ОБНОВЛЕНИЕ : Основываясь на обсуждении в комментариях, я думаю, вы 'после чего-то вроде этого:
def self.expired(within)
select('distinct companies.*').
latest(within).
joins(:user => :deals).
where('not companies.spam').
where('not deals.spam').
where('deals.deleted_at is null').
where(%q{
companies.id not in (
select company_id
from slots
where state = 1
and begin_at <= :time
group by company_id
having count(*) >= 10
)
}, :time => Time.zone.now + SLOT_EXPIRY_MARGIN.minutes
end
Этот бит злобности внизу захватывает все идентификаторы компании, у которых есть десять или более слотов с истекшим или использованным временем, а затем companies.id not in (...)
исключает их из окончательного набора результатов.