ActiveRecord加入和在哪里

时间:2012-03-14 19:10:55

标签: mysql ruby-on-rails ruby activerecord ruby-on-rails-3.1

我有三个模特公司,交易和老虎机。它们与公司has_many交易和Deal has_many槽相关联。如果所有A公司的所有交易都已过期,则所有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 个答案:

答案 0 :(得分:1)

AND的优先级高于SQL中的OR,因此您的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 |
+---+

此外,您可能希望在SQL中使用占位符而不是文本false,如果要切换数据库,这应该会使事情变得更容易(但当然,数据库可移植性在很大程度上是一个神话,所以这只是一条建议);你也可以在SQL中使用not。此外,using a class method is the preferred way to accept arguments for scopes。使用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而不是一个大的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

底部的那些肮脏的东西抓住了所有有十个或更多过期或使用过的插槽的公司ID,然后companies.id not in (...)将它们从最终结果集中排除。