Como criar um índice condicional no MySQL?

24

Como criar um índice para filtrar um intervalo ou subconjunto específico da tabela no MySQL? AFAIK é impossível criar diretamente, mas acho que é possível simular esse recurso.

Exemplo: quero criar um índice para a NAMEcoluna apenas para linhas comSTATUS = 'ACTIVE'

Essa funcionalidade seria chamada de índice filtrado no SQL Server e um índice parcial no Postgres.

Maniero
fonte

Respostas:

9

Atualmente, o MySQL não suporta índices condicionais.

Para obter o que você está perguntando (não que você deva fazê-lo;)), você pode começar a criar uma tabela auxiliar:

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

Então você adiciona três gatilhos na tabela principal:

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 ;

Precisamos delimiter //porque queremos usar ;dentro dos gatilhos.

Dessa forma, a tabela auxiliar conterá exatamente os IDs correspondentes às linhas principais da tabela que contêm a sequência "ACTIVE", sendo atualizada pelos gatilhos.

Para usar isso em um select, você pode usar o habitual join:

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

Se a tabela principal já contiver dados, ou caso você faça alguma operação externa que altere os dados de maneira incomum (EG: fora do MySQL), você pode corrigir a tabela auxiliar com isso:

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

Sobre o desempenho, provavelmente você terá inserções, atualizações e exclusões mais lentas. Isso pode fazer algum sentido apenas se você realmente lidar com alguns casos em que a condição desejada é positiva. Mesmo assim, provavelmente apenas testando você pode ver se o espaço economizado realmente justifica essa abordagem (e se você realmente está economizando algum espaço).

Bacco
fonte
7

Se eu entendi a pergunta corretamente, acho que o que você está tentando fazer é criar um índice nas duas colunas, NAME e STATUS. Isso permitiria que você consultasse com eficiência onde NAME = 'SMITH' e STATUS = 'ACTIVE'

Gelo preto
fonte
11
Ok, mas isso não é eficiente em termos de espaço se você tiver relativamente poucas linhas com o status ATIVO.
Maniero
Não, não é, mas isso não era um requisito na pergunta e não foi afirmado que a tabela estava fortemente ponderada para um dos valores. Para isso, eu criaria uma visão materializada do STATUS que você está procurando, mas o MySQL não suporta isso.
BlackICE
e espaço em disco é barato ...
BlackICE
2
Sim, não é um requisito direto, então comecei o comentário com um OK. Estou procurando algumas alternativas profissionais. E alternativas profissionais sempre procurando a maneira mais eficiente de realizar suas tarefas. Sua resposta provavelmente é a mais óbvia. Não há problema com isso. Mas eu discordo totalmente de "o espaço em disco é barato", não porque seja caro, é claro que é barato, no entanto, a memória não é tão barata, a memória tem limites baixos e o índice deve se basear principalmente na memória para ser eficiente. O acesso ao disco não é tão barato. Sua resposta certamente é uma maneira correta de atingir a meta, mas duvido que seja a melhor.
Maniero
Também discordo da memória, também é muito barato hoje em dia (certamente não tão barato quanto o espaço em disco, mas a US $ 10 / gig para algumas coisas, eu diria que você pode gastar um pouco :) :)
BlackICE
6

Você não pode fazer a indexação condicional, mas, por exemplo, você pode adicionar um índice de várias colunas em ( name, status).

Mesmo que indexe todos os dados nessas colunas, ainda o ajudará a encontrar os nomes que você está procurando com o status "ativo".

Jonathan
fonte
4

Você pode fazer isso dividindo os dados entre duas tabelas, usando visualizações para unir as duas tabelas quando todos os dados forem necessários e indexando apenas uma das tabelas nessa coluna - mas acho que isso causaria problemas de desempenho para consultas que precisam executar sobre a tabela inteira, a menos que o planejador de consultas seja mais inteligente do que eu acredito. Essencialmente, você particionaria manualmente a tabela (e aplicaria o índice a apenas uma das partições).

Infelizmente, o recurso de particionamento de tabela embutido não o ajudará em sua busca, pois você não pode aplicar um índice a uma única partição.

Você pode manter uma coluna extra com um índice e ter apenas um valor nessa coluna quando a condição na qual você deseja basear o índice for verdadeira, mas é provável que seja trabalhoso e tenha um valor limitado (ou negativo) em termos de eficiência de consulta e economia de espaço.

David Spillett
fonte
Eu NÃO teria duas tabelas apenas para ter uma melhor indexação, pois a associação ainda será cara, não é?
jcolebrand
@ jcolebrand: seria mais caro para consultas gerais (sobre as visualizações que fazem uma união), você precisaria selecionar especificamente na tabela de partições para usar o índice. O particionamento interno faria isso por você de maneira eficiente, mas apenas da maneira que Bigown deseja (economizar espaço) se suportar índices específicos da partição. Eu disse que ele poderia fazer isso, não que ele quisesse!
precisa saber é o seguinte
0

O MySQL agora possui colunas virtuais, que podem ser usadas para índices.

druud62
fonte
3
Como esse recurso pode ser usado para simular um índice filtrado?
ypercubeᵀᴹ
11
@ yper-trollᵀᴹ, druud62 pode estar pensando em Oracle: dbfiddle.uk/… - O MySQL não vê tratamento de NULLs da mesma maneira: dbfiddle.uk/…
Jack Douglas
@JackDouglas talvez. (não é que apenas uma otimização índice que economiza espaço pela maneira Em outras palavras poderia? select count(*) from foo where id is null ;usar um índice?)
ypercubeᵀᴹ
@ yper-trollᵀᴹ O Oracle não indexa linhas em que todas as colunas indexadas são NULL ( use-the-index-luke.com/sql/where-clause/null/index ) - e uma coluna virtual pode estar ativada,decode(status,'ACTIVE',name,null) por exemplo.
Jack Douglas
Thnx, eu pensei que tinha mudado nas versões recentes (e nulos foram indexados).
ypercubeᵀᴹ