Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a partial index when field is not null

I am trying to create a partial index on a field, but only when the field is not null. In other words, I want to be able to have many of the documents be able to store null, but for documents that actually have a value in the field, I want that value to be unique. Here is the code I tried to use

db.account.createIndex({ "email": 1 }, { unique: true, partialFilterExpression: { "email": { $ne: null } }})

From what I thought I understood, this should index the email field when it is not equal to null.

But it gives the error:

"unsupported expression in partial index: $not email $eq null"

I tried to change $ne: null to $exists: true, but there are already multiple documents with null value for the email field, so it also threw an error. My schema has an email field for the document, so each document will automatically have an email field, meaning $exists: true won't work anyways.

Any help on being able to index a field only when it is not null would be great.

like image 518
sme Avatar asked Sep 25 '20 15:09

sme


People also ask

How do partial indexes work?

A partial index is an index built over a subset of a table; the subset is defined by a conditional expression (called the predicate of the partial index). The index contains entries only for those table rows that satisfy the predicate.

What are partial indexes in MongoDB?

Partial indexes only index the documents in a collection that meet a specified filter expression. By indexing a subset of the documents in a collection, partial indexes have lower storage requirements and reduced performance costs for index creation and maintenance.

Can MongoDB use part of a compound index?

MongoDB can use the intersection of indexes to fulfill queries. For queries that specify compound query conditions, if one index can fulfill a part of a query condition, and another index can fulfill another part of the query condition, then MongoDB can use the intersection of the two indexes to fulfill the query.

Does MySQL support partial index?

MySQL as of version 8.0 does not support partial indexes. In MySQL, the term "partial index" is sometimes used to refer to prefix indexes, where only a truncated prefix of each value is stored in the index. This is another technique for reducing index size.


2 Answers

The $ne is not supported expression operator,

As per MongoDB Partial Index supported expressions are: 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,

You can use $type to check is string like this { "email": { $type: "string" } }, because null type is "null".

NOTE:

As noted in the query coverage documentation for partial indexes:


Since MongoDB will not use the partial index for a query or sort operation if using the index results in an incomplete result set.

To use the partial index, a query must contain the filter expression (or a modified filter expression that specifies a subset of the filter expression) as part of its query condition.

EX. to use partial index for $type filter you have to use below filter:

{ "email": { "$eq": "[email protected]", "$type": "string" } }
// or
{ $and: [{ "email": "[email protected]" }, { "email": { $type: "string" } }] }

Playground

like image 149
turivishal Avatar answered Nov 11 '22 09:11

turivishal


Based on inputs from @turivishal, updating here with actual code in the mongo shell.

> db.version()
4.2.6
> db.test2.createIndex({email:1},{unique:true, partialFilterExpression:{email:{$type:"string"}}});
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 1,
        "numIndexesAfter" : 2,
        "ok" : 1
}
> db.test2.getIndexes();
[
        {
                "v" : 2,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_",
                "ns" : "test.test2"
        },
        {
                "v" : 2,
                "unique" : true,
                "key" : {
                        "email" : 1
                },
                "name" : "email_1",
                "ns" : "test.test2",
                "partialFilterExpression" : {
                        "email" : {
                                "$type" : "string"
                        }
                }
        }
]
//note, collection data exists with type "null"/empty values for email for 
//multiple names eg. srini, srini2,srini3 below
> db.test2.find();
{ "_id" : ObjectId("5f6e1c7087ded5e7627c7a19"), "name" : "john", "email" : "[email protected]" }
{ "_id" : ObjectId("5f6e1c7087ded5e7627c7a1a"), "name" : "mary", "email" : "[email protected]" }
{ "_id" : ObjectId("5f6e1c7087ded5e7627c7a1c"), "name" : "srini" }
{ "_id" : ObjectId("5f6e1c7087ded5e7627c7a1d"), "name" : "kate", "email" : "[email protected]" }
{ "_id" : ObjectId("5f6e2a2dfbbd49e82b415126"), "name" : "srini2" }
{ "_id" : ObjectId("5f6e2a39fbbd49e82b415127"), "name" : "srini3" }
//write error for duplicate value of email
> db.test2.insertOne({name: "mary1",email:"[email protected]"});
2020-09-26T00:01:44.941+0530 E  QUERY    [js] WriteError({
        "index" : 0,
        "code" : 11000,
        "errmsg" : "E11000 duplicate key error collection: test.test2 index: email_1 dup key: { email: \"[email protected]\" }",
        "op" : {
                "_id" : ObjectId("5f6e3790fbbd49e82b415128"),
                "name" : "mary1",
                "email" : "[email protected]"
        }
}) :
like image 25
Mallik Avatar answered Nov 11 '22 07:11

Mallik