如何在“日期范围或零日期”查询中创建Mongoid?

时间:2013-07-15 20:36:44

标签: ruby mongoid

我正在尝试为匹配特定日期范围的记录创建Mongoid查询,或者具有nil值。这是我的ruby代码,它执行我希望变成Mongoid查询的函数:

class MyModel
  include Mongoid::Document
  field :name
  field :enabled, type: Boolean, default: false
  field :start_date, type: DateTime
  field :end_date, type: DateTime

  def self.active
    documents = where(enabled: true)
    documents = documents.keep_if {|doc| doc.start_date.nil? || doc.start_date <= Date.today}
    documents = documents.keep_if {|doc| doc.end_date.nil? || doc.end_date >= Date.tomorrow}
    documents
  end
end

如何通过将此方法转换为Mongoid查询来提高性能?

更新

以下是我用来验证正确行为的RSpec测试:

describe '.active' do
  let!(:disabled){ Fabricate(:model, enabled: false, name: 'disabled') }
  let!(:enabled_without_date){ Fabricate(:active_model, name: 'enabled_without_date') }
  let!(:past){ Fabricate(:active_model, start_date: 1.week.ago, end_date: Date.yesterday, name: 'past') }
  let!(:current){ Fabricate(:active_model, start_date: Date.today, end_date: Date.tomorrow, name: 'current') }
  let!(:future){ Fabricate(:active_model, start_date: Date.tomorrow, end_date: 1.week.from_now, name: 'future') }

  it 'returns only enabled and within the current time' do
    MyModel.count.should == 5
    models = MyModel.active.to_a
    models.should_not be_empty
    models.should_not include disabled
    models.should_not include past
    models.should_not include future
    models.should include enabled_without_date
    models.should include current
  end
end

6 个答案:

答案 0 :(得分:3)

如果转换条件:

(start <= X OR start.nil?) AND (end >= Y OR end.nil?)

进入析取形式,你得到:

(start <= X AND end >= Y) OR (start <= X and end.nil?) OR (start.nil? and end >= Y) or (start.nil? and end.nil?)

然后您可以通过单个$或子句来表达:

$or: [
  {:start_date.lte => start_date, :end_date.gte => end_date},
  {:start_date => nil, :end_date.gte => end_date},
  {:start_date.lte => start_date, :end_date => nil},
  {:start_date => nil, :end_date => nil},
]

如果两个值都必须设置为nil(也就是说,你不能有一个set和一个nil),这就变得更简单:

$or: [
  {:start_date.lte => start_date, :end_date.gte => end_date},
  {:start_date => nil},
]

要符合您的规范,完整查询将是:

Model.where({
  :enabled => true,
  :$or => [
    {:start_date.lte => Date.today.to_time, :end_date.gte => Date.tomorrow.to_time},
    {:start_date => nil},
  ]
})

答案 1 :(得分:1)

也许这会解决问题:

where(
  enabled: true,
  {:$or => [{start_date: nil}, {:start_date.lte => Date.today.to_time   }]},
  {:$or => [{end_date:   nil}, {:end_date.gte   => Date.tomorrow.to_time}]}
)

答案 2 :(得分:1)

使用以下代码:

where(:enabled => true, :$or => [{:start_date => nil}, {:start_date =>['$lte' => Date.today]}], :$or => [{:end_date => nil}, {:end_date =>['$gte' =>Date.tomorrow]}])

希望这对你有所帮助。

答案 3 :(得分:0)

使用

Document.where(
  enabled: true,
  '$or' => [
    { start_date: nil },
    { :start_date.lte => Date.today.to_time }
  ],
  '$or' => [
    { end_date: nil },
    { :end_date.gte => Date.tomorrow.to_time }
  ]
).each do |d|
  puts d.inspect
end

我能够得到

[2] pry(main)> load './documents.rb'
#<Document _id: 51e89c690e21d8ab0d9cf012, enabled: true, start_date: nil, end_date: nil>
#<Document _id: 51e8a2e62147b4bfb5f12c65, enabled: true, start_date: 2012-02-01 05:00:00 UTC, end_date: 2014-02-01 05:00:00 UTC>
#<Document _id: 51e8a4797372723f449765bd, enabled: true, start_date: nil, end_date: 2014-02-01 05:00:00 UTC>
=> true

更新

你是对的。我的回答完全是虚假的。第二个$or键将覆盖第一个键。但是,即使用$and包裹整个事物(因此使用哈希数组)也无济于事 - mongoid可能会阻止多个$or条件。

更明亮的是,Chris Heald的解决方案有效。这是一个complete proof。使用rspec mongoid_query.rb运行它。

start_date = Date.today
end_date = Date.tomorrow
where(
  enabled: true,
  :$or => [
    {:start_date.lte => start_date, :end_date.gte => end_date},
    {:start_date => nil, :end_date.gte => end_date},
    {:start_date.lte => start_date, :end_date => nil},
    {:start_date => nil, :end_date => nil},
  ]
)

答案 4 :(得分:0)

到目前为止给出的所有答案对我来说似乎都没问题。但是我会在之前的项目中添加另一个对我有用的语法变体,它也会处理冗余的$或key方案,这对我来说似乎有点可疑(但也许这样做完全没问题)。

Document.where({
   '$and' => [
       :enabled => true,
       '$or' => [
           :start_date => nil,
           :start_date => { '$lte' => Date.today.to_time }
       ],
       '$or' => [
           :end_date => nil,
           :end_date => { '$gte' => Date.tomorrow.to_time }
       ]
    ]
})

就进一步的建议而言:您是否也检查过您的规格? (你永远都不会知道 ... ;))。还要确保仅调试和测试部分问题,例如得到:启用过滤器自己工作,让日期过滤器自己工作,让自己的nil过滤器工作,然后尝试再次组合它们 - 也许这会引导你到问题的核心。此外,我看到了不同的变体,指定$ lte和$ gte在这里进行比较的日期。我自己在提供Time课程方面取得了成功,并确保对其进行实验!

答案 5 :(得分:0)

这似乎是mongodb驱动程序本身的限制! 刚刚使用mongodb java shell我创建了三条记录:

db.so.insert({s:0,e:10})
db.so.insert({s:10,e:20})
db.so.insert({s:20,e:30})

然后尝试了以下查询:

db.so.find({s:{$lte:15}, e:{$gte:15}})  
   ==> finds just the middle record - correct
db.so.find({s:{$lte:15}, $or:[{e:{$exists:false}},{e:{$gte:15}}]})  
   ==> finds just the middle record - correct
db.so.find({$or:[{s:{$exists:false}},{s:{$lte:15}}], $or:[{e:{$exists:false}},{e:{$gte:15}}]})
   ==> finds both the middle and the last record - OOPS
db.so.find({$or:[{s:{$exists:false}},{$and:[{s:{$lte:15}},{e:{$gte:15}}]}]})
   ==> finds just the middle record - correct

我认为这意味着您不能使用多个$或同一级别(因为它表示您可以嵌套它们)。我将在mongodb网站上发布这个问题,看看他们是否同意。 与此同时,在您的情况下,第四个查询指出了可能的工作轮次,尽管Chris Heald已经建议几乎相同:

Model.where({
    :enabled => true,
    :$or => [
        {:start_date => nil},
        :$and => [
            {:start_date.lte => Date.today.to_time}, 
            {:end_date.gte => Date.tomorrow.to_time}
        ]
    ]
})