tl;dr
Yes, it is possible to have multiple documents with a field set to null
or not defined, while enforcing unique "actual" values.
requirements:
- MongoDB v3.2+.
- Knowing your concrete value type(s) in advance (e.g, always a
string
or object
when not null
).
If you're not interested in the details, feel free to skip to the implementation
section.
longer version
To supplement @Nolan's answer, starting with MongoDB v3.2 you can use a partial unique index with a filter expression.
The partial filter expression has limitations. It can only include the following:
- equality expressions (i.e. field: value or using the
$eq
operator),
$exists: true
expression,
$gt
, $gte
, $lt
, $lte
expressions,
$type
expressions,
$and
operator at the top-level only
This means that the trivial expression {"yourField"{$ne: null}}
cannot be used.
However, assuming that your field always uses the same type, you can use a $type
expression.
{ field: { $type: <BSON type number> | <String alias> } }
MongoDB v3.6 added support for specifying multiple possible types, which can be passed as an array:
{ field: { $type: [ <BSON type1> , <BSON type2>, ... ] } }
which means that it allows the value to be of any of a number of multiple types when not null
.
Therefore, if we want to allow the email
field in the example below to accept either string
or, say, binary data
values, an appropriate $type
expression would be:
{email: {$type: ["string", "binData"]}}
implementation
mongoose
You can specify it in a mongoose schema:
const UsersSchema = new Schema({
name: {type: String, trim: true, index: true, required: true},
email: {
type: String, trim: true, index: {
unique: true,
partialFilterExpression: {email: {$type: "string"}}
}
}
});
or directly add it to the collection (which uses the native node.js driver):
User.collection.createIndex("email", {
unique: true,
partialFilterExpression: {
"email": {
$type: "string"
}
}
});
native mongodb driver
using collection.createIndex
db.collection('users').createIndex({
"email": 1
}, {
unique: true,
partialFilterExpression: {
"email": {
$type: "string"
}
}
},
function (err, results) {
// ...
}
);
mongodb shell
using db.collection.createIndex
:
db.users.createIndex({
"email": 1
}, {
unique: true,
partialFilterExpression: {
"email": {$type: "string"}
}
})
This will allow inserting multiple records with a null
email, or without an email field at all, but not with the same email string.