问题
I am working on a project which needs to expire unaccepted orders after a set amount of time. Here is the sample structure of an order:
{
_id: ObjectId,
time: int
history: {
created_at: Date,
accepted_at: ['Null', 'Date'],
completed_at: ['Null', 'Date']
}
}
If accepted_at
field is still null after some hours from created_at
, the order will be considered as expired.
If order is accepted and completed_at
is still null after time
hours from created_at
the order will be failed.
I want to get failed orders from document. Something like this:
db.orders.aggregate([
{
$match: {
"history.accepted_at": { $exists: true },
"history.created_at" : {
$exists: true,
$lte: new Date((new Date()).getTime() - $time *3600 * 1000)
}
}
}
])
回答1:
I would suggest you to store values of timestamps for all, that is most universal solution & also easy to manipulate for queries of such kind
So your schema be like
{
time:{
type: Number,
default: 0
},
history:{
created_at:{
type: Number,
default: 0
},
accepted_at:{
type: Number,
default: 0
},
completed_at:{
type: Number,
default: 0
}
}
}
And then write your query like this
db.collection.aggregate([
{
$addFields: {
"end_deadline": {
$add: [
"$history.created_at",
{
$multiply: [
"$time",
3600000
]
}
]
},
}
},
{
$match: {
"history.accepted_at": {
$exists: true
},
"history.completed_at": null,
$expr: {
$gte: [
ISODate("2020-01-23T00:00:00.441Z"),
"$end_deadline"
]
}
}
}
])
Sample Data
[
{
"time": 12,
"start_from": 1579910400441,
"history": {
"created_at": 1579737600441,
"accepted_at": 1579741200441
}
},
{
"time": 24,
"start_from": 1579932000441,
"history": {
"created_at": 1579737600441,
"accepted_at": 1579739400441,
"completed_at": 1579935600441
}
},
{
"time": 24,
"start_from": 1578700800441,
"history": {
"created_at": 1578528000441,
"accepted_at": 1578614400441
}
}
]
Sample response
[
{
"_id": ObjectId("5a934e000102030405000000"),
"end_deadline": 1.579780800441e+12,
"history": {
"accepted_at": 1.579741200441e+12,
"created_at": 1.579737600441e+12
},
"start_from": 1.579910400441e+12,
"time": 12
},
{
"_id": ObjectId("5a934e000102030405000002"),
"end_deadline": 1.578614400441e+12,
"history": {
"accepted_at": 1.578614400441e+12,
"created_at": 1.578528000441e+12
},
"start_from": 1.578700800441e+12,
"time": 24
}
]
回答2:
This is the correct answer for BSON Date type.
Sample Data
[
{
"time": 12,
"start_from": new Date("2020-01-25T00:00:00.441+00:00"),
"history": {
"created_at": new Date("2020-01-23T00:00:00.441+00:00"),
"accepted_at": new Date("2020-01-23T01:00:00.441+00:00")
}
},
{
"time": 24,
"start_from": new Date("2020-01-25T06:00:00.441+00:00"),
"history": {
"created_at": new Date("2020-01-23T00:00:00.441+00:00"),
"accepted_at": new Date("2020-01-23T00:30:00.441+00:00"),
"completed_at": new Date("2020-01-25T07:00:00.441+00:00")
}
},
{
"time": 24,
"start_from": new Date("2020-01-11T00:00:00.441+00:00"),
"history": {
"created_at": new Date("2020-01-09T00:00:00.441+00:00"),
"accepted_at": new Date("2020-01-10T00:00:00.441+00:00")
}
}
]
Query
db.collection.aggregate([
{
$addFields: {
"deadline": {
$add: [
"$start_from",
{
$multiply: [
"$time",
3600000
]
}
]
},
}
},
{
$match: {
"history.accepted_at": {
$exists: true
},
"history.completed_at": null,
$expr: {
$gte: [
"$deadline",
ISODate("2020-01-23T00:00:00.441Z")
]
}
}
}
])
Result
[
{
"_id": ObjectId("5a934e000102030405000000"),
"end_deadline": ISODate("2020-01-25T12:00:00.441Z"),
"history": {
"accepted_at": ISODate("2020-01-23T01:00:00.441Z"),
"created_at": ISODate("2020-01-23T00:00:00.441Z")
},
"start_from": ISODate("2020-01-25T00:00:00.441Z"),
"time": 12
}
]
You can see the working code in mongoplayground.
NOTE
Difference between BSON date type and BSON timestamps
The BSON timestamp type is for internal MongoDB use. For most cases, in application development, you will want to use the BSON date type.
BSON timestamp type and BSON Date type are both 64-bit integers.
BSON Date type:
Internally, Date objects are stored as a signed 64-bit integer representing the number of milliseconds since the Unix epoch (Jan 1, 1970).
BSON Timestamps:
BSON has a special timestamp type for internal MongoDB use and is not associated with the regular Date type. This internal timestamp type is a 64 bit value where:
- the most significant 32 bits are a time_t value (seconds since the Unix epoch)
- the least significant 32 bits are an incrementing ordinal for operations within a given second.
MongoDB official documentation
- You cannot use Date() function directly in query.
Date() returns the current date as a string in the mongo shell.
It only works in mongo shell. But in Node.js, it's OK because the mongo driver will convert it.
- ISODate is just a helper function.
ISODate() is a helper function that's built into to MongoDB and wraps the native JavaScript Date object. When you use the ISODate() constructor from the Mongo shell, it actually returns a JavaScript Date object.
Compose Tips: Dates and Dating in MongoDB
来源:https://stackoverflow.com/questions/59822405/mongodb-query-and-aggregation-for-bson-date-type