MongoDB: Mapreduce necessary? Range query on dates in a booking application - mongodb

My collection has the following simplified (booking) schema:
{
name: "room 1",
from: ISODate("2014-06-10T12:00:00Z"),
to: ISODate("2014-06-14T12:00:00Z")
},
{
name: "room 1",
from: ISODate("2014-06-25T12:00:00Z"),
to: ISODate("2014-06-27T12:00:00Z")
},
{
name: "room 2",
from: ISODate("2014-06-12T12:00:00Z"),
to: ISODate("2014-06-26T12:00:00Z")
}
I'd like to query, if a room is available in a given range. For example I'd like to know if
room 1 is available FROM 2014-06-11 TO 2014-06-13
room 1 is available FROM 2014-06-13 TO 2014-06-26
room 1 is available FROM 2014-06-15 TO 2014-06-18

Ok let's break this down, there are 4 ranges of booking conflicts:
start of another booking is before this start but the end is also before the end of this booking
start of another booking is after this start and the end is after this end
start of another booking is after this start and the end is before this end
start of other booking is before this start and after the end of this end
So you are looking for a query that can find all those ranges if they exist.
Now I set up this data:
> db.rooms.find()
{ "_id" : ObjectId("53ad206e1d8f2d8351182830"), "id" : 1, "from" : ISODate("2014-06-26T00:00:00Z"), "to" : ISODate("2014-06-28T00:00:00Z") }
{ "_id" : ObjectId("53ad276f1d8f2d8351182831"), "id" : 1, "from" : ISODate("2014-06-24T00:00:00Z"), "to" : ISODate("2014-07-01T00:00:00Z") }
{ "_id" : ObjectId("53ad28ad1d8f2d8351182832"), "id" : 1, "from" : ISODate("2014-06-20T00:00:00Z"), "to" : ISODate("2014-06-28T00:00:00Z") }
{ "_id" : ObjectId("53ad28c61d8f2d8351182833"), "id" : 1, "from" : ISODate("2014-06-20T00:00:00Z"), "to" : ISODate("2014-07-03T00:00:00Z") }
{ "_id" : ObjectId("53ad29971d8f2d8351182834"), "id" : 1, "from" : ISODate("2014-06-20T00:00:00Z"), "to" : ISODate("2014-06-21T00:00:00Z") }
(the 5th range being a tester to make sure the query doesn't return random results)
And then I ran:
> db.rooms.find({from:{$lte: ISODate('2014-06-30T00:00:00.000Z')}, to:{$gte: ISODate('2014-06-23T00:00:00.000Z')}})
{ "_id" : ObjectId("53ad206e1d8f2d8351182830"), "id" : 1, "from" : ISODate("2014-06-26T00:00:00Z"), "to" : ISODate("2014-06-28T00:00:00Z") }
{ "_id" : ObjectId("53ad276f1d8f2d8351182831"), "id" : 1, "from" : ISODate("2014-06-24T00:00:00Z"), "to" : ISODate("2014-07-01T00:00:00Z") }
{ "_id" : ObjectId("53ad28ad1d8f2d8351182832"), "id" : 1, "from" : ISODate("2014-06-20T00:00:00Z"), "to" : ISODate("2014-06-28T00:00:00Z") }
{ "_id" : ObjectId("53ad28c61d8f2d8351182833"), "id" : 1, "from" : ISODate("2014-06-20T00:00:00Z"), "to" : ISODate("2014-07-03T00:00:00Z") }
If that query returns there are bookings and as such you cannot insert.
That should cover all bases I think, I am a little sleep deprived so I might be wrong.

Considering your data is showing the dates on which the room is booked and you want to query for certain date ranges then you have to make sure that both these dates(for that you want to check availability for) lies outside the interval of "from" and "to" of the booked rooms, for that you have to run a query where both your input dates are either less than the "from" date or both are greater than the "to" date,this is how you do it
db.room.find({
$or:[
{$and : [{from:{$gt:new Date("2014-06-11T00:00:00.000Z")}},{to:{$gt:new Date("2014-06-13T00:00:00.000Z")}}]},
{$and : [{from:{$lt:new Date("2014-06-11T00:00:00.000Z")}},{to:{$lt:new Date("2014-06-13T00:00:00.000Z")}}]}
]
})
if you want to count the room just add .count() to the above query and if you want to query for a specific room then just add "name":"roomname" in the above query.
Hope it helps.

You can create range queries to check if from and to are within the searching dates. In addition the third use case is when the search dates cover one more documents' from/to interval.
My collection on which I performed tests looks like this:
> db.booking.find()
{ "_id" : ObjectId("53aa8d8cdfae7e8ccdd4b49d"), "name" : "room 1", "from" : ISODate("2014-06-10T12:00:00Z"), "to" : ISODate("2014-06-14T12:00:00Z") }
{ "_id" : ObjectId("53aa8da1dfae7e8ccdd4b49e"), "name" : "room 1", "from" : ISODate("2014-06-25T12:00:00Z"), "to" : ISODate("2014-06-27T12:00:00Z") }
{ "_id" : ObjectId("53aab77fe0cd5e6e1c56c08c"), "name" : "room 2", "from" : ISODate("2014-06-12T12:00:00Z"), "to" : ISODate("2014-06-26T12:00:00Z") }
{ "_id" : ObjectId("53ad1107d20cc7b2fe2df520"), "name" : "room 1", "from" : ISODate("2014-07-05T12:00:00Z"), "to" : ISODate("2014-07-07T12:00:00Z") }
{ "_id" : ObjectId("23ad1107d20cc7b2fe2df520"), "name" : "room 1", "from" : ISODate("2014-06-12T12:00:00Z"), "to" : ISODate("2014-06-26T12:00:00Z") }
>
Here are queries you can execute to check if a room is available (the return value of 0 means the room is available):
room 1 is available FROM 2014-06-11 TO 2014-06-13
db.booking.count({
name:"room 1",
$or:[
{
from:{$lte:new Date("2014-06-11T00:00:00.000Z")},
to:{$gte:new Date("2014-06-11T00:00:00.000Z")}
},
{
from:{$lte:new Date("2014-06-13T00:00:00.000Z")},
to:{$gte:new Date("2014-06-13T00:00:00.000Z")}
},
{
from:{$gte:new Date("2014-06-11T00:00:00.000Z")},
to:{$lte:new Date("2014-06-13T00:00:00.000Z")}
}
]})
room 1 is available FROM 2014-06-13 TO 2014-06-26
db.booking.count({
name:"room 1",
$or:[
{
from:{$lte:new Date("2014-06-13T00:00:00.000Z")},
to:{$gte:new Date("2014-06-13T00:00:00.000Z")}
},
{
from:{$lte:new Date("2014-06-26T00:00:00.000Z")},
to:{$gte:new Date("2014-06-26T00:00:00.000Z")}
},
{
from:{$gte:new Date("2014-06-13T00:00:00.000Z")},
to:{$lte:new Date("2014-06-26T00:00:00.000Z")}
}
]})
room 1 is available FROM 2014-06-15 TO 2014-06-18
db.booking.count({
name:"room 1",
$or:[
{
from:{$lte:new Date("2014-06-15T00:00:00.000Z")},
to:{$gte:new Date("2014-06-15T00:00:00.000Z")}
},
{
from:{$lte:new Date("2014-06-18T00:00:00.000Z")},
to:{$gte:new Date("2014-06-18T00:00:00.000Z")}
},
{
from:{$gte:new Date("2014-06-15T00:00:00.000Z")},
to:{$lte:new Date("2014-06-18T00:00:00.000Z")}
}
]})
room 1 is available FROM 2014-07-02 TO 2014-07-09
db.booking.count({
name:"room 1",
$or:[
{
from:{$lte:new Date("2014-07-02T00:00:00.000Z")},
to:{$gte:new Date("2014-07-02T00:00:00.000Z")}
},
{
from:{$lte:new Date("2014-07-09T00:00:00.000Z")},
to:{$gte:new Date("2014-07-09T00:00:00.000Z")}
},
{
from:{$gte:new Date("2014-07-02T00:00:00.000Z")},
to:{$lte:new Date("2014-07-09T00:00:00.000Z")}
}
]})

Related

Mongoose updateMany :: wont find any on given condition

I have updateMany function as follows
Article.updateMany({author: userId}, {author: anonym}, function(err, updated) {
if (err) {
res.send(err);
} else {
res.send(updated);
}
});
userId is = 6068b57dbe4eef0b579120c7
anonym is = 6069870676d6320f39e7e5a2
for testing purposes I have a single article in MongoDB as follows
db.articles.find()
{ "_id" : ObjectId("6068b591be4eef0b579120c8"), "favoritesCount" : 1, "comments" : [ ], "tagList" : [ ], "title" : "Martin", "description" : "Testib", "body" : "Asju", "author" : ObjectId("6068b57dbe4eef0b579120c7"), "slug" : "martin-2hzx78", "createdAt" : ISODate("2021-04-03T18:36:01.977Z"), "updatedAt" : ISODate("2021-04-03T18:53:29.809Z"), "__v" : 0 }
You can see that article has "author" : id field in it which currently shows userId as author.
I want to update that field and transfer authorship to anonym user.
When I send this request to postman I get following response
{
"n": 0,
"nModified": 0,
"ok": 1
}
And database remains unchanged. What am I doing wrong here ?

Insert document into mongodb from existing table

I am trying to write a query in mongo that will create a new table, loop through my data set, and insert the TopExecutiveTitle into the new table. I also would like it to keep count of each position and only insert a position into the table when it is new.
This is what I have so far. This code loops through my table and inserts the TopExectuiveTitle into a new table. However, it does not group them together and keep count. How do I write my query so that it will?
db.car.find().forEach( function (x) {
db.TopExecutiveTable.insert({Topexecutivetitle: x.Topexecutivetitle})
});
Here is a sample of a document in my database.
{
"_id" : ObjectId("5a22c8e562c2e489c5df70fa"),
"2016rank" : 1,
"Dealershipgroupname" : "AutoNation Inc.?",
"Address" : "200 S.W. 1st Ave.",
"City/State/Zip" : "Fort Lauderdale, FL 33301",
"Phone" : "(954) 769-7000",
"Companywebsite" : "www.autonation.com",
"Topexecutive" : "Mike Jackson",
"Topexecutivetitle" : "chairman & CEO",
"Totalnewretailunits" : "337,622",
"Totalusedunits" : "225,713",
"Totalfleetunits" : 3,
"Totalwholesaleunits" : "82,342",
"Total_units" : "649,415",
"Total_number_of _dealerships" : 260,
"Grouprevenuealldepartments*" : "$21,609,000,000",
"2015rank" : 1
}
The result I would like is something like this
"Topexecutivetitle" : "chairman & CEO"
"Count" : 3
"Topexecutivetitle" : "president"
"Count" : 7
}
To do this you need to use the aggregate function of mongo, something like this:
db.car.aggregate([
{
$group:{
_id:"$Topexecutivetitle",
count:{$sum:1}
}
},
{
$project:{
Topexecutivetitle:"$_id",
count:1,
_id:0
}
},
{
$out:"result"
}])
This will give you your desired output and store it into a new collection "result":
{
"_id" : "president",
"count" : 1.0
},
{
"_id" : "chairman & CEO",
"count" : 3.0
}

Is a mongodb query with 1 indexed field faster than multiple indexed fields?

In the following model a product is owned by a customer. and cannot be ordered by other customers. So I know that in an order by customer 1 there can only be products owned by customer one.
To give you an idea here is a simple version of the data model:
Orders:
{
'customer' : 1
'products' : [
{'productId' : 'a'},
{'productId' : 'b'}
]
}
Products:
{
'id' : 'a'
'name' : 'somename'
'customer' : 1
}
I need to find orders that contain certain products. I know the product id and customer id. I'm free to add/change indexes on my database.
Now my question is. Is it faster to just add a single field index on the product id's and query only using that ID. Or should I go for a compound index with customer and product id?
I'm not sure if this matters, but in my real model the list of products is actually a list of objects which have an amount and a dbref to the product. And the customer is also a dbref.
Here is a full order object:
{
"_id" : 0,
"_class" : "nl.pfa.myprintforce.models.Order",
"orderNumber" : "e35f1fa8-b4c4-4d53-89c9-66abe94a3553",
"status" : "ERROR",
"created" : ISODate("2017-03-30T11:50:50.292Z"),
"finished" : false,
"orderTime" : ISODate("2017-01-12T12:50:50.292Z"),
"expectedDelivery" : ISODate("2017-03-30T11:50:50.292Z"),
"totalItems" : 19,
"orderItems" : [
{
"amount" : 4,
"product" : {
"$ref" : "product",
"$id" : NumberLong(16)
}
},
{
"amount" : 7,
"product" : {
"$ref" : "product",
"$id" : NumberLong(26)
}
},
{
"amount" : 8,
"product" : {
"$ref" : "product",
"$id" : NumberLong(7)
}
}
],
"stateList" : [
{
"timestamp" : ISODate("2017-03-28T11:50:50.074Z"),
"status" : "NEW",
"message" : ""
},
{
"timestamp" : ISODate("2017-03-29T11:50:50.075Z"),
"status" : "IN_PRODUCTION",
"message" : ""
},
{
"timestamp" : ISODate("2017-03-30T11:50:50.075Z"),
"status" : "ERROR",
"message" : "Something went wrong"
}
],
"customer" : {
"$ref" : "customer",
"$id" : ObjectId("58dcf11a71571a24c475c044")
}
}
When I have the following indexes:
1: {"customer" : 1, "orderItems.product" : 1}
2: {"orderItems.product" : 1}
both count queries (I use count to forcefully find all documents without the network transfer):
a: db.getCollection('order').find({
'orderItems.product' : DBRef('product',113)
}).count()
b: db.getCollection('order').find({
'customer' : DBRef('customer',ObjectId("58de009671571a07540a51d5")),
'orderItems.product' : DBRef('product',113)
}).count()
Run with the same time of ~0.007 seconds on a set of 200k.
When I add 1000k record for a different customer (and different products) it does not effect the time at all.
an extended explain shows that:
query 1 just uses index 2.
query 2 uses index 2 but also considered index 1. Perhaps index intersection is used here?
Because if I drop index 1 the results are:
Query a: 0.007 seconds
Query b: 0.035 seconds (5x as long!)
So my conclusion is that with the right indexing both methods work about as fast. However, if you do not need the compound index for anything else it's just a waste of space & write speed.
So: single field index is better in my case.

How to update particular array element in MongoDB

I am newbie in MongoDB. I have stored data inside mongoDB in below format
"_id" : ObjectId("51d5725c7be2c20819ac8a22"),
"chrom" : "chr22",
"pos" : 17060409,
"information" : [
{
"name" : "Category",
"value" : "3"
},
{
"name" : "INDEL",
"value" : "INDEL"
},
{
"name" : "DP",
"value" : "31"
},
{
"name" : "FORMAT",
"value" : "GT:PL:GQ"
},
{
"name" : "PV4",
"value" : "1,0.21,0.00096,1"
}
],
"sampleID" : "Job1373964150558382243283"
I want to update the value to 11 which has the name as Category.
I have tried below query:
db.VariantEntries.update({$and:[ { "pos" : 117199533} , { "sampleID" : "Job1373964150558382243283"},{"information.name":"Category"}]},{$set:{'information.value':'11'}})
but Mongo replies
can't append to array using string field name [value]
How one can form a query which will update the particular value?
You can use the $ positional operator to identify the first array element to match the query in the update like this:
db.VariantEntries.update({
"pos": 17060409,
"sampleID": "Job1373964150558382243283",
"information.name":"Category"
},{
$set:{'information.$.value':'11'}
})
In MongoDB you can't adress array values this way. So you should change your schema design to:
"information" : {
'category' : 3,
'INDEL' : INDEL
...
}
Then you can adress the single fields in your query:
db.VariantEntries.update(
{
{"pos" : 117199533} ,
{"sampleID" : "Job1373964150558382243283"},
{"information.category":3}
},
{
$set:{'information.category':'11'}
}
)

MongoDB - how to query for a nested item inside a collection?

I have some data that looks like this:
[
{
"_id" : ObjectId("4e2f2af16f1e7e4c2000000a"),
"advertisers" : [
{
"created_at" : ISODate("2011-07-26T21:02:19Z"),
"category" : "Infinity Pro Spin Air Brush",
"updated_at" : ISODate("2011-07-26T21:02:19Z"),
"lowered_name" : "conair",
"twitter_name" : "",
"facebook_page_url" : "",
"website_url" : "",
"user_ids" : [ ],
"blog_url" : "",
},
and I was thinking that a query like this would give the id of the advertiser:
var start = new Date(2011, 1, 1);
> var end = new Date(2011, 12, 12);
> db.agencies.find( { "created_at" : {$gte : start , $lt : end} } , { _id : 1 , program_ids : 1 , advertisers { name : 1 } } ).limit(1).toArray();
But my query didn't work. Any idea how I can add the fields inside the nested elements to my list of fields I want to get?
Thanks!
Use dot notation (e.g. advertisers.name) to query and retrieve fields from nested objects:
db.agencies.find({
"advertisers.created_at": {
$gte: start,
$lt: end
}
},
{
_id: 1,
program_ids: 1,
"advertisers.name": 1
}
}).limit(1).toArray();
Reference: Retrieving a Subset of Fields
and Dot Notation
db.agencies.find(
{ "advertisers.created_at" : {$gte : start , $lt : end} } ,
{ program_ids : 1 , advertisers.name : 1 }
).limit(1).pretty();
There is one thing called dot notation that MongoDB provides that allows you to look inside arrays of elements. Using it is as simple as adding a dot for each array you want to enter.
In your case
"_id" : ObjectId("4e2f2af16f1e7e4c2000000a"),
"advertisers" : [
{
"created_at" : ISODate("2011-07-26T21:02:19Z"),
"category" : "Infinity Pro Spin Air Brush",
"updated_at" : ISODate("2011-07-26T21:02:19Z"),
"lowered_name" : "conair",
"twitter_name" : "",
"facebook_page_url" : "",
"website_url" : "",
"user_ids" : [ ],
"blog_url" : "",
},
{ ... }
If you want to go inside the array of advertisers to look for the property created_at inside each one of them, you can simply write the query with the property {'advertisers.created_at': query} like follows
db.agencies.find( { 'advertisers.created_at' : { {$gte : start , $lt : end} ... }