Запрос на сопоставление дат в массиве - PullRequest
0 голосов
/ 01 июня 2018

Я создал схему модели с несколькими вложенными полями, одним из которых является поле Timestamp:

{_id: Object_id,
  name: string,
someArray: [{Timestamp: Date, otherFields: ...},{Timestamp: Date, otherFields...},...],
...,
}

Отметка времени имеет тип: Timestamp: Date например (Timestamp: 2018-06-01T14:57:45.757647Z)

Теперь я хочу запросить только те документы из массива, которые находятся между начальной и конечной датой, которые получены в качестве параметров из URL-адреса API ...

/link/Collection/:start.:end.:id

URL-адрес моего маршрутизатора (со строками параметров как запрос) выглядит так:

http://localhost:6000/link/Collection/2018-06-01T14:50:45.2018-06-01T15:17:45.29

Моя функция запроса в mongoose / node (express) для получения данных выглядит следующим образом:

exports.list_by_start_end_date_ID = function(req, res) {
    console.log(req.body)
    d_id=req.params.id;
    start = req.params.start;
    end = req.params.end;
    console.log(d_id);
    console.log(start)
    console.log(end)
    console.log(new Date(start));
    console.log(new Date(end));
    //SomeColl.findById(d_id, "myCollection").where("myCollection.Timestamp").gte(new Date(start)).lte(new Date(end))
    SomeColl.findById(d_id, "myCollection",{"myCollection.Timestamp": {"$gte": new Date(start), "$lte": new Date(end)}})
    .exec( function(err, fps) {
        if (err)
            res.send(err);
        res.json(fps);
    });
};

Я получаю возвращение:

[{"Timestamp":"2018-06-01T14:57:45.757647Z"...},{"Timestamp":"2018-06-01T15:27:45.757647Z"...},{"Timestamp":"2018-06-01T15:12:45.757647Z"...}]

Я не получаю никакой ошибки, я также могу создать новую дату (начало) из параметров начала и конца, и это правильно, но, как вы можете видеть, документ со временем 15:27 не долженне возвращается ...

Я опробовал обе версии (также закомментированную версию) строк запроса, а также пробовал использовать пустую строку формата даты ISO, которую я передал в качестве параметра (начало / конец)на URL .. но ни один не работал.Как я могу сравнить даты в mongoose и получить правильные документы, переданные обратно?

РЕДАКТИРОВАТЬ: я попытался найти обходной путь, игнорируя операции db api и просто анализируя правильные документы (вложенные документы) массива с javascript..:

myColl.findById(d_id)
    .exec( function(err, fps) {
        if (err) {
        console.log(err);
            res.send(err);
        }
        else {          
            //console.log(fps["someArray"])
            laenge = fps["someArray"].length;
            console.log(laenge);
            startts = +new Date(start)
            endts = +new Date(end)
            TSarray = []
            console.log(startts,endts)
            for (let doc of fps["someArray"]) {
                ts = +new Date(doc["Timestamp"])
                //console.log(doc)
                if ((ts >= startts) && (ts <= endts)){
                    TSarray.push(doc)   
                    //console.log(TSarray)                              
                }
            }       
            res.json(TSarray)   
        //res.json(fps);
    }
    })//.then((res) => res.json())
};

Однако, когда я хочу получить результаты из массива, я получаю ошибку HTTP 304 .. Я пока не выяснил, как получить соответствующие поддокументы (на основе фильтракритерий) поля массива одного документа. Нужно ли использовать проекцию, чтобы получить только поле массива, а затем использовать некоторые критерии фильтра для этого массива, чтобы получить правильные поддокументы, или как это обычно работает?

// РЕДАКТИРОВАТЬ 2: Я пытался использовать структуру агрегации mongoDB, но получил возвращение []:

myColl.aggregate([{$match: {"id":d_id},
            someArray: {
                $filter: {
                    input: "$someArray",
                    as: "fp",
                    cond: {$and: [
                        {$gte: [ "$$fp.Timestamp", new Date(start)]},
                        {$lte: [ "$$fp.Timestamp", new Date(end)]}
                    ]}

                }
            }
        }
    }]).exec( function(err, fps) {
        if (err) {
        console.log(err);
            res.send(err);
        }
        else {  
            console.log(fps)
            res.json(fps);
    }
    })}
;

Это также не работает, что-то не так с этим запросом?Как я могу указать диапазон дат в mongoose с условием критерия фильтра?

// EDIT3: После 5 дней работы мне наконец-то удалось вернуть нужные документы на основе метки времени.Однако, чтобы получить документы с 14:00:00, я должен ввести 16:00:00 в качестве параметра url ... Я знаю, что это, вероятно, связано с UTC и часовыми поясами ... мой tz - Берлин,поэтому я думаю, что его UTC +2, так как серверы MongoDB находятся в Нью-Йорке, я думаю ... Как я могу лучше приспособиться к этой проблеме?

Вот моя функция:

myColl.findById(d_id, "someArray")
    .exec( function(err, fps) {
        if (err) {
        console.log(err);
            res.send(err);
        }
        else {  
            startts = +new Date(start)
            endts = +new Date(end)
            TSarray = []
            for (let doc of fps["Fahrplanabschnitte"]) {
                ts = + new Date(doc["Timestamp"]                    
                if ((ts >= startts) && (ts <= endts)){
                    TSarray.push(doc)   

                }               
            }   
            //for (let a of TSarray) {console.log(a)};
            res.json(TSarray);
        }
    })
};

1 Ответ

0 голосов
/ 04 июня 2018

Вам не хватает оператора $elemMatch в базовом запросе, а $filter, который вы пытались использовать в структуре агрегирования, фактически имеет неверный синтаксис.

Таким образом, возвращение документа, соответствующего датам, находящимся в пределах этого диапазона в массиве:

// 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); })

Фильтрация фактических элементов массива, которые должны быть возвращены:

// 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); })

Что нужно знатьв том, что в aggregate() вам на самом деле нужно «привести» значение ObjectId, потому что «автопередача» Mongoose здесь не работает.Обычно мангуст читает из схемы, чтобы определить, как приводить данные, но поскольку конвейеры агрегации «меняют вещи», этого не происходит.

$elemMatch существует, потому что как сказано в документации :

При указании условий для нескольких полей, вложенных в массив документов, вы можете указать запрос так, чтобы либо один документ удовлетворял этим условиям, либо любая комбинациядокументы (включая один документ) в массиве удовлетворяют условиям.

Используйте оператор $ elemMatch, чтобы указать несколько критериев для массива встроенных документов, чтобы хотя бы один внедренный документ удовлетворял всем указанным критериям.

Короче говоря $gte и $lt являются условием AND и считаются как «два», поэтому простая форма «точечной нотации» не применяется.Это также $lt, а не $lte, так как имеет смысл быть «меньше» «следующего дня», а не искать равенство вплоть до «последней миллисекунды»".

$filter, конечно, делает именно то, что предлагает его имя, и" фильтрует "фактическое содержимое массива, так что остаются только совпадающие элементы.


Демонстрация

Полный демонстрационный листинг создает два документа, один из которых имеет только два элемента массива, которые фактически соответствуют диапазону дат.Первый запрос показывает, что правильный документ соответствует диапазону.Вторая показывает «фильтрацию» массива:

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());

})()

Или немного более современно с синтаксисом async/await:

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()
  }

})()

Оба одинаковы и дают одинаковый вывод:

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
    }
  ]
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...