Як створити умовний індекс у MySQL?


24

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

Приклад: Я хочу створити індекс для NAMEстовпця лише для рядків ізSTATUS = 'ACTIVE'

Цей функціонал можна назвати відфільтрованим індексом у SQL Server та частковим індексом у Postgres.

Відповіді:


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 //тому що ми хочемо використовувати ;всередині тригерів.

Таким чином, допоміжна таблиця буде містити саме ідентифікатори, що відповідають основним рядкам таблиці, що містять рядок "АКТИВНО", що оновлюється тригерами.

Щоб використовувати це на a select, ви можете використовувати звичайні join:

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

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

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

Щодо продуктивності, ймовірно, у вас будуть повільніші вставки, оновлення та видалення. Це може мати певний сенс, лише якщо ви дійсно маєте справу з кількома випадками, коли бажаний стан є позитивним. Навіть таким чином, мабуть, лише тестуючи, ви зможете побачити, чи збережений простір справді виправдовує цей підхід (і якщо ви дійсно економите будь-який простір взагалі).


7

Якщо я правильно розумію питання, я думаю, що для того, щоб зробити те, що ви намагаєтеся, це створити індекс для обох стовпців, NAME та STATUS. Це ефективно дозволить вам запитати, де NAME = 'SMITH' та STATUS = 'ACTIVE'


1
Гаразд, але це не є простором, якщо у вас порівняно мало рядків зі статусом АКТИВНІ.
Маньєро

Ні, це не так, але це не було вимогою у питанні, і не було зазначено, що таблиця сильно зважена до одного зі значень. Для цього я б створив матеріалізований вигляд СТАТУСУ, який ви шукаєте, але MySQL їх не підтримує.
BlackICE

а дисковий простір дешевий ...
BlackICE

2
Так, це не пряма вимога, тому я почав коментар із ОК. Я шукаю кілька професійних альтернатив. І професійні альтернативи завжди шукають найефективніший спосіб виконати свої завдання. Можливо, ваша відповідь є найбільш очевидною. З цим немає жодних проблем. Але я повністю не погоджуюся з "дисковим простором дешевим", не тому, що він дорогий, звичайно, дешевий, проте пам'ять не така дешева, пам'ять має низькі межі, і індекс повинен жити в основному на пам'яті, щоб бути ефективним. Доступ до диска не такий дешевий. Ваша відповідь, безумовно, є одним правильним способом досягнення мети, але я сумніваюся, що це найкраще.
Маньєро

Я б не погодився з пам’яттю також, це досить дешево і в наші дні (звичайно, не так дешево, як місце на диску, але за 10 доларів / гіг за деяку частину, я б сказав, що ви можете трохи
розвіститися

6

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

Незважаючи на те, що він буде індексувати всі дані у цих стовпцях, він все одно допоможе вам знайти імена, які ви шукаєте зі статусом "активний".


4

Ви можете зробити це, розділивши дані між двома таблицями, використовуючи представлення для об'єднання двох таблиць, коли всі дані потрібні, та індексувати лише одну з таблиць у цьому стовпчику, - але я думаю, це може спричинити проблеми з ефективністю запитів, які потребують перебігайте всю таблицю, якщо планувальник запитів не розумніший, ніж я їй за це заслуга. По суті, ви б вручну розділили таблицю (і застосували індекс лише до одного з розділів).

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

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


Я б НЕ мав двох таблиць, щоб мати кращу індексацію, оскільки приєднання все ще буде дорогим, чи не так?
jcolebrand

@jcolebrand: для загальних запитів (за думками, що створюють об'єднання), це буде дорожче, вам потрібно буде спеціально вибрати з таблиці розділів, щоб використовувати індекс. Вбудований розділ зробив би це для вас ефективно, але лише так, як Bigown захоче (щоб заощадити місце), якщо він підтримував конкретні розділові індекси. Я сказав, що він може це зробити, а не те, що хотів би!
Девід Спіллетт

0

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


3
Як цю функцію можна використовувати для імітації відфільтрованого індексу?
ypercubeᵀᴹ

1
@ yper-trollᵀᴹ, druud62, можливо, думає про Oracle: dbfiddle.uk/… - MySQL не бачив, як поводитися з NULL так само, хоча: dbfiddle.uk/…
Джек Дуглас

@JackDouglas можливо. (Це не просто оптимізація індексу, яка до речі економить простір? Іншими словами, можна select count(*) from foo where id is null ;використовувати індекс?)
ypercubeᵀᴹ

@ yper-trollᵀᴹ Oracle не індексує рядки, де всі індексовані стовпці NULL ( use-the-index-luke.com/sql/where-clause/null/index ) - і, наприклад, може бути віртуальний стовпець decode(status,'ACTIVE',name,null).
Джек Дуглас

Thnx, я думав, що це змінилося в останніх версіях (і нулі були індексовані).
ypercubeᵀᴹ
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.