Comment créer un index conditionnel dans MySQL?

24

Comment créer un index pour filtrer une plage ou un sous-ensemble spécifique de la table dans MySQL? AFAIK c'est impossible de créer directement mais je pense qu'il est possible de simuler cette fonctionnalité.

Exemple: je veux créer un index pour la NAMEcolonne uniquement pour les lignes avecSTATUS = 'ACTIVE'

Cette fonctionnalité serait appelée un index filtré dans SQL Server et un index partiel dans Postgres.

Maniero
la source

Réponses:

9

MySQL ne prend actuellement pas en charge les index conditionnels.

Pour réaliser ce que vous demandez (pas que vous devriez le faire;)), vous pouvez commencer à créer une table auxiliaire:

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`)
);

Ensuite, vous ajoutez trois déclencheurs dans le tableau 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 ;

Nous avons besoin delimiter //parce que nous voulons utiliser ;à l'intérieur des déclencheurs.

De cette façon, la table auxiliaire contiendra exactement les ID correspondant aux lignes de la table principale qui contiennent la chaîne "ACTIVE", mises à jour par les déclencheurs.

Pour l'utiliser sur un select, vous pouvez utiliser l'habituel 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 table principale contient déjà des données, ou si vous effectuez une opération externe qui modifie les données de manière inhabituelle (EG: en dehors de MySQL), vous pouvez corriger la table auxiliaire avec ceci:

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

À propos des performances, vous aurez probablement des insertions, des mises à jour et des suppressions plus lentes. Cela n'a de sens que si vous vous occupez vraiment de quelques cas où la condition souhaitée est positive. Même de cette façon, en testant uniquement, vous pouvez voir si l'espace économisé justifie vraiment cette approche (et si vous économisez vraiment de l'espace).

Bacco
la source
7

Si je comprends bien la question, je pense que ce que vous essayez de faire est de créer un index sur les deux colonnes, NAME et STATUS. Cela vous permettrait efficacement d'interroger où NAME = 'SMITH' et STATUS = 'ACTIVE'

Glace noir
la source
1
D'accord, mais ce n'est pas efficace en termes d'espace si vous avez relativement peu de lignes avec le statut ACTIVE.
Maniero
Non, ce n'est pas le cas, mais ce n'était pas une exigence dans la question, et il n'a pas été dit que le tableau était fortement pondéré par l'une des valeurs. Pour cela, je créerais une vue matérialisée du STATUT que vous recherchez, mais MySQL ne les prend pas en charge.
BlackICE
et l'espace disque est bon marché ...
BlackICE
2
Oui, ce n'est pas une exigence directe, j'ai donc commencé le commentaire avec un OK. Je recherche des alternatives professionnelles. Et des alternatives professionnelles toujours à la recherche de la manière la plus efficace de faire vos tâches. Votre réponse est probablement la plus évidente. Aucun problème avec cela. Mais je suis totalement en désaccord avec "l'espace disque est bon marché", non pas parce qu'il est cher, bien sûr pas cher, mais la mémoire n'est pas si bon marché, la mémoire a de faibles limites et l'index doit vivre principalement sur la mémoire pour être efficace. L'accès au disque n'est pas si bon marché.Votre réponse est certainement une bonne façon d'atteindre l'objectif, mais je doute que ce soit le meilleur.
Maniero
Je ne serais pas d'accord non plus sur la mémoire, elle est assez bon marché ces jours-ci également (certainement pas aussi bon marché que l'espace disque, mais à 10 $ / gig pour certains, je dirais que vous pouvez faire des folies un peu :)
BlackICE
6

Vous ne pouvez pas faire d'indexation conditionnelle, mais pour votre exemple, vous pouvez ajouter un index multi-colonnes sur ( name, status).

Même s'il indexera toutes les données de ces colonnes, il vous aidera quand même à trouver les noms que vous recherchez avec le statut "actif".

Jonathan
la source
4

Vous pouvez le faire en divisant les données entre deux tables, en utilisant des vues pour unir les deux tables lorsque toutes les données sont nécessaires et en indexant une seule des tables de cette colonne - mais je pense que cela entraînerait des problèmes de performances pour les requêtes qui doivent parcourir toute la table à moins que le planificateur de requêtes ne soit plus intelligent que je ne le pense. Essentiellement, vous devez partitionner manuellement la table (et appliquer l'index à une seule des partitions).

Malheureusement, la fonction de partitionnement de table intégrée ne vous aidera pas dans votre quête car vous ne pouvez pas appliquer d'index à une seule partition.

Vous pouvez conserver une colonne supplémentaire avec un index et n’avoir une valeur dans cette colonne que lorsque la condition sur laquelle vous souhaitez que l’index soit basé est vraie, mais cela est susceptible de nécessiter beaucoup de travail et une valeur limitée (ou négative) en termes de efficacité des requêtes et économie d'espace.

David Spillett
la source
Je n'aurais PAS deux tables juste pour avoir une meilleure indexation, car la jointure sera toujours chère n'est-ce pas?
jcolebrand
@jcolebrand: cela coûterait plus cher pour les requêtes générales (sur les vues faisant une union), vous devez sélectionner spécifiquement dans la table de partition pour utiliser l'index. Le partitionnement intégré le ferait pour vous efficacement, mais uniquement comme Bigown le souhaite (pour économiser de l'espace) s'il prend en charge des index spécifiques à la partition. J'ai dit qu'il pouvait le faire, pas qu'il le voudrait!
David Spillett
0

MySQL a maintenant des colonnes virtuelles, qui peuvent être utilisées pour les index.

druud62
la source
3
Comment cette fonctionnalité peut-elle être utilisée pour simuler un index filtré?
ypercubeᵀᴹ
1
@ yper-trollᵀᴹ, druud62 pourrait penser à Oracle: dbfiddle.uk/… - MySQL ne semble pas traiter les NULL de la même façon: dbfiddle.uk/…
Jack Douglas
@JackDouglas peut-être. (n'est-ce pas juste une optimisation d'index qui économise de l'espace au fait? En d'autres termes, pourrait select count(*) from foo where id is null ;utiliser un index?)
ypercubeᵀᴹ
@ yper-trollᵀᴹ Oracle n'indexe pas les lignes où toutes les colonnes indexées sont NULL ( use-the-index-luke.com/sql/where-clause/null/index ) - et une colonne virtuelle peut être activéedecode(status,'ACTIVE',name,null) par exemple.
Jack Douglas
Thnx, je pensais que cela avait changé dans les versions récentes (et les valeurs nulles étaient indexées).
ypercubeᵀᴹ