Обработка необязательных/пустых данных в MongoDB

Я помню, как где-то читал, что движок mongo был более комфортным, когда вся структура документа уже была на месте в случае обновления, поэтому вот вопрос.

При работе с "пустыми" данными, например, при вставке пустой строки, должен ли я по умолчанию использовать ее null, "" или вообще не вставлять ее?

{
    _id: ObjectId("5192b6072fda974610000005"),
    description: ""
}

или

{
    _id: ObjectId("5192b6072fda974610000005"),
    description: null
}

или

{
    _id: ObjectId("5192b6072fda974610000005")
}

Вы должны помнить, что поле description может быть заполнено или не заполнено в каждом документе (на основе ввода пользователя).

Ответ 1

Введение

Если документ не имеет значения, БД считает его значение равным нулю. Предположим, что база данных со следующими документами:

{ "_id" : ObjectId("5192d23b1698aa96f0690d96"), "a" : 1, "desc" : "" }
{ "_id" : ObjectId("5192d23f1698aa96f0690d97"), "a" : 1, "desc" : null }
{ "_id" : ObjectId("5192d2441698aa96f0690d98"), "a" : 1 }

Если вы создаете запрос для поиска документов с полем desc, отличным от нуля, вы получите только один документ:

db.test.find({desc: {$ne: null}})
// Output:
{ "_id" : ObjectId("5192d23b1698aa96f0690d96"), "a" : 1, "desc" : "" }

База данных не отличается документами без поля desc и документами с полем desc со значением null. Еще одно испытание:

db.test.find({desc: null})
// Output:
{ "_id" : ObjectId("5192d2441698aa96f0690d98"), "a" : 1 }
{ "_id" : ObjectId("5192d23f1698aa96f0690d97"), "a" : 1, "desc" : null }

Но различия игнорируются в запросах, потому что, как показано в последнем примере выше, поля все еще сохраняются на диске, и вы получите документы с той же структурой документов, которые были отправлены в MongoDB.

Вопрос

Когда вы работаете с "пустыми" данными, например, при вставке пустой строки, должен ли я по умолчанию иметь значение null, "" или вообще не вставлять его?

От {desc: null} до {} нет большой разницы, потому что большинство операторов будут иметь одинаковый результат. Вы должны уделять особое внимание этим двум операторам:

Я бы сохранил документы без поля desc, потому что операторы будут продолжать работать как ожидалось, и я бы сэкономил некоторое пространство.

Коэффициент заполнения

Если документы в вашей базе данных растут часто, MongoDB, возможно, потребуется переместить документы во время обновления, поскольку в предыдущем месте документа недостаточно места. Чтобы предотвратить перемещение документов, MongoDB выделяет дополнительное пространство для каждого документа.

Количество дополнительного пространства, выделенного MongoDB на документ, контролируется дополняющим фактором. Вы не можете (и не должны) выбирать коэффициент заполнения, потому что MongoDB будет адаптировать его, но вы можете помочь MongoDB предварительно распределить внутреннее пространство для каждого документа, заполнив возможные будущие поля нулевыми значениями. Разница очень маленькая (в зависимости от вашего приложения) и может быть еще меньше после того, как MongoDB узнает лучший коэффициент заполнения.

Разреженные индексы

Этот раздел не слишком важен для вашей конкретной проблемы прямо сейчас, но может помочь вам, когда вы сталкиваетесь с аналогичными проблемами.

Если вы создаете уникальный индекс в поле desc, то вы не сможете сохранить более одного документа с тем же значением и в предыдущей базе данных у нас было более одного документа с одинаковым значением в поле desc. Попробуйте создать уникальный индекс в предыдущей представленной базе данных и посмотреть, какую ошибку мы получим:

db.test.ensureIndex({desc: 1}, {unique: true})
// Output:
{
    "err" : "E11000 duplicate key error index: test.test.$desc_1  dup key: { : null }",
    "code" : 11000,
    "n" : 0,
    "connectionId" : 3,
    "ok" : 1
}

Если мы хотим создать уникальный индекс в каком-либо поле и, пусть некоторые документы имеют это пустое поле, мы должны создать разреженный индекс. Попробуйте снова создать уникальный индекс:

// No errors this time:
db.test.ensureIndex({desc: 1}, {unique: true, sparse: true})

До сих пор так хорошо, но почему я объясняю все это? Потому что существует неясное поведение относительно редких индексов. В следующем запросе мы ожидаем, что документы ВСЕ отсортированы по убыванию.

db.test.find().sort({desc: 1})
// Output:
{ "_id" : ObjectId("5192d23f1698aa96f0690d97"), "a" : 1, "desc" : null }
{ "_id" : ObjectId("5192d23b1698aa96f0690d96"), "a" : 1, "desc" : "" }

Результат кажется странным. Что случилось с недостающим документом? Попробуйте выполнить запрос без его сортировки:

{ "_id" : ObjectId("5192d23b1698aa96f0690d96"), "a" : 1, "desc" : "" }
{ "_id" : ObjectId("5192d23f1698aa96f0690d97"), "a" : 1, "desc" : null }
{ "_id" : ObjectId("5192d2441698aa96f0690d98"), "a" : 1 }

На этот раз все документы были возвращены. Что происходит? Это просто, но не так очевидно. Когда мы сортируем результат по desc, мы используем разреженный индекс, созданный ранее, и нет записей для документов, у которых нет поля desc. Следующий запрос показывает нам использование индекса для сортировки результата:

db.test.find().sort({desc: 1}).explain().cursor
// Output:
"BtreeCursor desc_1"

Мы можем пропустить индекс с помощью подсказки:

db.test.find().sort({desc: 1}).hint({$natural: 1})
// Output:
{ "_id" : ObjectId("5192d23f1698aa96f0690d97"), "a" : 1, "desc" : null }
{ "_id" : ObjectId("5192d2441698aa96f0690d98"), "a" : 1 }
{ "_id" : ObjectId("5192d23b1698aa96f0690d96"), "a" : 1, "desc" : "" }

Резюме

  • Разреженные уникальные индексы не работают, если вы включили {desc: null}
  • Разреженные уникальные индексы не работают, если вы включили {desc: ""}
  • Разреженные индексы могут изменить результат запроса

Ответ 2

Существует небольшое различие между полем нулевого значения и документа без поля. Главное отличие состоит в том, что первый потребляет небольшое дисковое пространство, в то время как последнее не потребляет вообще. Их можно отличить с помощью оператора $exists.

Поле с пустой строкой сильно отличается от них. Хотя это зависит от цели, я не рекомендую использовать его в качестве замены для null. Если быть точным, их следует использовать для обозначения разных вещей. Например, подумайте о голосовании. Человек, который проводит пустую избирательную кампанию, отличается от человека, которому не разрешалось голосовать. Предыдущее голосование является пустой строкой, а последний - null.

Уже есть аналогичный вопрос здесь.