查询
在此页面上
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>
操作符组合
从 Mongoid7.1 开始,逻辑操作符(and
、or
、nor
和not
)已更改为具有与 ActiveRecord 相同的语义 。获取or
在 Mongoid 7中的语义。 0及更早版本,请使用如下所述的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
形式。
or
/nor
Behavior _----------------------
or
和 nor
分别生成 $or
和 $nor
MongoDB 操作符,使用接收器和所有参数作为操作数。例如:
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"}
如果接收器上的唯一条件是另一个 or
/nor
,则新条件将添加到现有列表中:
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
会将基于相应参数构建的或关系添加到条件中的现有条件。例如:
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
会将基于相应参数构建的否定或关系 ("nor") 添加到筛选条件中的现有条件。例如:
Band.where(label: /Trust/).none_of({name: 'Astral Projection'}, {name: /Best/}) # => {"label"=>/Trust/, "$nor"=>[{"name"=>"Astral Projection"}, {"name"=>/Best/}]}
not
行为
not
可以在没有参数的情况下调用方法,在这种情况下,该方法将对下一个指定的条件取反。还可以使用一个或多个哈希条件或Criteria
对象来调用not
,这些对象都会取反并添加到条件中。
# 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"}}
注意
$not
在 MongoDB 服务器中不能与字符串参数一起使用。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
与数组或正则表达式一起使用,请注意在 MongoDB Server 文档中所述的 $not
的注意事项/限制条件。
增量查询构建
默认情况下,当条件添加到查询中时,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 提供了三种合并策略:
覆盖:新的操作符实例使用相同的操作符替换同一字段上的任何现有条件。
相交:如果已经有一个条件在同一字段上使用了相同的操作符,则现有条件的值将与新条件的值相交,并将结果作为操作符值存储。
Union:如果同一字段上已存在使用相同运算符的条件,则新条件的值将添加到现有条件的值中,并将结果存储为运算符值。
以下代码片段使用 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
作为其值类型。当与 Array
兼容的类型(如 Range
)与这些操作符方法一起使用时,Mongoid会扩展这些类型的值:
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
字段,所以不能通过 without
省略该字段(以及其 id
别名):
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_by
上提供 order
方法,用于指定文档的顺序。这些方法采用哈希值,表示根据哪些字段对文档进行排序,以及对每个字段使用升序还是降序。
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
的范围(包括默认范围),此操作有时可能会产生令人惊异的结果。例如,在下面的代码片段中,乐队首先按名称排序,因为默认范围中的顺序优先于查询中给出的顺序,其原因是首先会评估默认范围:
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>”,包括在嵌入式文档中定义的字段别名。 |
| ||||||||||||||||
循环访问条件中所有匹配的文档。 |
| ||||||||||||||||
确定是否存在匹配文档。如果有 1 个或多个,则返回 true。
|
| ||||||||||||||||
获取符合给定条件的第五个文档。 如果未给出排序,此方法自动对 _id 添加排序。 |
| ||||||||||||||||
获取给定条件的第五个文档,如果不存在,则引发错误。 如果未给出排序,此方法自动对 _id 添加排序。 |
| ||||||||||||||||
根据提供的属性查找文档。如果未找到,则引发错误或返回 nil,具体取决于以下的值:
|
| ||||||||||||||||
通过提供的属性查找文档,如果找不到,则创建并返回一个新的持久性文档。请注意,参数中针对此方法提供的属性将覆盖“create_with”中的任何设置。 |
| ||||||||||||||||
通过提供的属性查找文档,如果找不到,则返回一个新文档。 |
| ||||||||||||||||
根据所提供的条件查找单个文档。通过传入限制参数来获取文档列表。此方法会自动添加对 _id 的排序。这可能会导致性能问题,因此如果不需要排序,可以改用 Criteria#take。 |
| ||||||||||||||||
根据所提供的条件查找单个文档,如果未找到任何文档,则引发错误。如果未给出排序,此方法自动对 _id 添加排序。这可能会导致性能问题,因此如果不需要排序,可以改用 Criteria#take!。 |
| ||||||||||||||||
通过提供的属性查找第一个文档,如果找不到,则创建并返回一个新的持久化文档。 |
| ||||||||||||||||
通过提供的属性查找第一个文档,如果找不到,则使用以下命令创建并返回一个新的持久化文档 |
| ||||||||||||||||
通过提供的属性查找第一个文档,如果找不到,则返回一个新文档。 |
| ||||||||||||||||
查找文档,搜索提供的 JavaScript 表达式,可以选择将指定的变量添加到评估作用域。MongoDB 4.2 及更低版本支持作用域参数。
首选 $expr 通过 |
| ||||||||||||||||
获取给定条件的第四个文档。 如果未给出排序,此方法自动对 _id 添加排序。 |
| ||||||||||||||||
获取给定条件下的第四个文档,若文档不存在,则引发错误。 如果未给出排序,此方法自动对 _id 添加排序。 |
| ||||||||||||||||
与计数相同,但会缓存对数据库的后续调用 |
| ||||||||||||||||
从一个文档中获取所提供字段的值。对于未设置的字段和不存在的字段,返回 nil。 该方法不对文档进行排序,因此不一定会返回第一个文档的值。 此方法接受点符号,从而允许在嵌入关联中引用字段。 该方法遵循 :ref:“字段别名 <field-aliases>”,包括在嵌入式文档中定义的字段别名。 |
| ||||||||||||||||
获取所提供字段的所有值。对于未设置的字段和不存在的字段,返回 nil。 此方法接受点符号,从而允许在嵌入关联中引用字段。 该方法遵循 :ref:“字段别名 <field-aliases>”,包括在嵌入式文档中定义的字段别名。 |
| ||||||||||||||||
设置条件的读取偏好。 |
| ||||||||||||||||
获取给定条件的第二个文档。 如果未给出排序,此方法自动对 _id 添加排序。 |
| ||||||||||||||||
获取给定条件的第二个文档,若文档不存在,则引发错误。 如果未给出排序,此方法自动对 _id 添加排序。 |
| ||||||||||||||||
获取给定条件的倒数第二个文档。 如果未给出排序,此方法自动对 _id 添加排序。 |
| ||||||||||||||||
根据给定条件获取倒数第二个文档,若文档不存在,则引发错误。 如果未给出排序,此方法自动对 _id 添加排序。 |
| ||||||||||||||||
从数据库中获取包含 n 个文档的列表,如果未提供参数,则仅获取一个文档的列表。 该方法不对文档应用排序,因此可能返回与 #first 和 #last 不同的文档。 |
| ||||||||||||||||
从数据库获取文档,如果不存在文档,则引发错误。 该方法不对文档应用排序,因此可能返回与 #first 和 #last 不同的文档。 |
| ||||||||||||||||
获取所提供字段的值与计数的映射关系。 此方法接受点符号,从而允许在嵌入关联中引用字段。 该方法遵循 :ref:“字段别名 <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 时间。使用未定义类型(因此隐式为 Object
)的 voted_at
字段时,日期在构建的查询中未经修改地使用。使用不存在的字段 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.
命名作用域可以采用 proc 和区块来接受参数或扩展功能。
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 7.4 及更高版本中获得 Mongoid 8 行为,请将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 及更高版本提供查询缓存功能。启用后,查询缓存会保存先前执行的查找和聚合查询的结果,并在将来进行重用,而不是再次执行查询,从而提高应用程序性能并减少数据库负载。
请查看驱动程序查询缓存文档,了解有关驱动程序查询缓存行为的详细信息。
本节的其余部分假设使用的是驱动程序 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