Как создать условный индекс в MySQL?

24

Как создать индекс для фильтрации определенного диапазона или подмножества таблицы в MySQL? AFAIK невозможно создать напрямую, но я думаю, что можно смоделировать эту функцию.

Пример: я хочу создать индекс для NAMEстолбца только для строк сSTATUS = 'ACTIVE'

Эта функциональность будет называться фильтрованным индексом в SQL Server и частичным индексом в Postgres.

Maniero
источник

Ответы:

9

MySQL в настоящее время не поддерживает условные индексы.

Чтобы достичь того, что вы просите (не то, что вы должны это делать;)) вы можете начать создавать вспомогательную таблицу:

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

Затем вы добавляете три триггера в основную таблицу:

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 ;

Нам нужно, delimiter //потому что мы хотим использовать ;внутри триггеров.

Таким образом, вспомогательная таблица будет содержать в точности идентификаторы, соответствующие строкам основной таблицы, которые содержат строку «ACTIVE», обновляемую триггерами.

Чтобы использовать это на select, вы можете использовать обычные join:

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

Если основная таблица уже содержит данные или если вы выполняете какую-либо внешнюю операцию, которая изменяет данные необычным способом (например, вне MySQL), вы можете исправить вспомогательную таблицу следующим образом:

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

Что касается производительности, вероятно, у вас будут медленные вставки, обновления и удаления. Это может иметь некоторый смысл, только если вы действительно имеете дело с несколькими случаями, когда желаемое условие является положительным. Даже в этом случае, возможно, только тестирование, которое вы можете увидеть, действительно ли сэкономленное пространство оправдывает этот подход (и действительно ли вы экономите какое-либо место вообще).

Bacco
источник
7

Если я правильно понимаю вопрос, я думаю, что то, что вы пытаетесь сделать, это создать индекс по обоим столбцам, NAME и STATUS. Это эффективно позволит вам запросить, где NAME = 'SMITH' и STATUS = 'ACTIVE'

Черный лед
источник
1
Хорошо, но это неэффективно, если у вас относительно мало строк со статусом ACTIVE.
Маньеро
Нет, это не так, но это не было требованием в вопросе, и не было указано, что таблица была сильно взвешена для одного из значений. Для этого я хотел бы создать материализованное представление STATUS, которое вы ищете, но MySQL не поддерживает их.
BlackICE
и дисковое пространство дешево ...
BlackICE
2
Да, это не прямое требование, поэтому я начал комментарий с ОК. Я ищу несколько профессиональных альтернатив. А профессиональные альтернативы всегда ищут наиболее эффективный способ решения ваших задач. Ваш ответ, вероятно, самый очевидный. Нет проблем с этим. Но я совершенно не согласен с тем, что «дисковое пространство дешево», не потому, что оно дорогое, конечно, дешево, но память не такая дешевая, у памяти низкие пределы, и индекс должен жить в первую очередь на оперативной памяти. Доступ к диску не так уж и дешев. Ваш ответ, безусловно, является одним из правильных способов достижения цели, но я сомневаюсь, что он лучший.
Маньеро
Я бы тоже не согласился с памятью, в наши дни она тоже довольно дешевая (конечно, не такая дешевая, как дисковое пространство, но по цене 10 долларов за гигабайт, я бы сказал, что вы можете немного
разориться
6

Вы не можете выполнять условную индексацию, но для вашего примера вы можете добавить многостолбцовый индекс в ( name, status).

Несмотря на то, что он будет индексировать все данные в этих столбцах, он все равно поможет вам найти искомые имена со статусом «активный».

Джонатан
источник
4

Вы могли бы сделать это, разделив данные между двумя таблицами, используя представления для объединения двух таблиц, когда нужны все данные, и индексируя только одну из таблиц в этом столбце - но я думаю, что это вызовет проблемы с производительностью для запросов, которые должны бегите по всей таблице, если планировщик запросов не является более умным, чем я считаю. По сути, вы бы вручную разбили таблицу (и применили индекс только к одному из разделов).

К сожалению, встроенная функция разделения таблиц не поможет вам в ваших поисках, так как вы не можете применить индекс к одному разделу.

Вы могли бы поддерживать дополнительный столбец с индексом и иметь значение в этом столбце только в том случае, если условие, на котором вы хотите, чтобы индекс основывался, является истинным, но это, вероятно, будет трудоемким и ограниченным (или отрицательным) значением с точки зрения эффективность запросов и экономия места.

Дэвид Спиллетт
источник
У меня НЕ было бы двух таблиц только для лучшей индексации, так как объединение все равно будет дорогим, не так ли?
Jcolebrand
@jcolebrand: для общих запросов это будет дороже (для представлений, выполняющих объединение), вам нужно будет специально выбрать таблицу разделов, чтобы использовать индекс. Встроенное разбиение будет делать это для вас эффективно, но только так, как хочет Bigown (для экономии места), если поддерживает индексы, специфичные для разделов. Я сказал, что он мог сделать это, но не то, что он хотел бы!
Дэвид Спиллетт
0

MySQL теперь имеет виртуальные столбцы, которые можно использовать для индексов.

druud62
источник
3
Как эту функцию можно использовать для имитации отфильтрованного индекса?
ypercubeᵀᴹ
1
@ yper-trollᵀᴹ, druud62 может подумать об Oracle: dbfiddle.uk/… - MySQL, похоже , не обрабатывает NULL одинаково: dbfiddle.uk/…
Джек Дуглас
@ ДжекДуглас, возможно. ( select count(*) from foo where id is null ;
Разве
@ yper-trollᵀᴹ Oracle не индексирует строки, в которых все индексированные столбцы имеют значение NULL ( use-the-index-luke.com/sql/where-clause/null/index ), и, decode(status,'ACTIVE',name,null)например, может быть включен виртуальный столбец .
Джек Дуглас
Спасибо, я думал, что изменилось в последних версиях (и нули были проиндексированы).
ypercubeᵀᴹ