查询
在本页
Mongoid 提供了一个由 ActiveRecord 启发的丰富查询 DSL。一个简单的查询看起来如下
Band.where(name: "Depeche Mode")
利用 Mongoid 各种功能的更复杂查询可能如下所示
Band. where(:founded.gte => "1980-01-01"). in(name: [ "Tool", "Deftones" ]). union. in(name: [ "Melvins" ])
查询方法返回Mongoid::Criteria
对象,它们是 MongoDB 查询语言 (MQL) 的可链式和惰性评估包装器。查询在迭代其结果集时执行。例如
# Construct a Criteria object: Band.where(name: 'Deftones') # => #<Mongoid::Criteria # selector: {"name"=>"Deftones"} # options: {} # class: Band # embedded: false> # Evaluate the query and get matching documents: Band.where(name: 'Deftones').to_a # => [#<Band _id: 5ebdeddfe1b83265a376a760, name: "Deftones", description: nil>]
像 first
和 last
这样的方法会立即返回单个文档。否则,使用像 each
或 map
这样的方法迭代 Criteria 对象会从服务器检索文档。to_a
可以用来强制执行返回文档数组的查询,实际上将 Criteria 对象转换为数组。
当在 Criteria 实例上调用查询方法时,该方法返回一个新的 Criteria 实例,其中将新条件添加到现有条件中
scope = Band.where(:founded.gte => "1980-01-01") # => #<Mongoid::Criteria # selector: {"founded"=>{"$gte"=>"1980-01-01"}} # options: {} # class: Band # embedded: false> scope.where(:founded.lte => "2020-01-01") # => #<Mongoid::Criteria # selector: {"founded"=>{"$gte"=>"1980-01-01", "$lte"=>"2020-01-01"}} # options: {} # class: Band # embedded: false> scope # => #<Mongoid::Criteria # selector: {"founded"=>{"$gte"=>"1980-01-01"}} # options: {} # class: Band # embedded: false>
条件语法
Mongoid 支持指定单个条件的三种方式
字段语法。
MQL 语法。
符号运算符语法。
所有语法都支持使用点表示法查询嵌套文档。所有语法都尊重字段类型,如果查询的字段在模型类中定义,以及字段别名。
本节中的示例使用以下模型定义
class Band include Mongoid::Document field :name, type: String field :founded, type: Integer field :m, as: :member_count, type: Integer embeds_one :manager end class Manager include Mongoid::Document embedded_in :band field :name, type: String end
字段语法
最简单的查询语法使用了基本的Ruby散列表。键可以是符号或字符串,并对应于MongoDB文档中的字段名称
Band.where(name: "Depeche Mode") # => #<Mongoid::Criteria # selector: {"name"=>"Depeche Mode"} # options: {} # class: Band # embedded: false> # Equivalent to: Band.where("name" => "Depeche Mode")
MQL语法
可以使用散列语法在任意字段上指定MQL运算符
Band.where(founded: {'$gt' => 1980}) # => #<Mongoid::Criteria # selector: {"founded"=>{"$gt"=>1980}} # options: {} # class: Band # embedded: false> # Equivalent to: Band.where('founded' => {'$gt' => 1980})
符号运算符语法
MQL运算符可以作为相应字段名称的符号上的方法指定,如下所示
Band.where(:founded.gt => 1980) # => #<Mongoid::Criteria # selector: {"founded"=>{"$gt"=>1980}} # options: {} # class: Band # embedded: false>
字段
在定义字段上查询
为了对一个字段进行查询,没有必要将其添加到模型类定义中。然而,如果字段在模型类中定义,Mongoid将会在构建查询时将查询值强制转换为定义的字段类型
Band.where(name: 2020, founded: "2020") # => #<Mongoid::Criteria # selector: {"name"=>"2020", "founded"=>2020} # options: {} # class: Band # embedded: false>
查询原始值
如果您想绕过Mongoid的查询类型强制行为并直接在数据库中查询原始类型值,请将查询值包装在Mongoid::RawValue
类中。这对于处理旧数据很有用。
Band.where(founded: Mongoid::RawValue("2020")) # => #<Mongoid::Criteria # selector: {"founded"=>"2020"} # options: {} # class: Band # embedded: false>
字段别名
Band.where(name: 'Astral Projection') # => #<Mongoid::Criteria # selector: {"n"=>"Astral Projection"} # options: {} # class: Band # embedded: false>
由于 id
和 _id
字段是别名,查询时可以使用任一一个。
Band.where(id: '5ebdeddfe1b83265a376a760') # => #<Mongoid::Criteria # selector: {"_id"=>BSON::ObjectId('5ebdeddfe1b83265a376a760')} # options: {} # class: Band # embedded: false>
嵌套文档
为了匹配嵌套文档字段的值,请使用点符号表示法。
Band.where('manager.name' => 'Smith') # => #<Mongoid::Criteria # selector: {"manager.name"=>"Smith"} # options: {} # class: Band # embedded: false> Band.where(:'manager.name'.ne => 'Smith') # => #<Mongoid::Criteria # selector: {"manager.name"=>{"$ne"=>"Smith"}} # options: {} # class: Band # embedded: false>
注意
查询总是返回顶层模型实例,即使所有条件都引用嵌套文档。
逻辑操作
Mongoid 支持在 Criteria
对象上执行 and
、or
、nor
和 not
逻辑操作。这些方法接受一个或多个条件哈希或另一个 Criteria
对象作为参数,其中 not
还有一个无参数版本。
# and with conditions Band.where(label: 'Trust in Trance').and(name: 'Astral Projection') # or with scope Band.where(label: 'Trust in Trance').or(Band.where(name: 'Astral Projection')) # not with conditions Band.not(label: 'Trust in Trance', name: 'Astral Projection') # argument-less not Band.not.where(label: 'Trust in Trance', name: 'Astral Projection')
为了与早期 Mongoid 版本保持向后兼容,所有逻辑操作方法也接受参数数组,这些参数将被展平以获取条件。将数组传递给逻辑操作已弃用,可能会在 Mongoid 的未来版本中删除。
以下所有调用都产生相同的查询条件
# Condition hashes passed to separate and invocations Band.and(name: 'SUN Project').and(member_count: 2) # Multiple condition hashes in the same and invocation Band.and({name: 'SUN Project'}, {member_count: 2}) # Multiple condition hashes in an array - deprecated Band.and([{name: 'SUN Project'}, {member_count: 2}]) # Condition hash in where and a scope Band.where(name: 'SUN Project').and(Band.where(member_count: 2)) # Condition hash in and and a scope Band.and({name: 'SUN Project'}, Band.where(member_count: 2)) # Scope as an array element, nested arrays - deprecated Band.and([Band.where(name: 'SUN Project'), [{member_count: 2}]]) # All produce: # => #<Mongoid::Criteria # selector: {"name"=>"SUN Project", "member_count"=>2} # options: {} # class: Band # embedded: false>
运算符组合
从 Mongoid 7.1 版本开始,逻辑运算符(and
、or
、nor
和 not
)的语义已更改,与以下相同ActiveRecord的语义。为了获得与Mongoid 7.0及更早版本中行为的or
语义,请使用下面描述的any_of
。
当同一字段上多次指定条件时,所有条件都将添加到标准中。
Band.where(name: 1).where(name: 2).selector # => {"name"=>"1", "$and"=>[{"name"=>"2"}]} Band.where(name: 1).or(name: 2).selector # => {"$or"=>[{"name"=>"1"}, {"name"=>"2"}]}
any_of
、none_of
、nor
和not
的行为类似,其中not
将产生如以下描述的不同查询形状。
当使用逻辑运算符and
、or
和nor
时,它们作用于到那个点构建的标准及其参数。where
与and
有相同的意义
# or joins the two conditions Band.where(name: 'Sun').or(label: 'Trust').selector # => {"$or"=>[{"name"=>"Sun"}, {"label"=>"Trust"}]} # or applies only to the first condition, the second condition is added # to the top level as $and Band.or(name: 'Sun').where(label: 'Trust').selector # => {"$or"=>[{"name"=>"Sun"}], "label"=>"Trust"} # Same as previous example - where and and are aliases Band.or(name: 'Sun').and(label: 'Trust').selector # => {"$or"=>[{"name"=>"Sun"}], "label"=>"Trust"} # Same operator can be stacked any number of times Band.or(name: 'Sun').or(label: 'Trust').selector # => {"$or"=>[{"name"=>"Sun"}, {"label"=>"Trust"}]} # The label: Foo condition is added to the top level as $and Band.where(name: 'Sun').or(label: 'Trust').where(label: 'Foo').selector # => {"$or"=>[{"name"=>"Sun"}, {"label"=>"Trust"}], "label"=>"Foo"}
and
行为
and
方法将新简单条件添加到标准的顶层,除非接收的标准已经在相应字段上具有条件,在这种情况下,条件将使用$and
组合。
Band.where(label: 'Trust in Trance').and(name: 'Astral Projection').selector # => {"label"=>"Trust in Trance Records", "name"=>"Astral Projection"} Band.where(name: /Best/).and(name: 'Astral Projection').selector # => {"name"=>/Best/, "$and"=>[{"name"=>"Astral Projection"}]}
从Mongoid 7.1版本开始,使用and
在相同字段上指定多个标准会将所有指定标准组合在一起,而在Mongoid的先前版本中,字段上的条件有时会替换先前在同一字段上指定的条件,这取决于使用了哪种形式的and
。
或
/非
行为 _----------------------
或
和 非
分别产生 MongoDB 操作符 $or
和 $nor
,使用接收者和所有参数作为操作数。例如
Band.where(name: /Best/).or(name: 'Astral Projection') # => {"$or"=>[{"name"=>/Best/}, {"name"=>"Astral Projection"}]} Band.where(name: /Best/).and(name: 'Astral Projection'). or(Band.where(label: /Records/)).and(label: 'Trust').selector # => {"$or"=>[{"name"=>/Best/, "$and"=>[{"name"=>"Astral Projection"}]}, {"label"=>/Records/}], "label"=>"Trust"}
如果接收者上只有另一个 或
/非
条件,则新条件将添加到现有列表中
Band.where(name: /Best/).or(name: 'Astral Projection'). or(Band.where(label: /Records/)).selector # => {"$or"=>[{"name"=>/Best/}, {"name"=>"Astral Projection"}, {"label"=>/Records/}]}
使用 any_of
在保持现有条件不变的情况下,将析取条件添加到 Criteria 对象中
any_of
行为
any_of
将其参数构建的析取条件添加到 Criteria 对象中的现有条件。例如
Band.where(label: /Trust/).any_of({name: 'Astral Projection'}, {name: /Best/}) # => {"label"=>/Trust/, "$or"=>[{"name"=>"Astral Projection"}, {"name"=>/Best/}]}
如果可能,将条件提升到顶级
Band.where(label: /Trust/).any_of({name: 'Astral Projection'}) # => {"label"=>/Trust/, "name"=>"Astral Projection"}
none_of
行为
none_of
将其参数构建的否定析取条件("非")添加到 Criteria 对象中的现有条件。例如
Band.where(label: /Trust/).none_of({name: 'Astral Projection'}, {name: /Best/}) # => {"label"=>/Trust/, "$nor"=>[{"name"=>"Astral Projection"}, {"name"=>/Best/}]}
not
行为
not
方法可以不传参数调用,在这种情况下,它将否定指定的下一个条件。not
还可以与一个或多个哈希条件或 Criteria
对象一起调用,这些条件将被否定并添加到条件中。
# not negates subsequent where Band.not.where(name: 'Best').selector # => {"name"=>{"$ne"=>"Best"}} # The second where is added as $and Band.not.where(name: 'Best').where(label: /Records/).selector # => {"name"=>{"$ne"=>"Best"}, "label"=>/Records/} # not negates its argument Band.not(name: 'Best').selector # => {"name"=>{"$ne"=>"Best"}}
注意
在 MongoDB 服务器中,$not
不能与字符串参数一起使用。Mongoid 使用 $ne
操作符来实现这种否定。
# String negation - uses $ne Band.not.where(name: 'Best').selector # => {"name"=>{"$ne"=>"Best"}} # Regexp negation - uses $not Band.not.where(name: /Best/).selector # => {"name"=>{"$not"=>/Best/}}
与 and
类似,not
将否定简单字段标准中的单个条件。对于复杂条件和当字段上已经定义了条件时,由于 MongoDB 服务器仅支持基于每个字段的 $not
操作符而不是全局,Mongoid 通过使用 {'$and' => [{'$nor' => ...}]}
构造来模拟 $not
。
# Simple condition Band.not(name: /Best/).selector # => {"name"=>{"$not"=>/Best/}} # Complex conditions Band.where(name: /Best/).not(name: 'Astral Projection').selector # => {"name"=>/Best/, "$and"=>[{"$nor"=>[{"name"=>"Astral Projection"}]}]} # Symbol operator syntax Band.not(:name.ne => 'Astral Projection') # => #<Mongoid::Criteria # selector: {"$and"=>[{"$nor"=>[{"name"=>{"$ne"=>"Astral Projection"}}]}]} # options: {} # class: Band # embedded: false>
如果使用 not
与数组或正则表达式,请注意 $not
在 MongoDB 服务器文档中 所述的注意事项/限制。
增量查询构建
默认情况下,当条件被添加到查询中时,Mongoid 考虑每个条件都是完整且独立的,与查询中可能存在的其他任何条件无关。例如,调用 in
两次会添加两个独立的 $in
条件。
Band.in(name: ['a']).in(name: ['b']) => #<Mongoid::Criteria selector: {"name"=>{"$in"=>["a"]}, "$and"=>[{"name"=>{"$in"=>["b"]}}]} options: {} class: Band embedded: false>
某些操作方法支持增量构建条件。在这种情况下,当添加使用受支持的运算符的字段条件时,如果该字段已存在使用同一运算符的条件,则根据指定的 合并策略 合并运算符表达式。
合并策略
Mongoid 提供了三种合并策略。
覆盖:新的操作符实例将使用相同操作符在该字段上替换任何现有条件。
交集:如果该字段上已经存在使用相同操作符的条件,现有条件的值将与新条件的值相交,并将结果存储为操作符值。
并集:如果该字段上已经存在使用相同操作符的条件,新条件的值将添加到现有条件的值中,并将结果存储为操作符值。
以下代码片段展示了所有策略,以 in
作为示例操作符
Band.in(name: ['a']).override.in(name: ['b']) => #<Mongoid::Criteria selector: {"name"=>{"$in"=>["b"]}} options: {} class: Band embedded: false> Band.in(name: ['a', 'b']).intersect.in(name: ['b', 'c']) => #<Mongoid::Criteria selector: {"name"=>{"$in"=>["b"]}} options: {} class: Band embedded: false> Band.in(name: ['a']).union.in(name: ['b']) => #<Mongoid::Criteria selector: {"name"=>{"$in"=>["a", "b"]}} options: {} class: Band embedded: false>
通过在 Criteria
实例上调用 override
、intersect
或 union
来请求策略。请求的策略将应用于查询中下一个调用的条件方法。如果下一个调用的条件方法不支持合并策略,则策略将被重置,如下例所示
Band.in(name: ['a']).union.ne(name: 'c').in(name: ['b']) => #<Mongoid::Criteria selector: {"name"=>{"$in"=>["a"], "$ne"=>"c"}, "$and"=>[{"name"=>{"$in"=>["b"]}}]} options: {} class: Band embedded: false>
由于 ne
不支持合并策略,因此 union
策略被忽略并重置,当第二次调用 in
时没有激活任何策略。
警告
合并策略目前假定之前的条件已被添加到查询的最顶层,但实际上情况并非总是如此(条件可能位于 $and
子句之下)。使用复杂条件与合并策略可能会导致构建不正确的查询。这种行为 计划在未来修复。
支持的操作符方法
以下操作符方法支持合并策略
all
in
nin
此方法集合可能在 Mongoid 的未来版本中扩展。为了未来兼容性,只有当下一个方法调用是一个支持合并策略的操作符时,才调用策略方法。
请注意,合并策略目前仅在通过指定方法添加条件时应用。在以下示例中,未应用合并策略,因为第二个条件是通过where
添加的,而不是通过in
Band.in(foo: ['a']).union.where(foo: {'$in' => 'b'}) => #<Mongoid::Criteria selector: {"foo"=>{"$in"=>["a"]}, "$and"=>[{"foo"=>{"$in"=>"b"}}]} options: {} class: Band embedded: false>
此行为可能在 Mongoid 的未来版本中发生变化,不应依赖于此。
相比之下,当调用支持合并策略的操作符方法时,现有查询是如何构建的并不重要。在以下示例中,第一个条件是通过where
添加的,但策略机制仍然适用
Band.where(foo: {'$in' => ['a']}).union.in(foo: ['b']) => #<Mongoid::Criteria selector: {"foo"=>{"$in"=>["a", "b"]}} options: {} class: Band embedded: false>
操作符值扩展
所有支持合并策略的操作符方法都接受Array
作为它们的值类型。Mongoid 扩展与这些操作符方法一起使用的与Array
兼容的类型,例如Range
Band.in(year: 1950..1960) => #<Mongoid::Criteria selector: {"year"=>{"$in"=>[1950, 1951, 1952, 1953, 1954, 1955, 1956, 1957, 1958, 1959, 1960]}} options: {} class: Band embedded: false>
此外,Mongoid 一直将非Array
值包裹在数组中,以下示例演示了这一点
Band.in(year: 1950) => #<Mongoid::Criteria selector: {"year"=>{"$in"=>[1950]}} options: {} class: Band embedded: false>
查询方法
elem_match
此匹配器查找具有数组字段的文档,其中数组值之一符合所有条件。例如:
class Band include Mongoid::Document field :name, type: String field :tours, type: Array end aerosmith = Band.create!(name: 'Aerosmith', tours: [ {city: 'London', year: 1995}, {city: 'New York', year: 1999}, ]) Band.elem_match(tours: {city: 'London'}).to_a # => [aerosmith]
elem_match
还可以与嵌入式关联一起使用。
class Band include Mongoid::Document field :name, type: String embeds_many :tours end class Tour include Mongoid::Document field :city, type: String field :year, type: Integer embedded_in :band end dm = Band.create!(name: 'Depeche Mode') aerosmith = Band.create!(name: 'Aerosmith') Tour.create!(band: aerosmith, city: 'London', year: 1995) Tour.create!(band: aerosmith, city: 'New York', year: 1999) Band.elem_match(tours: {city: 'London'}).to_a # => [aerosmith]
elem_match
不能与非嵌入式关联一起使用,因为 MongoDB 没有连接操作 - 条件将被添加到非嵌入式关联的源集合,而不是关联目标集合。
如以下示例所示,elem_match
还可以与递归嵌入式关联一起使用。
class Tag include Mongoid::Document field :name, type: String recursively_embeds_many end root = Tag.create!(name: 'root') sub1 = Tag.new(name: 'sub1', child_tags: [Tag.new(name: 'subsub1')]) root.child_tags << sub1 root.child_tags << Tag.new(name: 'sub2') root.save! Tag.elem_match(child_tags: {name: 'sub1'}).to_a # => [root] root.child_tags.elem_match(child_tags: {name: 'subsub1'}).to_a # => [sub1]
投影
Mongoid 提供了两个投影操作符:only
和 without
。
only
only
方法从数据库中检索指定的字段。此操作有时称为“投影”。
class Band include Mongoid::Document field :name, type: String field :label, type: String embeds_many :tours end class Tour include Mongoid::Document field :city, type: String field :year, type: Integer embedded_in :band end band = Band.only(:name).first
尝试引用尚未加载的属性会导致 Mongoid::Errors::AttributeNotLoaded
。
band.label #=> raises Mongoid::Errors::AttributeNotLoaded
尽管 Mongoid 目前允许写入尚未加载的属性,但这种写入不会持久化(MONGOID-4701)因此应该避免。
only
也可以与嵌套关联一起使用。
band = Band.only(:name, 'tours.year').last # => #<Band _id: 5c59afb1026d7c034dba46ac, name: "Aerosmith"> band.tours.first # => #<Tour _id: 5c59afdf026d7c034dba46af, city: nil, year: 1995>
注意
服务器版本 4.2 及以下版本允许在同一个查询中投影关联及其字段,如下所示。
band = Band.only(:tours, 'tours.year').last
最新的投影规范覆盖了之前的规范。例如,上述查询等价于。
band = Band.only('tours.year').last
服务器版本 4.4 及以上版本禁止在同一个查询中指定关联及其字段。
only
可以与引用关联(has_one、has_many、has_and_belongs_to_many)一起使用,但当前对引用关联忽略 - 将加载引用关联的所有字段(MONGOID-4704)。
请注意,如果一个文档有 has_one
或 has_and_belongs_to_many
关联,则必须将外键字段包含在 only
加载的属性列表中,以便加载这些关联。例如。
class Band include Mongoid::Document field :name, type: String has_and_belongs_to_many :managers end class Manager include Mongoid::Document has_and_belongs_to_many :bands end band = Band.create!(name: 'Astral Projection') band.managers << Manager.new Band.where(name: 'Astral Projection').only(:name).first.managers # => [] Band.where(name: 'Astral Projection').only(:name, :manager_ids).first.managers # => [#<Manager _id: 5c5dc2f0026d7c1730969843, band_ids: [BSON::ObjectId('5c5dc2f0026d7c1730969842')]>]
without
与only
相反,without
会导致指定的字段被省略
Band.without(:name) # => # #<Mongoid::Criteria # selector: {} # options: {:fields=>{"name"=>0}} # class: Band # embedded: false>
因为Mongoid需要_id
字段进行各种操作,所以它(以及它的id
别名)不能通过without
来省略
Band.without(:name, :id) # => # #<Mongoid::Criteria # selector: {} # options: {:fields=>{"name"=>0}} # class: Band # embedded: false> Band.without(:name, :_id) # => # #<Mongoid::Criteria # selector: {} # options: {:fields=>{"name"=>0}} # class: Band # embedded: false>
排序
Mongoid在Criteria
对象上提供了order
方法及其别名order_by
,以指定文档的排序。这些方法接受一个哈希,表示要按哪些字段排序文档,以及是否对每个字段使用升序或降序。
Band.order(name: 1) # => #<Mongoid::Criteria # selector: {} # options: {:sort=>{"name"=>1}} # class: Band # embedded: false> Band.order_by(name: -1, description: 1) # => #<Mongoid::Criteria # selector: {} # options: {:sort=>{"name"=>-1, "description"=>1}} # class: Band # embedded: false> Band.order_by(name: :desc, description: 'asc') # => #<Mongoid::Criteria # selector: {} # options: {:sort=>{"name"=>-1, "description"=>1}} # class: Band # embedded: false>
方向可以指定为整数1
和-1
,分别表示升序和降序,也可以指定为符号:asc
和:desc
,或者为字符串"asc"
和"desc"
。
或者,order
接受一个二维数组,指定排序顺序。字段名和方向可以是字符串或符号。
Band.order([['name', 'desc'], ['description', 'asc']]) Band.order([[:name, :desc], [:description, :asc]])
另一种提供排序的方式是使用符号上的#asc
和#desc
方法,如下所示
Band.order(:name.desc, :description.asc)
参数可以用SQL语法提供的字符串形式提供
Band.order('name desc, description asc')
最后,有asc
和desc
方法可以用来替代order
/order_by
Band.asc('name').desc('description') # => #<Mongoid::Criteria selector: {} options: {:sort=>{"name"=>1, "description"=>-1}} class: Band embedded: false>
order
调用可以被链接,在这种情况下,最旧的调用定义了最重要的标准,而最新的调用定义了最不重要的标准(因为Ruby字典保持其键的顺序)
Band.order('name desc').order('description asc') # => #<Mongoid::Criteria selector: {} options: {:sort=>{"name"=>-1, "description"=>1}} class: Band embedded: false>
如果存在使用order
/order_by
的scop,包括默认scop,这有时会导致令人惊讶的结果。例如,在下面的代码片段中,乐队首先按名称排序,因为默认scop中的顺序优先于查询中给出的顺序,因为默认scop首先被评估
class Band include Mongoid::Document field :name, type: String field :year, type: Integer default_scope -> { order(name: :asc) } end Band.order(year: :desc) # => #<Mongoid::Criteria selector: {} options: {:sort=>{"name"=>1, "year"=>-1}} class: Band embedded: false>
分页
Mongoid 在 Criteria
上提供了分页运算符 limit
、skip
和 batch_size
。
limit
limit
设置查询返回的文档总数
Band.limit(5) # => # #<Mongoid::Criteria # selector: {} # options: {:limit=>5} # class: Band # embedded: false>
skip
skip
(别名:offset
)设置在返回文档之前要跳过的查询结果数。如果指定了 limit
值,则将在跳过文档后应用。在进行分页时,建议将 skip
与 排序 结合使用,以确保结果的一致性。
Band.skip(10) # => # #<Mongoid::Criteria # selector: {} # options: {:skip=>10} # class: Band # embedded: false>
batch_size
当执行大型查询或在使用枚举方法(如Criteria#each
)迭代查询结果时,Mongoid会自动使用MongoDB getMore命令以批量加载数据。默认的batch_size
是1000,但您可以明确设置它。
Band.batch_size(500) # => # #<Mongoid::Criteria # selector: {} # options: {:batch_size=>500} # class: Band # embedded: false>
通过_id
查找
Mongoid在Criteria
对象上提供了find
方法,用于根据_id
值查找文档。
Band.find('5f0e41d92c97a64a26aabd10') # => #<Band _id: 5f0e41d92c97a64a26aabd10, name: "Juno Reactor">
find
方法在必要时会对参数进行类型转换,使其与模型中查询的_id
字段的声明类型相匹配。默认情况下,_id
类型是BSON::ObjectId
,因此上述查询相当于
Band.find(BSON::ObjectId.from_string('5f0e41d92c97a64a26aabd10')) # => #<Band _id: 5f0e41d92c97a64a26aabd10, name: "Juno Reactor">
注意
当使用驱动程序直接查询集合时,不会自动执行类型转换
Band.collection.find(_id: BSON::ObjectId.from_string('5f0e41d92c97a64a26aabd10')).first # => {"_id"=>BSON::ObjectId('5f0e41d92c97a64a26aabd10'), "name"=>"Juno Reactor"} Band.collection.find(_id: '5f0e41d92c97a64a26aabd10').first # => nil
find
方法可以接受多个参数或参数数组。在任一情况下,每个参数或数组元素都被视为一个_id
值,并返回具有所有指定_id
值的文档数组
Band.find('5f0e41d92c97a64a26aabd10', '5f0e41b02c97a64a26aabd0e') # => [#<Band _id: 5f0e41b02c97a64a26aabd0e, name: "SUN Project", description: nil, likes: nil>, #<Band _id: 5f0e41d92c97a64a26aabd10, name: "Juno Reactor", description: nil, likes: nil>] Band.find(['5f0e41d92c97a64a26aabd10', '5f0e41b02c97a64a26aabd0e']) # => [#<Band _id: 5f0e41b02c97a64a26aabd0e, name: "SUN Project", description: nil, likes: nil>, #<Band _id: 5f0e41d92c97a64a26aabd10, name: "Juno Reactor", description: nil, likes: nil>]
如果相同的_id
值出现多次,则只返回相应的文档一次
Band.find('5f0e41b02c97a64a26aabd0e', '5f0e41b02c97a64a26aabd0e') # => [#<Band _id: 5f0e41b02c97a64a26aabd0e, name: "SUN Project", description: nil, likes: nil>]
返回的文档没有顺序,可能以与提供的_id
值不同的顺序返回,如上述示例所示。
如果数据库中找不到任何_id
值,则find
的行为取决于raise_not_found_error
配置选项的值。如果该选项设置为true
,当任何_id
未找到时,find
将引发Mongoid::Errors::DocumentNotFound
异常。如果该选项设置为false
并且find
提供了一个要查找的单个_id
,并且没有匹配的文档,则find
返回nil
。如果该选项设置为false
并且find
提供了一个要查找的ID数组,其中一些未找到,则返回值是找到的文档数组(如果完全没有找到文档,则可能是空的)。
附加查询方法
Mongoid还有一些有用的方法用于条件。
操作 | 示例 | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
获取匹配过滤器或集合中所有文档的总数。请注意,这将始终对数据库进行计数。 从Mongoid 7.2版本开始, |
| ||||||||||||||||
使用集合元数据获取集合中文档的大致数量。 |
| ||||||||||||||||
获取单个字段的唯一值列表。请注意,这将始终对数据库进行唯一值的查询。 此方法接受点表示法,因此允许引用内嵌关联中的字段。 此方法尊重:ref:`field aliases <field-aliases>`,包括在嵌套文档中定义的别名。 |
| ||||||||||||||||
遍历条件中所有匹配的文档。 |
| ||||||||||||||||
确定是否存在匹配的文档。如果存在1个或更多,则返回true。
|
| ||||||||||||||||
根据给定的标准获取第五个文档。 此方法在没有给定排序的情况下自动添加_id的排序。 |
| ||||||||||||||||
根据给定的标准获取第五个文档,如果不存在则引发错误。 此方法在没有给定排序的情况下自动添加_id的排序。 |
| ||||||||||||||||
根据提供的属性查找文档。如果未找到,则根据 |
| ||||||||||||||||
根据提供的属性查找文档,如果未找到则创建并返回一个新持久化的实例。注意,此方法参数中提供的属性将覆盖在“create_with”中设置的任何属性。. |
| ||||||||||||||||
根据提供的属性查找文档,如果未找到则返回一个新的实例。 |
| ||||||||||||||||
根据提供的标准查找单个文档。通过传递限制参数获取文档列表。此方法自动添加_id的排序。这可能会导致性能问题,所以如果排序不理想,可以使用Criteria#take代替。 |
| ||||||||||||||||
根据提供的标准查找单个文档,如果未找到则引发错误。如果没有给定排序,此方法会自动添加_id的排序。这可能会导致性能问题,所以如果排序不理想,可以使用Criteria#take!代替。 |
| ||||||||||||||||
根据提供的属性查找第一个文档,如果未找到则创建并返回一个新的持久化实例。 |
| ||||||||||||||||
根据提供的属性查找第一个文档,如果未找到则创建并返回一个新持久化的实例,使用 |
| ||||||||||||||||
根据提供的属性查找第一个文档,如果未找到则返回一个新的实例。 |
| ||||||||||||||||
根据提供的JavaScript表达式查找文档,可选地添加指定的变量到评估范围。范围参数在MongoDB 4.2及以下版本中受支持。 优先使用 $expr 而不是 |
| ||||||||||||||||
根据给定的标准获取第四个文档。 此方法在没有给定排序的情况下自动添加_id的排序。 |
| ||||||||||||||||
根据给定的标准获取第四个文档,如果未找到则引发错误。 此方法在没有给定排序的情况下自动添加_id的排序。 |
| ||||||||||||||||
与count相同,但缓存了对数据库后续调用的调用。 |
| ||||||||||||||||
获取单个文档中提供的字段的值。对于未设置的字段和非存在的字段返回nil。 此方法不对文档应用排序,因此不一定返回来自第一个文档的值。 此方法接受点表示法,因此允许引用内嵌关联中的字段。 此方法尊重:ref:`field aliases <field-aliases>`,包括在嵌套文档中定义的别名。 |
| ||||||||||||||||
获取提供字段的全部值。对于未设置的字段和不存在字段,返回nil。 此方法接受点表示法,因此允许引用内嵌关联中的字段。 此方法尊重:ref:`field aliases <field-aliases>`,包括在嵌套文档中定义的别名。 |
| ||||||||||||||||
设置标准的读取偏好。 |
| ||||||||||||||||
获取给定标准的第二个文档。 此方法在没有给定排序的情况下自动添加_id的排序。 |
| ||||||||||||||||
获取给定标准的第二个文档,如果不存在则抛出错误。 此方法在没有给定排序的情况下自动添加_id的排序。 |
| ||||||||||||||||
获取给定标准的倒数第二个文档。 此方法在没有给定排序的情况下自动添加_id的排序。 |
| ||||||||||||||||
获取给定标准的倒数第二个文档,如果不存在则抛出错误。 此方法在没有给定排序的情况下自动添加_id的排序。 |
| ||||||||||||||||
从数据库获取n个文档的列表,如果没有提供参数则只获取一个。 此方法不对文档进行排序,因此可能返回与#first和#last不同的文档。 |
| ||||||||||||||||
从数据库获取文档,如果不存在则抛出错误。 此方法不对文档进行排序,因此可能返回与#first和#last不同的文档。 |
| ||||||||||||||||
获取提供的字段的值到计数的映射。 此方法接受点表示法,因此允许引用内嵌关联中的字段。 此方法尊重:ref:`field aliases <field-aliases>`,包括在嵌套文档中定义的别名。 |
| ||||||||||||||||
获取给定标准的第三个文档。 此方法在没有给定排序的情况下自动添加_id的排序。 |
| ||||||||||||||||
获取给定标准的第三个文档,如果不存在则抛出错误。 此方法在没有给定排序的情况下自动添加_id的排序。 |
| ||||||||||||||||
获取给定标准的倒数第三个文档。 此方法在没有给定排序的情况下自动添加_id的排序。 |
| ||||||||||||||||
获取给定标准的倒数第三个文档,如果不存在则抛出错误。 此方法在没有给定排序的情况下自动添加_id的排序。 |
|
预加载
Mongoid提供了一种从关联中预加载文档的功能,以防止在迭代具有关联访问的文档时出现n+1问题。预加载支持所有关联,除了多态belongs_to
关联。
class Band include Mongoid::Document has_many :albums end class Album include Mongoid::Document belongs_to :band end Band.includes(:albums).each do |band| p band.albums.first.name # Does not hit the database again. end
正则表达式
MongoDB和Mongoid允许通过正则表达式查询文档。
给定以下模型定义
class Band include Mongoid::Document field :name, type: String field :description, type: String end Band.create!(name: 'Sun Project', description: "Sun\nProject")
...我们可以以自然的方式使用简单的Ruby正则表达式进行查询
Band.where(name: /project/i).first # => #<Band _id: 5dc9f7d5ce4ef34893354323, name: "Sun Project", description: "Sun\nProject">
也可以通过显式构造BSON::Regexp::Raw
对象来使用PCRE语法进行查询
Band.where(description: /\AProject/).first # => #<Band _id: 5dc9f7d5ce4ef34893354323, name: "Sun Project", description: "Sun\nProject"> Band.where(description: BSON::Regexp::Raw.new('^Project')).first # => nil Band.where(description: BSON::Regexp::Raw.new('^Project', 'm')).first # => #<Band _id: 5dc9f7d5ce4ef34893354323, name: "Sun Project", description: "Sun\nProject">
字段条件
当条件使用模型中定义的字段时,如果条件中指定的值有规则,则将其转换为该字段的规则。例如,考虑以下包含Time
字段、Date
字段和一个隐式Object
字段的模型定义,并且有意没有定义名为deregistered_at
的字段
class Voter include Mongoid::Document field :born_on, type: Date field :registered_at, type: Time field :voted_at end
使用Date
和Time
值分别对born_on
和registered_at
字段进行查询是直接的
Voter.where(born_on: Date.today).selector # => {"born_on"=>2020-12-18 00:00:00 UTC} Voter.where(registered_at: Time.now).selector # => {"registered_at"=>2020-12-19 04:33:36.939788067 UTC}
但是,注意在所有可能的情况下提供Date
实例时的行为差异
Voter.where(born_on: Date.today).selector # => {"born_on"=>2020-12-18 00:00:00 UTC} Voter.where(registered_at: Date.today).selector # => {"registered_at"=>2020-12-18 00:00:00 -0500} Voter.where(voted_at: Date.today).selector # => {"voted_at"=>Fri, 18 Dec 2020} Voter.where(deregistered_at: Date.today).selector # => {"deregistered_at"=>2020-12-18 00:00:00 UTC}
当使用类型为Time
的registered_at
字段时,日期被解释为本地时间(根据配置的时间区域)。当使用类型为Date
的born_on
字段时,日期被解释为UTC。当使用没有定义类型的voted_at
字段(因此隐式地作为Object
)时,日期在构建的查询中未修改地使用。当使用不存在的字段deregistered_at
时,日期被解释为UTC并转换为时间,这与查询Date
字段的行为相匹配。
范围
范围提供了一种方便的方式来以更业务域风格的语法重用常见的条件。
命名范围
命名范围是在类加载时定义的简单标准,通过提供名称来引用。就像正常标准一样,它们是延迟加载的,并且可以链式调用。
class Band include Mongoid::Document field :country, type: String field :genres, type: Array scope :english, ->{ where(country: "England") } scope :rock, ->{ where(:genres.in => [ "rock" ]) } end Band.english.rock # Get the English rock bands.
命名范围可以接受进程和块来接受参数或扩展功能。
class Band include Mongoid::Document field :name, type: String field :country, type: String field :active, type: Boolean, default: true scope :named, ->(name){ where(name: name) } scope :active, ->{ where(active: true) do def deutsch tap do |scope| scope.selector.store("origin" => "Deutschland") end end end } end Band.named("Depeche Mode") # Find Depeche Mode. Band.active.deutsch # Find active German bands.
默认情况下,Mongoid 允许定义一个会覆盖现有类方法的范围,如下例所示
class Product include Mongoid::Document def self.fresh true end scope :fresh, ->{ where(fresh: true) } end
当命名范围会覆盖现有类方法时,Mongoid 会抛出错误,可以将 scope_overwrite_exception
配置选项 设置为 true
。
默认范围
当您发现自己经常在查询中应用相同的标准时,默认范围非常有用,并且希望将这些标准指定为默认值。默认范围是返回标准对象的进程。
class Band include Mongoid::Document field :name, type: String field :active, type: Boolean default_scope ->{ where(active: true) } end Band.each do |band| # All bands here are active. end
指定默认范围还可以将新模型的字段初始化为默认范围中给出的值,如果这些值是简单文本。
class Band include Mongoid::Document field :name, type: String field :active, type: Boolean field :num_tours, type: Integer default_scope ->{ where(active: true, num_tours: {'$gt' => 1}) } end # active is set, num_tours is not set Band.new # => #<Band _id: 5c3f7452ce4ef378295ca5f5, name: nil, active: true, num_tours: nil>
注意,如果在字段定义和默认范围中都提供了默认值,则默认范围中的值优先。
class Band include Mongoid::Document field :name, type: String field :active, type: Boolean, default: true default_scope ->{ where(active: false) } end Band.new # => #<Band _id: 5c3f74ddce4ef3791abbb088, name: nil, active: false>
由于默认范围如上所述初始化新模型的字段,因此建议不要使用点键和简单文本值定义默认范围。
class Band include Mongoid::Document field :name, type: String field :tags, type: Hash default_scope ->{ where('tags.foo' => 'bar') } end Band.create! # => Created document: {"_id"=>BSON::ObjectId('632de48f3282a404bee1877b'), "tags.foo"=>"bar"} Band.create!(tags: { 'foo' => 'bar' }) # => Created document: {"_id"=>BSON::ObjectId('632de4ad3282a404bee1877c'), "tags.foo"=>"bar", "tags"=>{"foo"=>"bar"}} Band.all.to_a # => [ #<Band _id: 632de4ad3282a404bee1877c, tags: {"foo"=>"bar"}> ]
Mongoid 8 允许在 Mongoid 中使用点键,当创建文档时,范围作为点键添加到属性中。
Band.new.attribute # => {"_id"=>BSON::ObjectId('632de97d3282a404bee1877d'), "tags.foo"=>"bar"}
而查询时,Mongoid 会寻找嵌入文档。
Band.create! # => Created document: {"_id"=>BSON::ObjectId('632de48f3282a404bee1877b'), "tags.foo"=>"bar"} Band.where # => #<Mongoid::Criteria selector: {"tags.foo"=>"bar"} options: {} class: Band embedded: false> # This looks for something like: { tags: { "foo" => "bar" } } Band.count # => 0
一种解决方案是将默认范围定义为复杂查询。
class Band include Mongoid::Document field :name, type: String field :tags, type: Hash default_scope ->{ where('tags.foo' => {'$eq' => 'bar'}) } end Band.create!(tags: { hello: 'world' }) Band.create!(tags: { foo: 'bar' }) # does not add a "tags.foo" dotted attribute Band.count # => 1
您可以使用 unscoped
告诉 Mongoid 不应用默认范围,这可以是内联的或带块的。
Band.unscoped.where(name: "Depeche Mode") Band.unscoped do Band.where(name: "Depeche Mode") end
您还可以告诉 Mongoid 在以后显式应用默认范围,以确保始终存在。
Band.unscoped.where(name: "Depeche Mode").scoped
如果您在一个关联模型上使用了默认范围,那么您必须重新加载该关联,以便应用范围。如果更改关联中会影响其在范围关联内可见性的文档的值,请注意这一点。
class Label include Mongoid::Document embeds_many :bands end class Band include Mongoid::Document field :active, default: true embedded_in :label default_scope ->{ where(active: true) } end label.bands.push(band) label.bands # [ band ] band.update_attribute(:active, false) label.bands # [ band ] Must reload. label.reload.bands # []
注意
默认范围应用后,它不再与其他查询条件区分开来。在特定情况下,使用 or
和 nor
运算符可能会导致意外行为
class Band include Mongoid::Document field :name field :active field :touring default_scope ->{ where(active: true) } end Band.where(name: 'Infected Mushroom') # => # #<Mongoid::Criteria # selector: {"active"=>true, "name"=>"Infected Mushroom"} # options: {} # class: Band # embedded: false> Band.where(name: 'Infected Mushroom').or(touring: true) # => # #<Mongoid::Criteria # selector: {"$or"=>[{"active"=>true, "name"=>"Infected Mushroom"}, {"touring"=>true}]} # options: {} # class: Band # embedded: false> Band.or(touring: true) # => # #<Mongoid::Criteria # selector: {"$or"=>[{"active"=>true}, {"touring"=>true}]} # options: {} # class: Band # embedded: false>
在最后一个例子中,您可能期望这两个条件(active: true
和 touring: true
)将与一个 $and
结合,但由于 Band
类已经应用了范围,它成为了 or
中的一个析取分支。
运行时默认范围覆盖
您可以使用 with_scope
方法在运行时更改块中的默认范围
class Band include Mongoid::Document field :country, type: String field :genres, type: Array scope :english, ->{ where(country: "England") } end criteria = Band.with_scope(Band.english) do Band.all end criteria # => # #<Mongoid::Criteria # selector: {"country"=>"England"} # options: {} # class: Band # embedded: false>
注意
如果嵌套了 with_scope 调用,当嵌套的 with_scope 块完成时,Mongoid 7 将当前范围设置为 nil,而不是父范围。Mongoid 8 将将当前范围设置为正确的父范围。要获取 Mongoid 8 的行为,在 Mongoid 7.4 和更高版本中,将全局选项 Mongoid.broken_scoping
设置为 false。
类方法
返回条件对象的模型上的类方法也被视为范围,并且也可以进行链式调用。
class Band include Mongoid::Document field :name, type: String field :active, type: Boolean, default: true def self.active where(active: true) end end Band.active
查询 + 持久化
Mongoid 支持在轻量级上对基于条件的持久化操作,当您想要表达性地执行多文档插入、更新和删除时。
警告
以下操作将忽略条件排序和分页条件,包括 order
、limit
、offset
和 batch_size
。
操作 | 示例 | ||
---|---|---|---|
创建一个新的持久化文档。 |
| ||
创建一个新的持久化文档,并在验证失败时抛出异常。 |
| ||
创建一个新的(未保存的)文档。 |
| ||
更新第一条匹配文档的属性。 |
| ||
更新所有匹配文档的属性。 |
| ||
对所有匹配文档执行 $addToSet 操作。 |
| ||
对所有匹配文档执行 $bit 操作。 |
| ||
对所有匹配文档执行 $inc 操作。 |
| ||
对所有匹配文档执行 $pop 操作。 |
| ||
对所有匹配文档执行 $pull 操作。 |
| ||
对所有匹配文档执行 $pullAll 操作。 |
| ||
对所有匹配文档执行 $push 操作。 |
| ||
对所有匹配文档执行带有 $each 的 $push 操作。 |
| ||
对所有匹配文档执行 $rename 操作。 |
| ||
对所有匹配文档执行 $set 操作。 |
| ||
对所有匹配文档执行 $unset 操作。 |
| ||
删除数据库中所有匹配的文档。 |
| ||
删除数据库中所有匹配的文档,同时运行所有回调。这会将所有文档加载到内存中,可能是一个昂贵的操作。 |
|
查询缓存
Ruby MongoDB 驱动程序版本 2.14 及以上提供查询缓存功能。启用后,查询缓存将保存先前执行的 find 和聚合查询的结果,并在将来重用这些结果,而不是再次执行查询,从而提高应用程序性能并减少数据库负载。
请查阅驱动查询缓存文档,以了解关于驱动查询缓存行为的详细信息。
本节的其余部分假设正在使用2.14.0或更高版本的驱动程序。
启用查询缓存
可以使用驱动程序的命名空间或Mongoid的命名空间来启用查询缓存。
自动启用查询缓存
MongoDB Ruby 驱动程序提供了中间件,可以自动为Rack网络请求和ActiveJob作业运行启用查询缓存。请参阅配置页面上的查询缓存Rack中间件部分以获取说明。
请注意,查询缓存中间件不适用于Web请求和/或作业之外执行的代码。
手动启用查询缓存
要手动为代码段启用查询缓存,请使用
Mongo::QueryCache.cache do # ... end
虽然我们建议使用上面描述的块形式,但查询缓存也可以显式地启用和禁用。
begin Mongo::QueryCache.enabled = true # ... ensure Mongo::QueryCache.enabled = false end
缓存 #first
的结果
在模型类上调用 first
方法会对底层查询按照 _id
字段进行升序排序。这可能会导致查询缓存产生意外行为。
例如,当在一个模型类上先调用 all
,然后调用 first
时,人们会期望第二个查询使用第一个查询的缓存结果。然而,由于对第二个查询的排序,这两个方法都将查询数据库并分别缓存它们的结果。
Band.all.to_a #=> Queries the database and caches the results Band.first #=> Queries the database again because of the sort
要使用缓存结果,请对模型类调用 all.to_a.first
。
异步查询
Mongoid 允许在后台异步运行数据库查询。当需要从不同的集合获取文档时,这可能是很有益的。
为了调度异步查询,请在 Criteria
上调用 load_async
方法。
class PagesController < ApplicationController def index @active_bands = Band.where(active: true).load_async @best_events = Event.best.load_async @public_articles = Article.where(public: true).load_async end end
在上面的示例中,将调度三个查询以异步执行。稍后可以像往常一样访问查询结果。
<ul> <%- @active_bands.each do -%> <li><%= band.name %></li> <%- end -%> </ul>
即使查询已调度为异步执行,它也可能在调用者的线程上同步执行。具体取决于何时访问查询结果,有三种可能的场景:
如果已调度异步任务已执行,则返回结果。
如果任务已启动但尚未完成,则调用者的线程将阻塞,直到任务完成。
如果任务尚未启动,则将其从执行队列中删除,并在调用者的线程上同步执行查询。
注意
即使 load_async
方法返回一个 Criteria
对象,您也不应该对这个对象进行任何操作,除了访问查询结果。查询将在调用 load_async
后立即执行,因此后续对 Criteria
对象的更改可能不会生效。
配置异步查询执行
默认情况下禁用异步查询。当禁用异步查询时,load_async
将在当前线程上立即执行查询,根据需要阻塞。因此,在这种情况下对符合标准的 load_async
调用大致等同于调用 to_a
以强制执行查询。
要启用异步查询执行,必须设置以下配置选项
development: ... options: # Execute asynchronous queries using a global thread pool. async_query_executor: :global_thread_pool # Number of threads in the pool. The default is 4. # global_executor_concurrency: 4