Fecha publicación: 12 jul 2021

MongoDb índices: Text Index II

Los índices de búsquedas de texto son muy potentes, pero hay que conocer ciertos detalles de su funcionamiento, o puede que no nos arrojen los resultados que esperamos.

En este vídeo vamos a ver como obtener pesos de resultados de búsqueda, como excluir palabras, y como manejarnos en lenguaje castellano.

Manos a la obra

Vamos a crear una base de datos nueva que la llamaremos clinica, en ella tendremos una colección que se llamará consultas, esta colección tendrá un campo diagnóstico que será de texto libre, y es donde el médico introduce el diagnóstico del paciente (ésto es sólo para practicar, lo ideal sería normalizar dicha información en la base de datos).

Creamos la base de datos e insertamos unos datos de prueba:

use clinica
db.consultas.insertMany([
  {
    nombre: "Juan Perez",
    especialidad: "general",
    diagnostico: "Dolor abdominal, Fiebre alta, tos, posible caso de COVID",
  },
  {
    nombre: "María Pelaez",
    especialidad: "general",
    diagnostico: "Tensión alta, posible episodio de ataque de ansiedad",
  },
  {
    nombre: "Javier Garcia",
    especialidad: "cardiología",
    diagnostico: "Arritmias, acompañado de tensión alta",
  },
  {
    nombre: "Manuel Gómez",
    especialidad: "general",
    diagnostico: "Fiebre alta, tos y mucosidades",
  },
]);

Vamos a crear un índice para el campo diagnóstico, como está en castellano, vamos a índicarselo en el createIndex, de esta manera, nos aseguramos que va a tratar bien los campos con tilde, caracteres especiales, identificar palabras que debe ignorar en una búsqueda como: a, de, con, ante, y...).

db.consultas.createIndex(
  { diagnostico: "text" },
  { default_language: "spanish" }
);

Ahora podemos buscar tensión con o sin tilde y obtenemos resultados:

db.consultas.find({ $text: { $search: "tensión" } });
db.consultas.find({ $text: { $search: "tension" } });

Cabe mencionar que en el caso de que puedas tener campos con multiples idiomas, mongoDb te ofrece la opción language override

Otro tema muy interesante es evaluar el tipo de resultado que nos da esta búsqueda: lo que hace este $text $search es buscar por palabras, es decir si buscamos tensión alta nos podemos encontrar una sorpresa

db.consultas.find(
  { $text: { $search: "tension alta" } },
  { nombre: 1, diagnostico: 1 }
);

resultados

[
  {
    _id: ObjectId("60fd4c5ce8171ee4f3ad7680"),
    nombre: "Juan Perez",
    diagnostico: "Fiebre alta, tos y mucosidades",
  },
  {
    _id: ObjectId("60fd4c5ce8171ee4f3ad767f"),
    nombre: "Javier Garcia",
    diagnostico: "Arritmias, acompañado de tensión alta",
  },
  {
    _id: ObjectId("60fd4c5ce8171ee4f3ad767e"),
    nombre: "María Pelaez",
    diagnostico: "Tensión alta, posible episido de ataque de ansiedad",
  },
  {
    _id: ObjectId("60fd4c5ce8171ee4f3ad767d"),
    nombre: "Juan Perez",
    diagnostico:
      "Dolor abdominal, Fiebre alta, tos y falta de secrecciones nasales, posible caso de COVID",
  },
];

Resulta que también nos da como primer resultado fiebre alta ¿Comooor? bueno resulta que alta existe en esa entrada..., ok, aceptamos barco, pero yo quiero que aparezca primero tensión alta ¿Qué está pasando aquí? Que no le indicamos que ordene los resultados por relevancia, para hacer esto, _MongoDb le asigna a cada resultado de la consulta un peso, a más peso más palabras coinciden con lo que se está buscando, si ordenamos por relevancia podemos ver los resultados en el orden que esperamos, veamos como hacer ésto:

Primero sacamos los pesos de relevancia, añadimos a la proyección de resultados un campo que llamaremos score tiramos de los metadatos que nos da el índice de texto, en este caso el campo textScore.

db.consultas.find(
  { $text: { $search: "tension alta" } },
  { nombre: 1, diagnostico: 1, score: { $meta: "textScore" } }
);

Veamos los resultados:

[
  {
    _id: ObjectId("60fd8cc6e8171ee4f3ad7681"),
    nombre: "Juan Perez",
    diagnostico: "Dolor abdominal, Fiebre alta, tos, posible caso de COVID",
    score: 0.5625,
  },
  {
    _id: ObjectId("60fd8cc6e8171ee4f3ad7682"),
    nombre: "María Pelaez",
    diagnostico: "Tensión alta, posible episido de ataque de ansiedad",
    score: 1.1666666666666667,
  },
  {
    _id: ObjectId("60fd8cc6e8171ee4f3ad7684"),
    nombre: "Juan Perez",
    diagnostico: "Fiebre alta, tos y mucosidades",
    score: 0.625,
  },
  {
    _id: ObjectId("60fd8cc6e8171ee4f3ad7683"),
    nombre: "Javier Garcia",
    diagnostico: "Arritmias, acompañado de tensión alta",
    score: 1.25,
  },
];

Esto empieza a tener sentido, tensión alta tiene más peso que fiebre alta, ¿Y si ordenamos por ese campo?

db.consultas
  .find(
    { $text: { $search: "tensíon alta" } },
    { nombre: 1, diagnostico: 1, score: { $meta: "textScore" } }
  )
  .sort({ score: { $meta: "textScore" } });

Ahora si nos aparece arriba tension alta.

[
  {
    _id: ObjectId("60fd8cc6e8171ee4f3ad7683"),
    nombre: "Javier Garcia",
    diagnostico: "Arritmias, acompañado de tensión alta",
    score: 1.25,
  },
  {
    _id: ObjectId("60fd8cc6e8171ee4f3ad7682"),
    nombre: "María Pelaez",
    diagnostico: "Tensión alta, posible episido de ataque de ansiedad",
    score: 1.1666666666666667,
  },
  {
    _id: ObjectId("60fd8cc6e8171ee4f3ad7684"),
    nombre: "Juan Perez",
    diagnostico: "Fiebre alta, tos y mucosidades",
    score: 0.625,
  },
  {
    _id: ObjectId("60fd8cc6e8171ee4f3ad7681"),
    nombre: "Juan Perez",
    diagnostico: "Dolor abdominal, Fiebre alta, tos, posible caso de COVID",
    score: 0.5625,
  },
];

Aunque... si queremos buscar exactamente tensión alta ¿Por qué no indicarle que busque exactamente por ese substring?, para hacer esto rodeamos el string tensión alta entre comillas dobles.

db.consultas.find(
  { $text: { $search: '"tensión alta"' } },
  { nombre: 1, diagnostico: 1 }
);

Otra opción interesante que nos permite este tipo de búsquedas es la de omitir resultados que tengan ciertas palabras, ésto lo hacemos añadiendo como prefijo un menos a la palabra que queramos hacer que descarte el resultado, por ejemplo queremos buscar pacientes que hayan tenido fiebre, tos, pero no mucosidades, le añadimos un menos a mucosidades

db.consultas
  .find(
    { $text: { $search: "fiebre tos -mucosidades" } },
    { nombre: 1, diagnostico: 1, score: { $meta: "textScore" } }
  )
  .sort({ score: { $meta: "textScore" } });

Los índices text son una herramienta muy potente, pero hay que saber bien cuando usarlos ya que:

  • Tiene un coste generarlos.
  • Pueden llegar a ocupar bastante memoria y disco duro.
  • Pueden hacer que las escrituras sean más lentas.

¿Con ganas de aprender Backend?

En Lemoncode impartimos un Bootcamp Backend Online, centrado en stack node y stack .net, en él encontrarás todos los recursos necesarios: clases de los mejores profesionales del sector, tutorías en cuanto las necesites y ejercicios para desarrollar lo aprendido en los distintos módulos. Si quieres saber más puedes pinchar aquí para más información sobre este Bootcamp Backend.