mongodb中的多重限制条件

时间:2014-07-04 05:36:24

标签: ruby mongodb activerecord aggregation-framework rails-activerecord

我有一个集合,其中一个字段是" type"。我希望得到每种类型的一些值,具体取决于所有类型相同的条件。就像我想要A型的2个文件,类型B的2个。 如何在单个查询中执行此操作?我正在使用Ruby Active Record。

2 个答案:

答案 0 :(得分:1)

您将无法仅使用类型列和必须是一个查询的约束直接执行此操作。然而,(一如既往)有一种方法可以实现这一目标。

要查找不同类型的文档,您需要具有某种类型的附加值,平均根据您希望数据的方式分发类型。

db.users.insert({type: 'A', index: 1})
db.users.insert({type: 'B', index: 2})
db.users.insert({type: 'A', index: 3})
db.users.insert({type: 'B', index: 4})
db.users.insert({type: 'A', index: 5})
db.users.insert({type: 'B', index: 6})

然后在查询db.users.find(index: {$gt: 2, $lt: 7})的项目时,您将获得正确的项目分配。

虽然我不确定这是你在找什么

答案 1 :(得分:1)

一般来说,您所描述的是围绕MongoDB社区的一个相对常见的问题,我们可以将其描述为“最高n结果问题”。这是给定一些可能以某种方式排序的输入,如何在不依赖数据中的任意索引值的情况下获得最高n结果。

MongoDB有$first运算符,aggregation framework可用于处理问题的“前1”部分,因为这实际上是在分组边界上找到的“第一个”项目,比如你的“类型”。但是,获得超过“一个”的结果当然会更多地涉及到。关于修改其他运算符以处理n结果或“限制”或“切片”,有一些JIRA问题。值得注意的是SERVER-6074。但问题可以通过几种方式解决。

用于MongoDB存储的rails Active Record模式的常用实现是MongoidMongo Mapper,两者都允许通过.collection访问器访问“本机”mongodb集合函数。这是您基本上需要能够使用本地方法,例如.aggregate(),它支持比一般Active Record聚合更多的功能。

这是一个使用mongoid的聚合方法,但是一旦您有权访问本机集合对象,一般代码就不会改变:

require "mongoid"
require "pp";

Mongoid.configure.connect_to("test");

class Item
  include Mongoid::Document
  store_in collection: "item"

  field :type, type: String
  field :pos, type: String
end

Item.collection.drop

Item.collection.insert( :type => "A", :pos => "First" )
Item.collection.insert( :type => "A", :pos => "Second"  )
Item.collection.insert( :type => "A", :pos => "Third" )
Item.collection.insert( :type => "A", :pos => "Forth" )
Item.collection.insert( :type => "B", :pos => "First" )
Item.collection.insert( :type => "B", :pos => "Second" )
Item.collection.insert( :type => "B", :pos => "Third" )
Item.collection.insert( :type => "B", :pos => "Forth" )

res = Item.collection.aggregate([
  { "$group" => {
      "_id" => "$type",
      "docs" => {
        "$push" => {
          "pos" => "$pos", "type" => "$type"
        }
      },
      "one" => {
        "$first" => {
          "pos" => "$pos", "type" => "$type"
        }
      }
  }},
  { "$unwind" =>  "$docs" },
  { "$project" => {
    "docs" => {
      "pos" => "$docs.pos",
      "type" => "$docs.type",
      "seen" => {
        "$eq" => [ "$one", "$docs" ]
      },
    },
    "one" => 1
  }},
  { "$match" => {
    "docs.seen" => false
  }},
  { "$group" => {
    "_id" => "$_id",
    "one" => { "$first" => "$one" },
    "two" => {
      "$first" => {
        "pos" => "$docs.pos",
        "type" => "$docs.type"
      }
    },
    "splitter" => {
      "$first" => {
        "$literal" => ["one","two"]
      }
    }
  }},
  { "$unwind" => "$splitter" },
  { "$project" => {
    "_id" => 0,
    "type" => {
      "$cond" => [
        { "$eq" => [ "$splitter", "one" ] },
        "$one.type",
        "$two.type"
      ]
    },
    "pos" => {
      "$cond" => [
        { "$eq" => [ "$splitter", "one" ] },
        "$one.pos",
        "$two.pos"
      ]
    }
  }}
])

pp res

代码中实际上没有使用文档中的命名,并且“First”,“Second”等所示数据中的标题实际上只是用于说明您确实获得了“前2”文档列出的结果。

因此,这里的方法主要是创建一个由您的密钥“分组”的文档的“堆栈”,例如“type”。这里的第一件事是使用$first运算符从该堆栈中获取“第一个”文档。

后续步骤匹配堆栈中的“see”元素并对其进行过滤,然后使用$first运算符再次从堆栈中取出“下一个”文档。最后的步骤实际上是将文档返回到输入中找到的原始表单,这通常是从这样的查询中得到的结果。

所以结果当然只是每种类型的前2个文档:

{ "type"=>"A", "pos"=>"First" }
{ "type"=>"A", "pos"=>"Second" }
{ "type"=>"B", "pos"=>"First" }
{ "type"=>"B", "pos"=>"Second" }

在最近的答案中,有更长时间的讨论和版本以及其他解决方案:

Mongodb aggregation $group, restrict length of array

基本上同样的事情,尽管标题和案件的目的是匹配多达10个顶级条目或更高。那里还有一些管道生成代码用于处理更大的匹配以及根据您的数据可以考虑的一些替代方法。