复杂的MongoDB查询聚合

时间:2013-10-11 13:11:19

标签: mongodb mongoid

我有一份文件,代表一个嵌入许多房间的房子。结构就是这样。

{
   description: 'House'
   total_price: 1000,
   rooms: [{
     description: 'Small Single Room',
     type: single,
     available: true,
     price: 300
   }, {
     description: 'Big Single Room',
     type: single,
     price: 400,
     available: true
   }, {
     description: 'Another Room',
     type: single,
     price: 300,
     available: true
   }]
}

我很难解决这个问题:如何让所有房屋都有单人房,总价700.

要做到这一点,我需要以下内容。

  • 在某些嵌入式文档属性上设置条件
  • 设置可用嵌入式房间总和的条件
  • 在总和上设置条件

目前我正在努力了解如何做到这一点。我一直在寻找aggregate和其他方法,但我找不到可行的解决方案。

非常感谢。

2 个答案:

答案 0 :(得分:0)

这是您可以使用的一种方法。将剩余房间和剩余价格保存在根级别(初始化为总价格和总房间数)。每当您将房间的可用设置为false时,请从remaining_price和remaining_rooms中减去价格和计数。

{
   description: 'House'
   total_price: 1000,
   remaining_price: 700,
   remaining_rooms: 2,
   rooms: [{
     description: 'Small Single Room',
     type: single,
     available: false,
     price: 300
   }, {
     description: 'Big Single Room',
     type: single,
     price: 400,
     available: true
   }, {
     description: 'Another Room',
     type: single,
     price: 300,
     available: true
   }]
}

现在您可以简单地查询您想要使用的房间(我正在使用mongoid编写查询,您可以将其转换为您正在使用的任何内容):

House.where(:remaining_price.lte => 700, :remaining_rooms.gte => 2).to_a

答案 1 :(得分:0)

这是一个聚合框架答案。 请注意,第一个$ match会在集合级别过滤文档 并使用$ elemMatch匹配数组元素的类型和可用性。 为了得到房屋可用房间的价格总和, 不可用的房间必然被过滤掉并丢失。 $ group阶段中的添加字段保留数据以供查看, 但是,您的应用可能更适合通过_id获取完整的文档。

希望这能以原始意图回答您的问题 并展示了聚合框架的强大功能。

测试/单元/ house_test.rb

require 'test_helper'
require 'pp'

class HouseTest < ActiveSupport::TestCase
  def setup
    House.delete_all
  end

  test "complex aggregate query" do
    puts "\nMongoid::VERSION:#{Mongoid::VERSION}\nMoped::VERSION:#{Moped::VERSION}"
    total_price_limit = 700
    pipeline = [
        {'$match' => {'rooms' => {'$elemMatch' => {'type' => 'single', 'available' => true}}}},
        {'$unwind' => "$rooms"},
        {'$match' => {'rooms.available' => true}},
        {'$group' => {
            '_id' => '$_id',
            'description' => {'$last' => '$description'},
            'total_price_available' => {'$sum' => '$rooms.price'},
            'rooms' => {'$push' => '$rooms'},
        }
        },
        {'$match' => {'total_price_available' => {'$lte' => total_price_limit}}},
        {'$sort' => {'total_price_available' => 1}},
    ]
    docs = [
        {
            description: 'House 1',
            total_price: 1000,
            rooms: [{
                        description: 'Small Single Room',
                        type: 'single',
                        available: true,
                        price: 300
                    }, {
                        description: 'Big Single Room',
                        type: 'single',
                        price: 400,
                        available: true
                    }, {
                        description: 'Another Room',
                        type: 'single',
                        price: 300,
                        available: true
                    }]
        },
        {
            description: 'House 2',
            total_price: 600,
            rooms: [{
                        description: 'Small Single Room',
                        type: 'single',
                        available: true,
                        price: 300
                    }, {
                        description: 'Big Single Room',
                        type: 'single',
                        price: 400,
                        available: false
                    }, {
                        description: 'Another Room',
                        type: 'single',
                        price: 300,
                        available: true
                    }]
        }
    ]
    docs.each { |house| House.create(house) }
    pp House.collection.aggregate(pipeline)
  end
end

$ rake test

Run options:

# Running tests:

[1/1] HouseTest#test_complex_aggregate_query
Mongoid::VERSION:3.1.5
Moped::VERSION:1.5.1
[{"_id"=>"5272dbe2e4d30b7e0a000002",
  "description"=>"House 2",
  "total_price_available"=>600,
  "rooms"=>
   [{"description"=>"Small Single Room",
     "type"=>"single",
     "available"=>true,
     "price"=>300},
    {"description"=>"Another Room",
     "type"=>"single",
     "price"=>300,
     "available"=>true}]}]
Finished tests in 0.046359s, 21.5708 tests/s, 0.0000 assertions/s.
1 tests, 0 assertions, 0 failures, 0 errors, 0 skips