Jak utworzyć indeks warunkowy w MySQL?

24

Jak utworzyć indeks do filtrowania określonego zakresu lub podzbioru tabeli w MySQL? AFAIK nie można utworzyć bezpośrednio, ale myślę, że można symulować tę funkcję.

Przykład: Chcę utworzyć indeks dla NAMEkolumny tylko dla wierszy zSTATUS = 'ACTIVE'

Ta funkcja nazywa się indeksem filtrowanym w programie SQL Server i indeksem częściowym w Postgres.

Maniero
źródło

Odpowiedzi:

9

MySQL obecnie nie obsługuje indeksów warunkowych.

Aby osiągnąć to, o co prosisz (nie to, że powinieneś to zrobić;)) możesz zacząć tworzyć tabelę pomocniczą:

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

Następnie dodajesz trzy wyzwalacze w głównej tabeli:

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 ;

Potrzebujemy, delimiter //ponieważ chcemy używać ;wewnątrz wyzwalaczy.

W ten sposób tabela pomocnicza będzie zawierać dokładnie identyfikatory odpowiadające głównym wierszom tabeli zawierającym ciąg „AKTYWNY”, aktualizowanym przez wyzwalacze.

Aby użyć tego na a select, możesz użyć zwykłego join:

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

Jeśli główna tabela już zawiera dane lub jeśli wykonasz operację zewnętrzną, która zmienia dane w nietypowy sposób (np. Poza MySQL), możesz naprawić tabelę pomocniczą w ten sposób:

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

Jeśli chodzi o wydajność, prawdopodobnie będziesz mieć wolniejsze wstawianie, aktualizowanie i usuwanie. Może to mieć sens tylko wtedy, gdy naprawdę masz do czynienia z kilkoma przypadkami, w których pożądany stan jest pozytywny. Nawet w ten sposób, prawdopodobnie tylko testy pozwalają sprawdzić, czy zaoszczędzone miejsce naprawdę uzasadnia to podejście (i czy naprawdę oszczędzasz w ogóle miejsce).

Bacco
źródło
7

Jeśli dobrze rozumiem pytanie, myślę, że to, co osiągniesz, to utworzenie indeksu dla obu kolumn, NAZWA i STATUS. Pozwoliłoby to skutecznie zapytać, gdzie NAME = „SMITH” i STATUS = „ACTIVE”

Czarny lód
źródło
1
Ok, ale to nie zajmuje mało miejsca, jeśli masz stosunkowo niewiele linii ze statusem AKTYWNY.
Maniero
Nie, nie jest, ale nie było to wymagane w pytaniu i nie stwierdzono, że tabela była mocno ważona do jednej z wartości. W tym celu stworzyłbym zmaterializowany widok STATUSU, którego szukasz, ale MySQL go nie obsługuje.
BlackICE,
a miejsce na dysku jest tanie ...
BlackICE
2
Tak, nie jest to bezpośredni wymóg, więc komentarz zacząłem od OK. Szukam profesjonalnych alternatyw. A profesjonalne alternatywy zawsze szukają najbardziej wydajnego sposobu wykonywania swoich zadań. Twoja odpowiedź jest prawdopodobnie najbardziej oczywista. Nie ma z tym problemu. Ale całkowicie nie zgadzam się z tym, że „miejsce na dysku jest tanie”, nie dlatego, że jest drogie, oczywiście tanie, jednak pamięć nie jest tak tania, pamięć ma niskie limity, a indeks powinien żyć przede wszystkim na pamięci, aby była wydajna. Dostęp do dysku nie jest tak tani. Twoja odpowiedź z pewnością jest jednym z prawidłowych sposobów osiągnięcia celu, ale wątpię, aby był najlepszy.
Maniero
Nie zgadzam się również co do pamięci, jest też całkiem tania (z pewnością nie tak tanie jak miejsce na dysku, ale za 10 USD / gig za część, powiedziałbym, że możesz trochę
popsuć
6

Nie możesz wykonywać indeksowania warunkowego, ale na przykład możesz dodać indeks wielokolumnowy na ( name, status).

Mimo że zindeksuje wszystkie dane w tych kolumnach, nadal pomoże ci znaleźć poszukiwane nazwy ze statusem „aktywne”.

Jonathan
źródło
4

Można to zrobić, dzieląc dane między dwie tabele, używając widoków do zjednoczenia dwóch tabel, gdy wszystkie dane są potrzebne, i indeksując tylko jedną tabelę w tej kolumnie - ale myślę, że spowodowałoby to problemy z wydajnością zapytań, które muszą przeglądaj całą tabelę, chyba że planista zapytań jest bardziej sprytny niż mu się wydaje. Zasadniczo należy ręcznie podzielić tabelę na partycje (i zastosować indeks tylko do jednej z partycji).

Niestety, wbudowana funkcja partycjonowania tabel nie pomoże ci w twoim zadaniu, ponieważ nie możesz zastosować indeksu do pojedynczej partycji.

Możesz utrzymać dodatkową kolumnę z indeksem i mieć wartość w tej kolumnie tylko wtedy, gdy warunek, na którym ma być oparty indeks, jest spełniony, ale jest to prawdopodobnie pracochłonne i ma ograniczoną (lub ujemną) wartość pod względem efektywność zapytań i oszczędność miejsca.

David Spillett
źródło
NIE miałbym dwóch tabel tylko po to, by mieć lepsze indeksowanie, ponieważ łączenie będzie nadal drogie, prawda?
jcolebrand
@ jcolebrand: byłoby droższe w przypadku ogólnych zapytań (w porównaniu z widokami tworzącymi unię), aby skorzystać z indeksu, należy wybrać konkretnie tabelę partycji. Wbudowane partycjonowanie zrobiłoby to dla ciebie efektywnie, ale tylko w taki sposób, w jaki Bigown chce (aby zaoszczędzić miejsce), jeśli obsługuje indeksy specyficzne dla partycji. Powiedziałem, że może to zrobić, a nie że chciałby!
David Spillett
0

MySQL ma teraz wirtualne kolumny, których można używać do indeksów.

druud62
źródło
3
Jak można użyć tej funkcji do symulacji przefiltrowanego indeksu?
ypercubeᵀᴹ
1
@ yper-trollᵀᴹ, druud62 może myśleć o Oracle: dbfiddle.uk/… - MySQL nie widział jednak traktowania NULL w ten sam sposób: dbfiddle.uk/…
Jack Douglas
@JackDouglas, być może. (czy nie jest to tylko optymalizacja indeksu, która przy okazji oszczędza miejsce? Innymi słowy, można select count(*) from foo where id is null ;użyć indeksu?)
ypercubeᵀᴹ
@ yper-trollᵀᴹ Oracle nie indeksuje wierszy, w których wszystkie indeksowane kolumny mają wartość NULL ( use-the-index-luke.com/sql/where-clause/null/index ) - i na decode(status,'ACTIVE',name,null)przykład może być włączona kolumna wirtualna .
Jack Douglas,
Dzięki, myślałem, że zmieniło się w ostatnich wersjach (i wartości zerowe zostały zindeksowane).
ypercubeᵀᴹ