22 - Indices - simples y compuestos

Indices simples

Un índice es simple cuando se hace por un único campo del documento, debemos utilizar el método 'createIndex' e indicar el campo por el cual queremos generar el archivo índice.

use base1
db.libros.drop()

db.libros.insertOne(
  {
    _id: 1,  
    titulo: 'El aleph',
    autor: 'Borges',
    editorial: ['Siglo XXI','Planeta'],
    precio: 20,
    cantidad: 50 
  }
)
db.libros.insertOne(
  {
    _id: 2,  
    titulo: 'Martin Fierro',
    autor: 'Jose Hernandez',
    editorial: ['Siglo XXI'],
    precio: 50,
    cantidad: 12
  }
)
db.libros.insertOne(
  {
    _id: 3,  
    titulo: 'Aprenda PHP',
    autor: 'Mario Molina',
    editorial: ['Siglo XXI','Planeta'],
    precio: 50,
    cantidad: 20
  }
)
db.libros.insertOne(
  {
    _id: 4,  
    titulo: 'Java en 10 minutos',
    editorial: ['Siglo XXI'],
    precio: 45,
    cantidad: 1 
  }
)

db.libros.createIndex( {titulo : 1} )

db.libros.find({},{ titulo:1 }).sort({ titulo:1 }).pretty()

Cuando ejecutamos el bloque anterior al crearse el índices nos informa de los índices anteriores que tenía la colección y la nueva cantidad de índices:

MongoDB creación de índices

Para indexar en orden inverso cuando se crea el índice debemos especificar un -1:

db.libros.createIndex( {titulo : -1} )

Los datos quedan ordenados de la 'z' a la 'a'.

Indices compuestos

Un índice es compuesto cuando se hace por dos o más campos del documento, debemos utilizar también el método 'createIndex' e indicar los campos por los cuales generar el archivo índice.

use base1
db.libros.drop()

db.libros.insertOne(
  {
    _id: 1,  
    titulo: 'El aleph',
    autor: 'Borges',
    editorial: ['Siglo XXI','Planeta'],
    precio: 20,
    cantidad: 50 
  }
)
db.libros.insertOne(
  {
    _id: 2,  
    titulo: 'Martin Fierro',
    autor: 'Jose Hernandez',
    editorial: ['Siglo XXI'],
    precio: 50,
    cantidad: 12
  }
)
db.libros.insertOne(
  {
    _id: 3,  
    titulo: 'Aprenda PHP',
    autor: 'Mario Molina',
    editorial: ['Siglo XXI','Planeta'],
    precio: 50,
    cantidad: 20
  }
)
db.libros.insertOne(
  {
    _id: 4,  
    titulo: 'Java en 10 minutos',
    editorial: ['Siglo XXI'],
    precio: 45,
    cantidad: 1 
  }
)

db.libros.createIndex( {titulo : 1, autor : 1} )

db.libros.find({titulo:'Aprenda PHP',autor:'Mario Molina'}).pretty()

Hemos creado un índice en la colección libros por los campos titulo y autor:

db.libros.createIndex( {titulo : 1, autor : 1} )

Luego si la colección almacena millones de documentos una consulta por los campos título y autor es casi instantánea:

db.libros.find({titulo:'Aprenda PHP',autor:'Mario Molina'}).pretty()

Acceso solo a los datos de los índices.

Para maximizar la eficiencia de una consulta podemos filtrar por campos que se encuentran indexados y recuperar datos directamente del índice. En estos casos MongoDB no debe acceder a los documentos de la colección, solo debe acceder a los datos que se encuentran en el archivo índice.

use base1
db.articulos.drop()

db.articulos.insertOne(
  {
    _id: 1,  
    nombre: 'MULTIFUNCION HP DESKJET 2675',
    rubro: 'impresora',
    precio: 3000,
    stock: 20 
  }
)
db.articulos.insertOne(
  {
    _id: 2,  
    nombre: 'MULTIFUNCION EPSON EXPRESSION XP241',
    rubro: 'impresora',
    precio: 3700,
    stock: 5 
  }
)
db.articulos.insertOne(
  {
    _id: 3,  
    nombre: 'LED 19 PHILIPS',
    rubro: 'monitor',
    precio: 4500,
    stock: 2
  }
)
db.articulos.insertOne(
  {
    _id: 4,  
    nombre: 'LED 22 PHILIPS',
    rubro: 'monitor',
    precio: 5700,
    stock: 4
  }
)
db.articulos.insertOne(
  {
    _id: 5,  
    nombre: 'LED 27 PHILIPS',
    rubro: 'monitor',
    precio: 12000,
    stock: 1
  }
)

db.articulos.insertOne(
  {
    _id: 6,  
    nombre: 'LOGITECH M90',
    rubro: 'mouse',
    precio: 300,
    stock: 4
  }
)

db.articulos.createIndex( {rubro : 1, _id : 1} )

db.articulos.find({rubro:'monitor'},{rubro:1, _id:1})

Conocer estadísticas de la consulta.

Si queremos conocer información sobre los requerimientos reales de una consulta podemos utilizar el método explain:

db.articulos.find({rubro:'monitor'},{rubro:1, _id:1}).explain('executionStats')

Nos retorna muchos datos que pueden ser útiles para planificar que estructuras de índices definir:

> db.articulos.find({rubro:'monitor'},{rubro:1, _id:1}).explain('executionStats')
{
        "queryPlanner" : {
                "plannerVersion" : 1,
                "namespace" : "base1.articulos",
                "indexFilterSet" : false,
                "parsedQuery" : {
                        "rubro" : {
                                "$eq" : "monitor"
                        }
                },
                "winningPlan" : {
                        "stage" : "PROJECTION",
                        "transformBy" : {
                                "rubro" : 1,
                                "_id" : 1
                        },
                        "inputStage" : {
                                "stage" : "IXSCAN",
                                "keyPattern" : {
                                        "rubro" : 1,
                                        "_id" : 1
                                },
                                "indexName" : "rubro_1__id_1",
                                "isMultiKey" : false,
                                "multiKeyPaths" : {
                                        "rubro" : [ ],
                                        "_id" : [ ]
                                },
                                "isUnique" : false,
                                "isSparse" : false,
                                "isPartial" : false,
                                "indexVersion" : 2,
                                "direction" : "forward",
                                "indexBounds" : {
                                        "rubro" : [
                                                "[\"monitor\", \"monitor\"]"
                                        ],
                                        "_id" : [
                                                "[MinKey, MaxKey]"
                                        ]
                                }
                        }
                },
                "rejectedPlans" : [ ]
        },
        "executionStats" : {
                "executionSuccess" : true,
                "nReturned" : 3,
                "executionTimeMillis" : 0,
                "totalKeysExamined" : 3,
                "totalDocsExamined" : 0,
                "executionStages" : {
                        "stage" : "PROJECTION",
                        "nReturned" : 3,
                        "executionTimeMillisEstimate" : 0,
                        "works" : 4,
                        "advanced" : 3,
                        "needTime" : 0,
                        "needYield" : 0,
                        "saveState" : 0,
                        "restoreState" : 0,
                        "isEOF" : 1,
                        "invalidates" : 0,
                        "transformBy" : {
                                "rubro" : 1,
                                "_id" : 1
                        },
                        "inputStage" : {
                                "stage" : "IXSCAN",
                                "nReturned" : 3,
                                "executionTimeMillisEstimate" : 0,
                                "works" : 4,
                                "advanced" : 3,
                                "needTime" : 0,
                                "needYield" : 0,
                                "saveState" : 0,
                                "restoreState" : 0,
                                "isEOF" : 1,
                                "invalidates" : 0,
                                "keyPattern" : {
                                        "rubro" : 1,
                                        "_id" : 1
                                },
                                "indexName" : "rubro_1__id_1",
                                "isMultiKey" : false,
                                "multiKeyPaths" : {
                                        "rubro" : [ ],
                                        "_id" : [ ]
                                },
                                "isUnique" : false,
                                "isSparse" : false,
                                "isPartial" : false,
                                "indexVersion" : 2,
                                "direction" : "forward",
                                "indexBounds" : {
                                        "rubro" : [
                                                "[\"monitor\", \"monitor\"]"
                                        ],
                                        "_id" : [
                                                "[MinKey, MaxKey]"
                                        ]
                                },
                                "keysExamined" : 3,
                                "seeks" : 1,
                                "dupsTested" : 0,
                                "dupsDropped" : 0,
                                "seenInvalidated" : 0
                        }
                }
        },
        "serverInfo" : {
                "host" : "diego-PC",
                "port" : 27017,
                "version" : "4.0.5",
                "gitVersion" : "3739429dd92b92d1b0ab120911a23d50bf03c412"
        },
        "ok" : 1
}

Entre los datos podemos encontrar:

"executionStats" : {
                "executionSuccess" : true,
                "nReturned" : 3,
                "executionTimeMillis" : 0,
                "totalKeysExamined" : 3,
                "totalDocsExamined" : 0,

Podemos ver que en el campo "totalDocsExamined" almacena un cero, esto quiere decir que no se consultaron documentos, sino directamente los datos almacenados en los índices (esto aumenta mucho la eficiencia de la consulta cuando tenemos millones de documentos).

Si cambiamos la consulta y recuperamos también el precio:

db.articulos.find({rubro:'monitor'},{rubro:1, _id:1, precio:1}).explain('executionStats')

Luego podemos comprobar que si se accedieron a los documentos a parte del archivo índice:

"executionStats" : {
                "executionSuccess" : true,
                "nReturned" : 3,
                "executionTimeMillis" : 0,
                "totalKeysExamined" : 3,
                "totalDocsExamined" : 3,

Indices únicos

Podemos asegurarnos que un documento tiene valores distintos para uno o varios campos definiendo un índice único. La sintaxis para la definición de un índice único:

use base1

db.clientes.drop()

db.clientes.insertOne(
  {
    _id: 1,  
    nombre: 'Perez Ana',
    dni: '20439455',
    domicilio: 'San Martin 222',
    provincia: 'Santa Fe'
  }
)

db.clientes.insertOne(
  {
    _id: 2,  
    nombre: 'Garcia Juan',
    dni: '21495834',
    domicilio: 'Rivadavia 333',
    provincia: 'Buenos Aires'
  }
)



db.clientes.insertOne(
  {
    _id: 3,  
    nombre: 'Perez Luis',
    dni: '20888722',
    domicilio: 'Sarmiento 444',
    provincia: 'Buenos Aires'
  }
)

db.clientes.createIndex({dni:1},{unique:true})

db.clientes.find()

db.clientes.insertOne(
  {
    _id: 4,  
    nombre: 'Peña Lucas',
    dni: '20888722',
    domicilio: 'General Paz 323',
    provincia: 'Buenos Aires'
  }
)

Para crear un índice único debemos pasar un segundo parámetro al método 'createIndex':

db.clientes.createIndex({dni:1},{unique:true})

Luego si intentamos ingresar un documento en la colección 'clientes' con un 'dni' repetido, MongoDB no lo permite:

MongoDB creación de índices únicos