Complex MongoDB query aggregate - mongodb

I've a document representing a house embedding many rooms. The structure is this.
{
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
}]
}
I'm having hard time to solve this question: how can I get all houses having available single rooms for a total price of 700.
To do this I need what follows.
set a condition on some embedded document properties
set a condition on the sum of the available embedded rooms
set a condition on the sum
At the moment I'm trying to understand how this can be done. I've been looking to aggregate and other methods, but I couldn't find a working solution.
Thanks a lot.

Here is one approach that you could use. Save remaining rooms and remaining price at root level (initialize with total price and total rooms). Whenever you set available as false for a room, subtract the price and count from remaining_price and 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
}]
}
Now you can simply query the rooms you want using (I am writing the query using mongoid, which you can convert to whatever you are using):
House.where(:remaining_price.lte => 700, :remaining_rooms.gte => 2).to_a

Here's an aggregation framework answer.
Note that the first $match filters documents at the collection level
and uses $elemMatch for matching both type and availability on an array element.
In order to do the sum of the prices of the available rooms for a house,
the unavailable rooms are necessarily filtered out and lost.
Addition fields in the $group stage preserve data for viewing,
but it may be more appropriate for your app to fetch full docs via the _id's.
Hope that this answers your question in its original intent
and demonstrates the power of the aggregation framework.
test/unit/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

Related

How to get a field of an object in document?

I am not sure how to phrase this question properly but basically I have an "Order" Schema and each order model contains an array of product objects created using "Product" Schema
When I create an order, my req.body is this
body: {
orderItems: [ [Object] ],
shippingAddress: {
fullName: '123',
address: '1231',
city: '123',
postalCode: '123',
country: '123'
},
shippingPrice: 0,
paymentMethod: 'Stripe',
itemsPrice: 338,
totalPrice: 338
},
If I log req.body.orderItems, I can see each product object printed
{
_id: new ObjectId("62d51a3895cad3ca283302f3"),
name: 'Christmas Cake',
slug: 'christmas-cake',
image: '/images/cake.jpg',
shop: 'Custom Cakes',
category: 'food',
description: 'Custom baked christmas cake, pre-order needed.',
price: 70,
stock: 1,
rating: 3,
numReviews: 2,
__v: 2,
createdAt: 2022-07-18T08:30:48.931Z,
updatedAt: 2022-07-20T03:03:00.592Z,
}
{
_id: new ObjectId("62d7a8c126dcacfc13055e3d"),
name: 'cake',
slug: 'cake',
image: '/images/cake.jpg',
shop: 'Custom Cakes',
category: 'food',
description: 'cake',
price: 15,
stock: 2,
rating: 0,
numReviews: 0,
user: { _id: new ObjectId("62d51d57c08cd7e6675e8d45")},
reviews: [],
createdAt: 2022-07-20T07:03:29.372Z,
updatedAt: 2022-07-20T07:03:59.315Z,
__v: 0
}
But I am unable to obtain the 'shop' field. req.body.orderItems.shop returns undefined

Actions on google reservations for transaction API

We are building boot using action on builder SDK. In bot we have transaction related feature so we want to implement reservation feature to make appointment reservation. In our Bot we just need reservation. user can't cancel/delete reservation (As our member can contact the user for next 3 4 days, we are just asking user free time so we can contact user). (We did't have reservation feature yet but google team force us to do so). My Question is while implementing reservation we have https://developers.google.com/assistant/transactions/physical/dev-guide-physical-reservations#validate_transaction_requirements_optional this feature optional we are not implementing this, for second step we have to build Order like this https://developers.google.com/assistant/transactions/physical/dev-guide-physical-reservations#build_the_order
const order = {
createTime: '2019-09-24T18:00:00.877Z',
lastUpdateTime: '2019-09-24T18:00:00.877Z',
merchantOrderId: orderId, // A unique ID String for the order
userVisibleOrderId: orderId,
transactionMerchant: {
id: 'http://www.example.com',
name: 'Example Merchant',
},
contents: {
lineItems: [
{
id: 'LINE_ITEM_ID',
name: 'Dinner reservation',
description: 'A world of flavors all in one destination.',
reservation: {
status: 'PENDING',
userVisibleStatusLabel: 'Reservation is pending.',
type: 'RESTAURANT',
reservationTime: {
timeIso8601: '2020-01-16T01:30:15.01Z',
},
userAcceptableTimeRange: {
timeIso8601: '2020-01-15/2020-01-17',
},
partySize: 6,
staffFacilitators: [
{
name: 'John Smith',
},
],
location: {
zipCode: '94086',
city: 'Sunnyvale',
postalAddress: {
regionCode: 'US',
postalCode: '94086',
administrativeArea: 'CA',
locality: 'Sunnyvale',
addressLines: [
'222, Some other Street',
],
},
},
},
},
],
},
buyerInfo: {
email: 'janedoe#gmail.com',
firstName: 'Jane',
lastName: 'Doe',
displayName: 'Jane Doe',
},
followUpActions: [
{
type: 'VIEW_DETAILS',
title: 'View details',
openUrlAction: {
url: 'http://example.com',
},
},
{
type: 'CALL',
title: 'Call us',
openUrlAction: {
url: 'tel:+16501112222',
},
},
{
type: 'EMAIL',
title: 'Email us',
openUrlAction: {
url: 'mailto:person#example.com',
},
},
],
termsOfServiceUrl: 'http://www.example.com'
};
According to doc we have to create order object like above, but we are unable to find any doc what are optional filed in above object and what value to pass for reservationTime and userAcceptableTimeRange.
reservationTime: {
timeIso8601: '2020-01-16T01:30:15.01Z',
},
userAcceptableTimeRange: {
timeIso8601: '2020-01-15/2020-01-17',
}
Further more do we need to implement Set up asynchronous requests to the Orders API (https://developers.google.com/assistant/transactions/physical/dev-guide-physical-reservations#set_up_asynchronous_requests_to_the_orders_api) as we don't need to update user appoint time and user can't cancel appointment.
Is there any other way we can avoid reservation? please guide me, thanks in advance.

Group by and retain original fields

I see in mongodb aggregations, specially in $group we can use accumulator to create new fields. But i want the old keys
Suppose i have this data
[
{ name: "My Plan 101", billingCycle: 'day', amount: 1, credits: 100, price: 7 },
{ name: "My Plan 102", billingCycle: 'day', amount: 1, credits: 150, price: 10 },
{ name: "My Plan 103", billingCycle: 'day', amount: 2, credits: 150, price: 15 },
{ name: "My Plan 104", billingCycle: 'month', amount: 3, credits: 150, price: 15 },
{ name: "My Plan 105", billingCycle: 'month', amount: 3, credits: 200, price: 20 },
]
Then the aggregation should be like
[
'day': {
'1': [{ name: 'My Plan 101' }, { name: 'My Plan 102' }],
'2': [{ name: 'My Plan 103' }]
},
'month': {
'3': [{ name: 'My Plan 104' }, { name: 'My Plan 105' }]
}
]
I tried a lot with mongodb aggregation but couldn't solve it so I used lodash for this.
let plans = await Plan.find()
plans = _.groupBy(plans, 'billingCycle');
for (const billingCyle in plans) {
let $plans = plans[billingCyle];
plans[billingCyle] = _.groupBy($plans, "amount")
}
console.log(plans)
The above snipped has solved my problem

What's a good way to look up list of enumerated values?

I have a collection of case with a field named status (integer) whose valid values are 0, 1, 2, 4 and 8, representing "New", "WIP", "Solved", "Canceled" and "Closed" respectively.
So, in mongoose syntax, it might be like:
const caseSchema = new Schema({
createdOn: Date,
subittedBy: String,
status: Number,
...
});
const statusSchema = new Schema({
value: Number,
description: String
});
Is this a good way to organize the data? How do I make a query to retrieve cases with the status field properly filled with the description?
It is one way to do it sure. You could do the query by using $lookup. It would look something like this:
db.getCollection('<YourCasesColName>').aggregate([
{ $match : { 'status' : 1 } }, // or { $in: [1,2,3] },
{
$lookup: {
from: '<YourStatusColName>',
localField: 'status',
foreignField: 'value',
as: 'statusDoc',
}
}
])
Another way is to add a reference to the actual status via ObjectId so that instead of numbers in the cases you would be storing references to the actual Status objects and in this way have a better referential integrity. However you would still need to do similar query to get both in one shot. So here is what I am talking about:
const caseSchema = new Schema({
createdOn: Date,
subittedBy: String,
status: { type: mongoose.Schema.Types.ObjectId, ref: 'Status' },
// ^ now your status hows reference to exactly the type of status it has
});
const statusSchema = new Schema({
value: Number,
description: String
});
So the actual data would look like this:
// Statuses
[{
_id: <StatusMongoObjectID_1>,
value: 1,
description: 'New'
},{
_id: <StatusMongoObjectID_2>,
value: 2,
description: 'New'
}]
// Cases
[{
_id: <MongoObjectID>,
createdOn: '<SomeISODate>',
subittedBy: '<SomeString>',
status: <StatusMongoObjectID_1>
},
{
_id: <MongoObjectID>,
createdOn: '<SomeISODate>',
subittedBy: '<SomeString>',
status: <StatusMongoObjectID_2>
}]

How to create multiple reocrds of Ext.data.Model

I have this code:
Ext.define('mObject', {
extend: 'Ext.data.Model',
fields: [
{name: 'text', type: 'string'},
{name: 'x', type: 'int', convert: null},
{name: 'y', type: 'int', convert: null}
]
});
var obj = Ext.create('mObject', {
text : 'test test',
x : 100,
y : 24
});
How can I add more records? and is there any way to go to a specific record by a field value, I mean if I have 200 records and I ant to navigate to a record with name='yaya' do I have to go through all records and search for 'yaya' or there is alternative way?
Thank you all,
Ext.define('mObject', {
extend: 'Ext.data.Model',
fields: [
{name: 'text', type: 'string'},
{name: 'x', type: 'int', convert: null},
{name: 'y', type: 'int', convert: null}
]}
});
var mObject=Ext.create('Ext.data.Store', {
model: 'mObject',
data : []
});
mObject.add({text: 'sample1', x: 34, y:544});
mObject.add({text: 'sample2', x: 243, y:76});
mObject.add({text: 'sample3', x: 54, y:123});
mObject.add({text: 'sample4', x: 67, y:43});
mObject.add({text: 'sample5', x: 12, y:655});
var indx=mObject.find('text','sample4');
alert( mObject.getAt(indx).get('text'));