Wie erstelle ich einen bedingten Index in MySQL?

24

Wie erstelle ich einen Index, um einen bestimmten Bereich oder eine bestimmte Teilmenge der Tabelle in MySQL zu filtern? AFAIK ist es unmöglich, direkt zu erstellen, aber ich denke, es ist möglich, diese Funktion zu simulieren.

Beispiel: Ich möchte einen Index für eine NAMESpalte nur für Zeilen mit erstellenSTATUS = 'ACTIVE'

Diese Funktionalität würde ein aufgerufen werden gefilterten Index in SQL Server und ein Teilindex in Postgres.

Maniero
quelle

Antworten:

9

MySQL unterstützt derzeit keine bedingten Indizes.

Um zu erreichen, wonach Sie fragen (nicht, dass Sie es tun sollten;)), können Sie eine Hilfstabelle erstellen:

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

Dann fügen Sie drei Trigger in die Haupttabelle ein:

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 ;

Wir brauchen, delimiter //weil wir ;in den Triggern verwenden wollen.

Auf diese Weise enthält die Hilfstabelle genau die IDs, die den Haupttabellenzeilen entsprechen, die die Zeichenfolge "ACTIVE" enthalten und von den Triggern aktualisiert werden.

Um das auf einem zu benutzen, selectkönnen Sie das übliche benutzen join:

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

Wenn die Haupttabelle bereits Daten enthält oder Sie eine externe Operation ausführen, die Daten auf ungewöhnliche Weise ändert (z. B. außerhalb von MySQL), können Sie die Hilfstabelle folgendermaßen reparieren:

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

In Bezug auf die Leistung werden Sie wahrscheinlich langsamere Einfügungen, Aktualisierungen und Löschvorgänge haben. Dies kann nur dann sinnvoll sein, wenn Sie sich wirklich mit wenigen Fällen befassen, in denen der gewünschte Zustand positiv ist. Selbst auf diese Weise können Sie wahrscheinlich nur beim Testen feststellen, ob der gespeicherte Speicherplatz diesen Ansatz wirklich rechtfertigt (und ob Sie überhaupt wirklich Speicherplatz sparen).

Bacco
quelle
7

Wenn ich die Frage richtig verstehe, ist es meiner Meinung nach das Ziel, einen Index für beide Spalten, NAME und STATUS, zu erstellen. So können Sie effizient abfragen, wo NAME = 'SMITH' und STATUS = 'ACTIVE'

BlackICE
quelle
1
Ok, aber das ist nicht platzsparend, wenn Sie relativ wenige Zeilen mit dem Status ACTIVE haben.
Maniero
Nein, ist es nicht, aber das war keine Anforderung in der Frage, und es wurde nicht angegeben, dass die Tabelle stark mit einem der Werte gewichtet war. Dafür würde ich eine materialisierte Ansicht des STATUS erstellen, den Sie suchen, aber MySQL unterstützt diese nicht.
BlackICE
und Speicherplatz ist billig ...
BlackICE
2
Ja, dies ist keine direkte Anforderung, daher habe ich den Kommentar mit einem OK begonnen. Ich suche nach professionellen Alternativen. Und professionelle Alternativen, die immer nach der effizientesten Art suchen, Ihre Aufgaben zu erledigen. Ihre Antwort ist wahrscheinlich die naheliegendste. Kein Problem damit. Aber ich bin völlig anderer Meinung als "Festplattenspeicher ist billig", nicht weil es teuer ist, natürlich ist es billig, aber der Speicher ist nicht so billig, der Speicher hat niedrige Grenzwerte und der Index sollte in erster Linie auf dem Speicher basieren, um effizient zu sein. Festplattenzugriff ist nicht so billig. Ihre Antwort ist sicherlich ein richtiger Weg, um das Ziel zu erreichen, aber ich bezweifle, dass es der beste ist.
Maniero
Ich würde der Erinnerung auch nicht zustimmen, es ist heutzutage auch ziemlich billig (sicherlich nicht so billig wie Festplattenspeicher, aber bei 10 $ / Gig für einen Teil davon würde ich sagen, dass Sie ein bisschen spucken können :)
BlackICE
6

Sie können keine bedingte Indizierung durchführen, aber für Ihr Beispiel können Sie einen mehrspaltigen Index für ( name, status) hinzufügen .

Obwohl es alle Daten in diesen Spalten indiziert, hilft es Ihnen dennoch, die gesuchten Namen mit dem Status "aktiv" zu finden.

Jonathan
quelle
4

Sie könnten dies tun, indem Sie die Daten auf zwei Tabellen aufteilen, die beiden Tabellen mithilfe von Ansichten zusammenfassen, wenn alle Daten benötigt werden, und nur eine der Tabellen in dieser Spalte indizieren - aber ich denke, dies würde zu Leistungsproblemen bei Abfragen führen, die erforderlich sind Durchsuchen Sie die gesamte Tabelle, es sei denn, der Abfrageplaner ist schlauer, als ich es zu schätzen weiß. Im Wesentlichen würden Sie die Tabelle manuell partitionieren (und den Index nur auf eine der Partitionen anwenden).

Leider hilft Ihnen die integrierte Tabellenpartitionierungsfunktion bei Ihrer Suche nicht, da Sie keinen Index auf eine einzelne Partition anwenden können.

Sie könnten eine zusätzliche Spalte mit einem Index pflegen und nur dann einen Wert in dieser Spalte haben, wenn die Bedingung erfüllt ist, auf der der Index basieren soll. Dies ist jedoch wahrscheinlich arbeitsintensiv und von begrenztem (oder negativem) Wert in Bezug auf Abfrageeffizienz und Platzersparnis.

David Spillett
quelle
Ich würde NICHT zwei Tabellen haben, nur um eine bessere Indizierung zu haben, da der Join immer noch teuer sein wird, nicht wahr?
Jcolebrand
@jcolebrand: Es wäre teurer für allgemeine Abfragen (über die Ansichten, die eine Vereinigung durchführen), Sie müssten speziell aus der Partitionstabelle auswählen, um den Index zu verwenden. Die integrierte Partitionierung würde dies effizient für Sie erledigen, jedoch nur so, wie Bigown dies wünscht (um Platz zu sparen), wenn sie partitionsspezifische Indizes unterstützt. Ich sagte, er könnte es tun, nicht, dass er es wollen würde!
David Spillett
0

MySQL verfügt nun über virtuelle Spalten, die für Indizes verwendet werden können.

druud62
quelle
3
Wie kann diese Funktion verwendet werden, um einen gefilterten Index zu simulieren?
ypercubeᵀᴹ
1
@ yper-trollᵀᴹ, druud62 könnte an Oracle denken: dbfiddle.uk/… - MySQL behandelt NULLs jedoch nicht auf die gleiche Weise: dbfiddle.uk/…
Jack Douglas
@ JackDouglas vielleicht. ( select count(*) from foo where id is null ;
Ist das
@ yper-trollᵀᴹ Oracle indiziert keine Zeilen, in denen alle indizierten Spalten NULL sind ( use-the-index-luke.com/sql/where-clause/null/index ) - und eine virtuelle Spalte könnte decode(status,'ACTIVE',name,null)beispielsweise aktiviert sein .
Jack Douglas
Danke, ich dachte, das hat sich in den letzten Versionen geändert (und Nullen wurden indiziert).
ypercubeᵀᴹ