我正在尝试为匹配特定日期范围的记录创建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
答案 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}
]
]
})