ActiveRecord присоединяется и где - PullRequest
1 голос
/ 14 марта 2012

У меня есть три модели Company, Deal и Slot.Они связаны как сделки компании has_many и слоты Deal has_many.Срок действия всей компании A может истечь, если срок действия всех ее сделок истек.И срок действия сделки истекает, когда срок действия всех ее слотов истек.

Я написал область действия.

scope :expired,
  lambda { |within|
    self.select(
      'DISTINCT companies.*'
    ).latest(within).joins(
      :user =>{ :deals => :slots }
    ).where(
      "companies.spam = false AND deals.deleted_at IS NULL
        AND deals.spam = false AND slots.state = 1 
        OR slots.begin_at <= :time",
      :time => Time.zone.now + SLOT_EXPIRY_MARGIN.minutes
    )
  }

Приведенная выше область действия не кажется мне правильной из того, что я пытаюсьдостичь.Мне нужны компании со всеми слотами, так как все сделки находятся в состоянии 1 или значение begin_at меньше, чем время истечения.

Спасибо, что заглянули заранее.

1 Ответ

1 голос
/ 15 марта 2012

И имеет более высокий приоритет, чем ИЛИ в 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 (...) исключает их из окончательного набора результатов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...