¿Cómo crear un índice condicional en MySQL?

24

¿Cómo crear un índice para filtrar un rango o subconjunto específico de la tabla en MySQL? AFAIK es imposible de crear directamente, pero creo que es posible simular esta característica.

Ejemplo: quiero crear un índice para la NAMEcolumna solo para las filas conSTATUS = 'ACTIVE'

Esta funcionalidad se llamaría un índice filtrado en SQL Server y un índice parcial en Postgres.

Maniero
fuente

Respuestas:

9

MySQL actualmente no admite índices condicionales.

Para lograr lo que está pidiendo (no es que deba hacerlo;)) puede comenzar a crear una tabla auxiliar:

CREATE TABLE  `my_schema`.`auxiliary_table` (
   `id` int unsigned NOT NULL,
   `name` varchar(250), /* specify the same way as in your main table */
   PRIMARY KEY (`id`),
   KEY `name` (`name`)
);

Luego agrega tres disparadores en la tabla principal:

delimiter //

CREATE TRIGGER example_insert AFTER INSERT ON main_table
FOR EACH ROW
BEGIN
   IF NEW.status = 'ACTIVE' THEN
      REPLACE auxiliary_table SET
         auxiliary_table.id = NEW.id,
         auxiliary_table.name = NEW.name;
   END IF;
END;//

CREATE TRIGGER example_update AFTER UPDATE ON main_table
FOR EACH ROW
BEGIN
   IF NEW.status = 'ACTIVE' THEN
      REPLACE auxiliary_table SET
         auxiliary_table.id = NEW.id,
         auxiliary_table.name = NEW.name;
   ELSE
      DELETE FROM auxiliary_table WHERE auxiliary_table.id = OLD.id;
   END IF;
END;//

CREATE TRIGGER example_delete AFTER DELETE ON main_table
FOR EACH ROW
BEGIN
   DELETE FROM auxiliary_table WHERE auxiliary_table.id = OLD.id;
END;//

delimiter ;

Necesitamos delimiter //porque queremos usar ;dentro de los disparadores.

De esa manera, la tabla auxiliar contendrá exactamente los ID correspondientes a las filas de la tabla principal que contienen la cadena "ACTIVE", que se actualizarán mediante los desencadenantes.

Para usar eso en un select, puede usar lo habitual join:

SELECT main_table.* FROM auxiliary_table LEFT JOIN main_table
   ON auxiliary_table.id = main_table.id
   ORDER BY auxiliary_table.name;

Si la tabla principal ya contiene datos, o si realiza alguna operación externa que cambia los datos de una manera inusual (por ejemplo, fuera de MySQL), puede arreglar la tabla auxiliar con esto:

INSERT INTO auxiliary_table SET
   id = main_table.id,
   name = main_table.name,
   WHERE main_table.status="ACTIVE";

Sobre el rendimiento, probablemente tendrá inserciones, actualizaciones y eliminaciones más lentas. Esto puede tener sentido solo si realmente maneja pocos casos en los que la condición deseada es positiva. Incluso de esa manera, probablemente solo probando puede ver si el espacio ahorrado realmente justifica este enfoque (y si realmente está ahorrando espacio).

Bacco
fuente
7

Si entiendo la pregunta correctamente, creo que lo que lograría lo que está tratando de hacer es crear un índice en ambas columnas, NOMBRE y ESTADO. Eso le permitiría eficientemente consultar dónde NAME = 'SMITH' y STATUS = 'ACTIVE'

BlackICE
fuente
1
Ok, pero esto no es espacio eficiente si tiene relativamente pocas líneas con estado ACTIVO.
Maniero
No, no lo es, pero ese no era un requisito en la pregunta, y no se afirmó que la tabla estaba muy ponderada a uno de los valores. Para eso, crearía una vista materializada del ESTADO que está buscando, pero MySQL no los admite.
BlackICE
y el espacio en disco es barato ...
BlackICE
2
Sí, no es un requisito directo, así que comencé el comentario con un OK. Estoy buscando algunas alternativas profesionales. Y alternativas profesionales que siempre buscan la forma más eficiente de realizar sus tareas. Su respuesta probablemente sea la más obvia. Ningún problema con eso. Pero estoy totalmente en desacuerdo con "el espacio en disco es barato", no porque sea caro, por supuesto que es barato, sin embargo, la memoria no es tan barata, la memoria tiene límites bajos y el índice debe vivir principalmente en la memoria para ser eficiente. El acceso al disco no es tan barato. Su respuesta ciertamente es una forma correcta de lograr el objetivo, pero dudo que sea la mejor.
Maniero
No estaría de acuerdo en la memoria también, es bastante barato en estos días también (ciertamente no es tan barato como el espacio en disco, pero a $ 10 por concierto, diría que puede derrochar un poco :)
BlackICE
6

No puede hacer indexación condicional, pero para su ejemplo, puede agregar un índice de varias columnas en ( name, status).

Aunque indexará todos los datos en esas columnas, aún lo ayudará a encontrar los nombres que está buscando con el estado "activo".

Jonathan
fuente
4

Puede hacerlo dividiendo los datos entre dos tablas, utilizando vistas para unir las dos tablas cuando se necesitan todos los datos e indexando solo una de las tablas en esa columna, pero creo que esto causaría problemas de rendimiento para las consultas que necesitan recorra toda la tabla a menos que el planificador de consultas sea más inteligente de lo que yo le atribuyo. Esencialmente, estaría particionando manualmente la tabla (y aplicando el índice a solo una de las particiones).

Desafortunadamente, la función de particionamiento de tabla incorporada no lo ayudará en su búsqueda, ya que no puede aplicar un índice a una sola partición.

Podría mantener una columna adicional con un índice y solo tener un valor en esa columna cuando la condición en la que desea que se base el índice sea verdadera, pero es probable que esto requiera mucho trabajo y un valor limitado (o negativo) en términos de consulta de eficiencia y ahorro de espacio.

David Spillett
fuente
NO tendría dos tablas solo para tener una mejor indexación, ya que la unión seguirá siendo costosa, ¿no?
jcolebrand
@jcolebrand: sería más costoso para consultas generales (sobre las vistas que hacen una unión), necesitaría seleccionar específicamente de la tabla de particiones para usar el índice. La partición incorporada lo haría de manera eficiente, pero solo de la manera que Bigown quiere (para ahorrar espacio) si admitiera índices específicos de partición. ¡Dije que podía hacerlo, no que quisiera!
David Spillett
0

MySQL ahora tiene columnas virtuales, que pueden usarse para índices.

druud62
fuente
3
¿Cómo se puede usar esta función para simular un índice filtrado?
ypercubeᵀᴹ
1
@ yper-trollᵀᴹ, druud62 podría estar pensando en Oracle: dbfiddle.uk/… - No se ha visto que MySQL trate los NULL de la misma manera: dbfiddle.uk/…
Jack Douglas
@JackDouglas quizás. (no es tan sólo una optimización de índice que ahorra espacio en el camino de otro modo podría? select count(*) from foo where id is null ;utilizar un índice?)
ypercubeᵀᴹ
@ yper-trollᵀᴹ Oracle no indexa las filas donde todas las columnas indexadas son NULL ( use-the-index-luke.com/sql/where-clause/null/index ), y podría haber una columna virtual activada,decode(status,'ACTIVE',name,null) por ejemplo.
Jack Douglas
Gracias, pensé que eso había cambiado en versiones recientes (y los nulos estaban indexados).
ypercubeᵀᴹ