В Mongodb обнаружена ошибка в «$ lte» (запрос или агрегирование) при поиске диапазонов дат

#database #mongodb #date #mongodb-compass #non-relational-database

#База данных #mongodb #Дата #mongodb-compass #нереляционная база данных

Вопрос:

Сценарий:

У меня есть база данных, размещенная в MongoDB Atlas.

В этой базе данных есть коллекция, которая, среди прочих данных, имеет created поле типа Date .

 pseudoCode Schema:
... {
created: { type: Date }
}...
  

Я хочу выполнить запрос, который позволит мне найти все объекты, существующие в коллекции, которые имеют created значение между конкретными днями including и граничными датами.

Давайте предположим, что диапазон дат 2020-08-01 и 2020-08-31 , запрос будет

 {created: {'$gte': new Date('2020-08-01'), '$lte': new Date('2020-08-31')}}
  

верно?

Неправильно.

Выполняя запрос таким образом, я получаю только результаты, которые являются greater than or equal to «2020-08-01» и lower than «2020-08-31». Это означает, что, даже если я выполняю $lte запрос, я всегда получаю $lt результаты.

Тесты, которые я сделал

Я тестировал это только для поля даты типа atm в разных коллекциях и постоянно сталкивался с одной и той же проблемой. Пока не было времени для дальнейших исследований по различным типам данных.

Я тестировал это на aggregation $match конвейерах и find запросах на:

  • моя кодовая база
  • чистый скрипт, который просто выполняет эти операции
  • непосредственно в MongoDB Compass

Во всех 3 случаях результаты соответствуют выявленной проблеме и подтверждают проблему.

Быстрое исправление

Просто используйте $lt вместо $lte и всегда учитывайте на 1 день больше, чем вы предполагали. Используя предыдущий пример, запрос станет

 {created: {'$gte': new Date('2020-08-01'), '$lt': new Date('2020-09-01')}}
  

и в этом случае я получаю результаты ожидаемого диапазона дат «2020-08-01» — «2020-08-31».

Обратите внимание, что я мог бы также использовать $lte , и я бы получил точно такие же результаты, однако $lt форма логически более правильна для тех, кто читает код.

Почему я публикую это

Я обнаружил, что за эти годы мало кто сообщал об этой проблеме, более релевантными ссылками являются эта проблема с GitHub (первоначально разработчик полагал, что проблема связана с mongoose, затем было предложено решение для проверки схемы, но это не проблема, поскольку в моем случае схема определена правильно, и я протестировал ее на Compass напрямую) и это групповое обсуждение Google (проблема плохо сформулирована и не получила ответа).).

Но я не нашел решения.

Несмотря на то, что я быстро исправил проблему, я хотел бы лучше указать на это и понять, если:

  • Я делаю что-то не так, и это ожидаемое поведение
  • в моем запросе я что-то делаю неправильно
  • существует проблема, с $lte которой необходимо правильно обращаться

У кого есть идеи?

Ответ №1:

Когда вы запускаете new Date('2020-08-01') , результат на самом деле ISODate("2020-08-01T00:00:00Z")

Итак

 {created: {'$gte': new Date('2020-08-01'), '$lte': new Date('2020-08-31')}}
  

становится

 {created: {'$gte': ISODate("2020-08-01T00:00:00Z"), '$lte': ISODate("2020-08-31T00:00:00Z")}}
  

т. е. день 2020-08-31 не включен. Вы также можете учитывать часовые пояса, если данные были вставлены как местное время и, следовательно, сохранены не как 2020-08-02T00:00:00Z но 2020-08-02T02:00:00Z , например.

Одним из решений является добавление одного дня и использование $lt :

 {created: {'$gte': new Date('2020-08-01'), '$lt': new Date('2020-09-01')}}
  

или вы можете использовать Moment.js вот так:

 {created: {'$gte': new Date('2020-08-01'), '$lte': moment.utc('2020-08-31').endOf('day').toDate()}}
  

или, возможно moment.utc('2020-08-01').endOf('month').toDate()

Комментарии:

1. это то, о чем я не подумал! Я был сосредоточен на дате и полностью проигнорировал рассмотрение часовой части, которая все еще передается после преобразования в запросе. О! Я жду еще немного, чтобы узнать, есть ли какие-либо другие ответы, но я думаю, что у нас есть победитель здесь. Спасибо!

Ответ №2:

Быстрое исправление

Просто используйте $lt вместо $lte и всегда учитывайте на 1 день больше, чем вы предполагали. Используя предыдущий пример, запрос станет

 {created: {'$gte': new Date('2020-08-01'), '$lt': new Date('2020-09-01')}}
  

и в этом случае я получаю результаты ожидаемого диапазона дат «2020-08-01» — «2020-08-31».

Обратите внимание, что я мог бы также использовать $lte , и я бы получил точно такие же результаты, однако $lt форма логически более правильна для тех, кто читает код.