Ihnen fehlt $elemMatch
-Operator für die Basisabfrage und den $filter
Sie es mit dem Aggregation Framework versucht haben, hat tatsächlich eine falsche Syntax.
Das Zurückgeben des Dokuments, das mit den Daten innerhalb dieses Bereichs im Array übereinstimmt, lautet also:
// Simulating the date values
var start = new Date("2018-06-01"); // otherwise new Date(req.params.start)
var end = new Date("2018-07-01"); // otherwise new Date(req.params.end)
myColl.find({
"_id": req.params.id,
"someArray": {
"$elemMatch": { "$gte": start, "$lt": end }
}
}).then( doc => {
// do something with matched document
}).catch(e => { console.err(e); res.send(e); })
Das Filtern der tatsächlich zurückzugebenden Array-Elemente ist:
// Simulating the date values
var start = new Date("2018-06-01");
var end = new Date("2018-07-01");
myColl.aggregate([
{ "$match": {
"_id": mongoose.Types.ObjectId(req.params.id),
"someArray": {
"$elemMatch": { "$gte": start, "$lt": end }
}
}},
{ "$project": {
"name": 1,
"someArray": {
"$filter": {
"input": "$someArray",
"cond": {
"$and": [
{ "$gte": [ "$$this.Timestamp", start ] }
{ "$lt": [ "$$this.Timestamp", end ] }
]
}
}
}
}}
]).then( docs => {
/* remember aggregate returns an array always, so if you expect only one
* then it's index 0
*
* But now the only items in 'someArray` are the matching ones, so you don't need
* the code you were writing to just pull out the matching ones
*/
console.log(docs[0].someArray);
}).catch(e => { console.err(e); res.send(e); })
Zu beachten sind die Dinge in aggregate()
Sie müssen die ObjectId
tatsächlich "umwandeln". Wert, da Mongoose "Autocasting" hier nicht funktioniert. Normalerweise liest Mongoose aus dem Schema, um zu bestimmen, wie die Daten gecastet werden, aber da Aggregationspipelines „Dinge ändern“, passiert dies nicht.
Der $elemMatch
ist da, weil wie die Dokumentation sagt
:
Kurz gesagt $gte
und $lt
sind eine UND-Bedingung und zählen als „zwei“, daher entfällt die einfache „Punkt-Notation“. Es ist auch $lt
und nicht $lte
, da es sinnvoller ist, "kleiner als" der "nächste Tag" zu sein, anstatt bis zur "letzten Millisekunde" Gleichheit zu suchen.
Der $filter
tut natürlich genau das, was der Name vermuten lässt und "filtert" den eigentlichen Array-Inhalt so, dass nur passende Elemente zurückgelassen werden.
Demonstration
Die vollständige Demonstrationsauflistung erstellt zwei Dokumente, von denen eines nur zwei Array-Elemente enthält, die tatsächlich mit dem Datumsbereich übereinstimmen. Die erste Abfrage zeigt, dass das richtige Dokument mit dem Bereich übereinstimmt. Die zweite zeigt die "Filterung" des Arrays:
const { Schema, Types: { ObjectId } } = mongoose = require('mongoose');
const uri = 'mongodb://localhost/test';
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
const subSchema = new Schema({
timestamp: Date,
other: String
});
const testSchema = new Schema({
name: String,
someArray: [subSchema]
});
const Test = mongoose.model('Test', testSchema, 'filtertest');
const log = data => console.log(JSON.stringify(data, undefined, 2));
const startDate = new Date("2018-06-01");
const endDate = new Date("2018-07-01");
(function() {
mongoose.connect(uri)
.then(conn =>
Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()))
)
.then(() =>
Test.insertMany([
{
_id: "5b1522f5cdac0b6da18f7618",
name: 'A',
someArray: [
{ timestamp: new Date("2018-06-01"), other: "C" },
{ timestamp: new Date("2018-07-04"), other: "D" },
{ timestamp: new Date("2018-06-10"), other: "E" }
]
},
{
_id: "5b1522f5cdac0b6da18f761c",
name: 'B',
someArray: [
{ timestamp: new Date("2018-07-04"), other: "D" },
]
}
])
)
.then(() =>
Test.find({
"someArray": {
"$elemMatch": {
"timestamp": { "$gte": startDate, "$lt": endDate }
}
}
}).then(docs => log({ docs }))
)
.then(() =>
Test.aggregate([
{ "$match": {
"_id": ObjectId("5b1522f5cdac0b6da18f7618"),
"someArray": {
"$elemMatch": {
"timestamp": { "$gte": startDate, "$lt": endDate }
}
}
}},
{ "$addFields": {
"someArray": {
"$filter": {
"input": "$someArray",
"cond": {
"$and": [
{ "$gte": [ "$$this.timestamp", startDate ] },
{ "$lt": [ "$$this.timestamp", endDate ] }
]
}
}
}
}}
]).then( filtered => log({ filtered }))
)
.catch(e => console.error(e))
.then(() => mongoose.disconnect());
})()
Oder etwas moderner mit async/await
Syntax:
const { Schema, Types: { ObjectId } } = mongoose = require('mongoose');
const uri = 'mongodb://localhost/test';
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
const subSchema = new Schema({
timestamp: Date,
other: String
});
const testSchema = new Schema({
name: String,
someArray: [subSchema]
});
const Test = mongoose.model('Test', testSchema, 'filtertest');
const log = data => console.log(JSON.stringify(data, undefined, 2));
(async function() {
try {
const startDate = new Date("2018-06-01");
const endDate = new Date("2018-07-01");
const conn = await mongoose.connect(uri);
// Clean collections
await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
// Create test items
await Test.insertMany([
{
_id: "5b1522f5cdac0b6da18f7618",
name: 'A',
someArray: [
{ timestamp: new Date("2018-06-01"), other: "C" },
{ timestamp: new Date("2018-07-04"), other: "D" },
{ timestamp: new Date("2018-06-10"), other: "E" }
]
},
{
_id: "5b1522f5cdac0b6da18f761c",
name: 'B',
someArray: [
{ timestamp: new Date("2018-07-04"), other: "D" },
]
}
]);
// Select matching 'documents'
let docs = await Test.find({
"someArray": {
"$elemMatch": {
"timestamp": { "$gte": startDate, "$lt": endDate }
}
}
});
log({ docs });
let filtered = await Test.aggregate([
{ "$match": {
"_id": ObjectId("5b1522f5cdac0b6da18f7618"),
"someArray": {
"$elemMatch": {
"timestamp": { "$gte": startDate, "$lt": endDate }
}
}
}},
{ "$addFields": {
"someArray": {
"$filter": {
"input": "$someArray",
"cond": {
"$and": [
{ "$gte": [ "$$this.timestamp", startDate ] },
{ "$lt": [ "$$this.timestamp", endDate ] }
]
}
}
}
}}
]);
log({ filtered });
mongoose.disconnect();
} catch(e) {
console.error(e)
} finally {
process.exit()
}
})()
Beide sind gleich und geben die gleiche Ausgabe:
Mongoose: filtertest.remove({}, {})
Mongoose: filtertest.insertMany([ { _id: 5b1522f5cdac0b6da18f7618, name: 'A', someArray: [ { _id: 5b1526952794447083ababf6, timestamp: 2018-06-01T00:00:00.000Z, other: 'C' }, { _id: 5b1526952794447083ababf5, timestamp: 2018-07-04T00:00:00.000Z, other: 'D' }, { _id: 5b1526952794447083ababf4, timestamp: 2018-06-10T00:00:00.000Z, other: 'E' } ], __v: 0 }, { _id: 5b1522f5cdac0b6da18f761c, name: 'B', someArray: [ { _id: 5b1526952794447083ababf8, timestamp: 2018-07-04T00:00:00.000Z, other: 'D' } ], __v: 0 } ], {})
Mongoose: filtertest.find({ someArray: { '$elemMatch': { timestamp: { '$gte': new Date("Fri, 01 Jun 2018 00:00:00 GMT"), '$lt': new Date("Sun, 01 Jul 2018 00:00:00 GMT") } } } }, { fields: {} })
{
"docs": [
{
"_id": "5b1522f5cdac0b6da18f7618",
"name": "A",
"someArray": [
{
"_id": "5b1526952794447083ababf6",
"timestamp": "2018-06-01T00:00:00.000Z",
"other": "C"
},
{
"_id": "5b1526952794447083ababf5",
"timestamp": "2018-07-04T00:00:00.000Z",
"other": "D"
},
{
"_id": "5b1526952794447083ababf4",
"timestamp": "2018-06-10T00:00:00.000Z",
"other": "E"
}
],
"__v": 0
}
]
}
Mongoose: filtertest.aggregate([ { '$match': { _id: 5b1522f5cdac0b6da18f7618, someArray: { '$elemMatch': { timestamp: { '$gte': 2018-06-01T00:00:00.000Z, '$lt': 2018-07-01T00:00:00.000Z } } } } }, { '$addFields': { someArray: { '$filter': { input: '$someArray', cond: { '$and': [ { '$gte': [ '$$this.timestamp', 2018-06-01T00:00:00.000Z ] }, { '$lt': [ '$$this.timestamp', 2018-07-01T00:00:00.000Z ] } ] } } } } } ], {})
{
"filtered": [
{
"_id": "5b1522f5cdac0b6da18f7618",
"name": "A",
"someArray": [
{
"_id": "5b1526952794447083ababf6",
"timestamp": "2018-06-01T00:00:00.000Z",
"other": "C"
},
{
"_id": "5b1526952794447083ababf4",
"timestamp": "2018-06-10T00:00:00.000Z",
"other": "E"
}
],
"__v": 0
}
]
}