segunda-feira, 7 de junho de 2010

Tipo ENUM no MySQL e no PostgreSQL

Tempos atrás eu publiquei aqui, sobre os tipos ENUM e SET no MySQL, porém as respostas dos leitores deixaram claro que eu não havia explicado o assunto direito. E relendo o texto, realmente assumo que ficou péssimo. Não consegui passar o conceito, quanto menos a aplicabilidade desses recursos.

Então aqui cabe a redenção. Vamos ao tipo ENUM. Ele não é um tipo definido no padrão SQL, e sim implementações proprietárias, encontrado nos bancos de dados mais comuns. Assim seu comportamento, espaço em disco e queries que podem ser usadas diferem ligeiramente entre suas implementações, mas a idéia por trás do tipo ENUM é comum a todos.

As vantagens do uso do tipo ENUM são:
  • ganho de performance
  • segurança dos dados

As desvantagens são:
  • limites de uso (a alteração requer a reestruturação da tabela)
  • a não portabilidade devido as diferenças de implementação

A principal característica do tipo ENUM é permitir a criação de uma lista enumerada, sendo cada registro um par index-value. A idéia é bem simples mesmo, mas extremamente eficaz e poderosa. O uso do tipo ENUM deve ser feito em parâmetros onde há pouca ou nenhuma alteração durante a existência da aplicação - por exemplo, sexo, ou um sistema de rating com valores fixos de entrada.

CREATE TABLE 'myPics' (
'id' INT NOT NULL AUTO_INCREMENT ,
'url' VARCHAR( 255 ) NOT NULL ,
'rate' ENUM('bad', 'good', 'excellent') NULL DEFAULT 'good',
PRIMARY KEY ('id') ,
INDEX ('url')
) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_bin;

Na tabela de exemplo acima, criamos o campo rate, que deve armazenar a classificação da foto. Os possíveis valores são: NULL, 'bad', 'good' e 'excellent'. Ou seja você tem valores pré-definidos para o input de dados, e a cada um é atribuído um índice na ordem em que foram declarados. Os possíveis valores para o nosso campo são:
valor índice
NULL NULL
'' 0
bad 1
good 2
excellent 3

Na hora da query uma possível construção seria:

SELECT name, rate FROM myPics WHERE rate >= 'good';

Isso demonstra a utilidade do tipo ENUM. Ao invés de criar uma tabela auxiliar e atribuir valores a ela, podemos reduzir essa complexidade fazendo uso do tipo ENUM em nossa arquitetura. Ah sim, qualquer função que demande tipos numéricos (SUM(), AVG(), MIN(), MAX(), COUNT()....) podem ser usadas em cima dos campos ENUM. Nesse caso, os valores são primeiro transformados no seu índice numérico.

E vale mencionar mais uma vez que o índice atribuído depende da ordem da declaração dos possíveis valores. Se você declarar ENUM ('bom', 'ruim'), 'bom' terá um valor menor do que 'ruim', e se declarar ENUM ('ruim', 'bom'), 'ruim' terá um valor menor do que 'bom'.

No Postgresql o uso do tipo enum é ligeiramente diferente. O trecho a seguir ilustra o mesmo exemplo anterior, só no que postgre.

CREATE TYPE rating AS ENUM ('bad', 'good', 'excellent');
CREATE TABLE myPics (
id serial,
url varchar(255),
rate rating
);

É necessário criar um tipo de dado com nome próprio do tipo ENUM, e depois atribuí-lo à coluna. Na hora de utilizar, nos beneficiamos dos mesmos recursos de construção.

Agora sobre o tipo SET. O tipo SET só existe no MySQL. E uma possível aplicação para ilustrar sua utilidade seria um sistema de tags, implementado de forma nativa na tabela.

Considere a seguinte tabela:

CREATE TABLE 'myPics' (
'id' INT NOT NULL AUTO_INCREMENT ,
'url' VARCHAR( 255 ) NOT NULL ,
'rate' ENUM('bad', 'good', 'excellent') NULL,
'tags' SET('outdoor', 'studio', 'stock', 'fruits', 'nature', 'close-up', 'people') NULL
PRIMARY KEY ('id') ,
INDEX ('url')
) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_bin

Nela, podemos atribuir valores como:

INSERT INTO myPics (id, url, rate, tags) VALUES (NULL, 'xxx', 'good', 'people, close-up'), (NULL, 'yyy', 'excellent', 'fruits, nature, close-up');

Ou seja declarando as tags direto no insert. Na hora de fazer o SELECT, contamos com mais uma função nativa do MySQL bem útil, a FIND_IN_SET:

SELECT * from myPics WHERE FIND_IN_SET('nature', tags) > 0 AND FIND_IN_SET('close-up', tags) > 0;

Isso vai trazer todos os registros cujo campo tags contenha os valores 'nature' e 'close-up'.

Bom, acho que agora o conceito e a aplicação dos tipos ENUM e SET está agora mais clara e mais útil.

-----------
keepReading

8 comentários:

  1. Muito bom o artigo, não li o original mas este ficou bem claro. :D

    Só acho que poderia comentar também o limite do SET, que é 64 elementos.

    Abraços,

    ResponderExcluir
  2. Artigo muito esclarecedor! :D

    ResponderExcluir
  3. por favor, qual o nome do tipo de campo similar ao ENUM no Lianja, alguém conhece esse software? http://www.lianja.com/

    http://www.lianja.com/doc/index.php/Using_Lianja_SQL#Creating_a_Lianja_Table_using_Lianja_SQL

    Eu sou artista digital metido a criar app pra smartphone e descobri esse incrível software, estou penando....

    Grato
    Bruno.

    ResponderExcluir
  4. Ótimo artigo... mas ainda fiquei com uma dúvida:
    Usando o exemplo das fotos... se usando o SET eu crio várias categorias "do tipo viagens", "amigos", "família"... e posteriormente quero adicionar outra categoria, tipo "trabalho"... tenho que redigitar o código? Ou tem como fazer isso de alguma forma na própria página PHP?
    Em outras palavras, o operador tem como adicionar uma nova categoria em tempo de execução?

    ResponderExcluir
    Respostas
    1. Você pode deixar o usuário criar em tempo de execução, mas não é recomendado pois você terá que executar um alter table no campo enum ou set para adicionar o novo índice do campo.
      e ao executa um alter table, o SGDB executa um LOCK na tabela, para garantir integridade das informações.

      Excluir
  5. Muito obrigado pelas explicações. O post estava bem explicado. Gostei principalmente de teres mostrado as vantagens e desvantagens, além da aplicabilidade desse tipo de dado. Obrigado.

    ResponderExcluir
  6. Olá! Esta parte aqui da publicação está errada...

    E vale mencionar mais uma vez que o índice atribuído depende da ordem da declaração dos possíveis valores. Se você declarar ENUM ('bom', 'ruim'), 'bom' terá um valor menor do que 'ruim', e se declarar ENUM ('ruim', 'bom'), 'ruim' terá um valor menor do que 'bom'.

    ResponderExcluir
    Respostas
    1. O dado que tenta para o lado esquerdo é o que tem o index maior do que estão situados ao seu lado direito, logo: ENUM('bom','ruim') o "bom", que esta do lado esquerdo possui valor maior que o ruim. outro caso ENUM('ruim','bom') agora o dado com index de maior valor é o "ruim".

      Excluir