Come creare un indice condizionale in MySQL?


24

Come creare un indice per filtrare un intervallo o un sottoinsieme specifico della tabella in MySQL? AFAIK è impossibile creare direttamente ma penso che sia possibile simulare questa funzione.

Esempio: voglio creare un indice per la NAMEcolonna solo per le righe conSTATUS = 'ACTIVE'

Questa funzionalità si chiamerebbe un indice filtrato in SQL Server e un indice parziale in Postgres.

Risposte:


9

MySQL al momento non supporta gli indici condizionali.

Per raggiungere quello che stai chiedendo (non che tu debba farlo;)) puoi iniziare a creare una tabella ausiliaria:

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

Quindi aggiungi tre trigger nella tabella principale:

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 ;

Abbiamo bisogno delimiter //perché vogliamo usare ;all'interno dei grilletti.

In questo modo, la tabella ausiliaria conterrà esattamente gli ID corrispondenti alle righe della tabella principale che contengono la stringa "ATTIVO", in fase di aggiornamento dai trigger.

Per usarlo su a select, puoi usare il solito join:

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

Se la tabella principale contiene già dei dati, o nel caso in cui si esegua un'operazione esterna che modifica i dati in modo insolito (ad esempio: fuori da MySQL), è possibile correggere la tabella ausiliaria con questo:

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

Per quanto riguarda le prestazioni, probabilmente avrai inserimenti, aggiornamenti ed eliminazioni più lenti. Questo può avere senso solo se si affrontano davvero pochi casi in cui la condizione desiderata è positiva. Anche così, probabilmente solo testando puoi vedere se lo spazio risparmiato giustifica davvero questo approccio (e se stai davvero risparmiando spazio).


7

Se capisco correttamente la domanda, penso che ciò che realizzarebbe ciò che stai cercando di fare è creare un indice su entrambe le colonne, NAME e STATUS. Ciò consentirebbe in modo efficiente di interrogare dove NAME = 'SMITH' e STATUS = 'ACTIVE'


1
Ok, ma questo non è efficiente nello spazio se hai relativamente poche righe con stato ATTIVO.
Maniero,

No, non lo è, ma questo non era un requisito nella domanda e non è stato affermato che la tabella fosse pesantemente ponderata su uno dei valori. Per questo vorrei creare una visione materializzata dello STATUS che stai cercando, ma MySQL non li supporta.
BlackICE,

e lo spazio su disco è economico ...
BlackICE

2
Sì, non è un requisito diretto, quindi ho iniziato il commento con un OK. Sto cercando delle alternative professionali. E alternative professionali sempre alla ricerca del modo più efficiente per svolgere i tuoi compiti. La tua risposta probabilmente è la più ovvia. Nessun problema. Ma non sono assolutamente d'accordo con "lo spazio su disco è economico", non perché è costoso, ovviamente è economico ma la memoria non è così economica, la memoria ha limiti bassi e l'indice dovrebbe vivere principalmente sulla memoria per essere efficiente. L'accesso al disco non è così economico. La tua risposta è certamente un modo corretto per raggiungere l'obiettivo, ma dubito che sia il migliore.
Maniero

Non sarei in disaccordo anche sulla memoria, è abbastanza economico in questi giorni (certamente non economico come lo spazio su disco, ma a $ 10 / concerto per alcuni di esso, direi che puoi concedervi un po 'di lusso :)
BlackICE

6

Non è possibile eseguire l'indicizzazione condizionale, ma ad esempio è possibile aggiungere un indice a più colonne su ( name, status).

Anche se indicizzerà tutti i dati in quelle colonne, ti aiuterà comunque a trovare i nomi che stai cercando con lo stato "attivo".


4

Puoi farlo dividendo i dati tra due tabelle, usando le viste per unire le due tabelle quando tutti i dati sono necessari e indicizzando solo una delle tabelle su quella colonna - ma penso che ciò causerebbe problemi di prestazioni per le query che devono correre su tutta la tabella a meno che il planner delle query non sia più intelligente di quanto io gli dia credito. In sostanza, partizionerai manualmente la tabella (e applicherai l'indice a una sola delle partizioni).

Sfortunatamente la funzione di partizionamento delle tabelle integrata non ti aiuterà nella tua missione in quanto non puoi applicare un indice a una singola partizione.

È possibile mantenere una colonna aggiuntiva con un indice e avere un valore in quella colonna solo quando la condizione su cui si desidera basare l'indice è vera, ma è probabile che questa sia ad alta intensità di manodopera e di valore limitato (o negativo) in termini di efficienza delle query e risparmio di spazio.


NON avrei due tabelle solo per avere una migliore indicizzazione, poiché il join sarà comunque costoso, no?
jcolebrand

@jcolebrand: sarebbe più costoso per le query generali (rispetto alle viste che fanno un sindacato), dovrai selezionare specificamente dalla tabella delle partizioni per usare l'indice. Il partizionamento integrato lo farebbe per te in modo efficiente, ma solo il modo in cui Bigown vuole (per risparmiare spazio) se supporta gli indici specifici della partizione. Ho detto che poteva farlo, non che avrebbe voluto!
David Spillett,

0

MySQL ora ha colonne virtuali, che possono essere utilizzate per gli indici.


3
Come può essere utilizzata questa funzione per simulare un indice filtrato?
ypercubeᵀᴹ

1
@ yper-trollᵀᴹ, druud62 potrebbe pensare a Oracle: dbfiddle.uk/… - MySQL non ha visto trattare i NULL allo stesso modo: dbfiddle.uk/…
Jack Douglas,

@JackDouglas forse. (non è solo un'ottimizzazione dell'indice che consente di risparmiare spazio tra l'altro? In altre parole potrebbe select count(*) from foo where id is null ;utilizzare un indice?)
ypercubeᵀᴹ

@ yper-trollᵀᴹ Oracle non indicizza le righe in cui tutte le colonne indicizzate sono NULL ( use-the-index-luke.com/sql/where-clause/null/index ) e, decode(status,'ACTIVE',name,null)ad esempio, potrebbe essere attiva una colonna virtuale .
Jack Douglas,

Grazie, ho pensato che fosse cambiato nelle versioni recenti (e che i null fossero indicizzati).
ypercubeᵀᴹ
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.