É razoável marcar todas as colunas, exceto uma, como chave primária?

9

Eu tenho uma mesa representando filmes. Os campos são:
id (PK), title, genre, runtime, released_in, tags, origin, downloads.

Meu banco de dados não pode ser poluído por linhas duplicadas, portanto, desejo impor exclusividade. O problema é que filmes diferentes podem ter o mesmo título ou até os mesmos campos, exceto tagse downloads. Como impor exclusividade?

Pensei em duas maneiras:

  • faça todos os campos, exceto downloadsa chave primária. Eu estou me mantendo de downloadsfora, pois é JSON e provavelmente afetará o desempenho.
  • mantenha apenas idcomo chave primária, mas adicione uma restrição exclusiva a todas as outras colunas (exceto, novamente downloads).

Li essa pergunta que é muito parecida, mas não entendi bem o que devo fazer. Atualmente, esta tabela não está relacionada a nenhuma outra tabela, mas no futuro poderia ser.

No momento, tenho pouco menos de 20.000 registros, mas espero que o número cresça. Não sei se isso é um pouco relevante para o problema.

Edição: Eu modifiquei o esquema e aqui está como eu criaria a tabela:

CREATE TABLE movies (
    id          serial PRIMARY KEY,
    title       text NOT NULL,
    runtime     smallint NOT NULL CHECK (runtime >= 0),
    released_in smallint NOT NULL CHECK (released_in > 0),
    genres      text[] NOT NULL default ARRAY[]::text[],
    tags        text[] NOT NULL default ARRAY[]::text[],
    origin      text[] NOT NULL default ARRAY[]::text[],
    downloads   json NOT NULL,
    inserted_at timestamp NOT NULL default current_timestamp,
    CONSTRAINT must_be_unique UNIQUE(title,runtime,released_in,genres,tags,origin)
);

Também adicionei a timestampcoluna, mas isso não é um problema, pois não vou tocá-la. Por isso, será sempre automático e único.

rubik
fonte
Pergunta intimamente relacionada (com resposta) sobre SO: Preciso de uma chave primária para minha tabela, que possui um UNIQUE (composto de 4 colunas), um dos quais pode ser NULL? . Se qualquer uma das colunas puder ser NULL, considere com urgência: dba.stackexchange.com/q/9759/3684 .
Erwin Brandstetter

Respostas:

4

Sua definição de tabela parece razoável agora. Com todas as colunas, NOT NULLa UNIQUErestrição funcionará conforme o esperado - exceto erros de digitação e pequenas diferenças ortográficas, o que pode ser bastante comum, receio. Considere o comentário de @ a_horse .

Alternativa com índice exclusivo funcional

A outra opção seria um índice exclusivo funcional (semelhante ao que o @Dave comentou ). Mas eu usaria um uuidtipo de dados para otimizar o tamanho e o desempenho do índice.

A conversão da matriz para o texto não é IMMUTABLE(devido à sua implementação genérica):

Portanto, você precisa de uma pequena função auxiliar para declará- la imutável:

CREATE OR REPLACE FUNCTION f_movie_uuid(_title text
                                      , _runtime int2
                                      , _released_in int2
                                      , _genres text[]
                                      , _tags text[]
                                      , _origin text[])
  RETURNS uuid LANGUAGE sql IMMUTABLE AS  -- faking IMMUTABLE
'SELECT md5(_title || _runtime::text || _released_in::text
         || _genres::text || _tags::text || _origin::text)::uuid';

Use-o para a definição de índice:

CREATE UNIQUE INDEX movies_uni_idx
ON movies (f_movie_uuid(title,runtime,released_in,genres,tags,origin));

SQL Fiddle.

Mais detalhes:

Você pode usar o UUID gerado como PK, mas eu ainda usaria a serialcoluna com seus 4 bytes, o que é simples e barato para referências FK e outros fins. Um UUID seria uma ótima opção para sistemas distribuídos que precisam gerar valores de PK independentemente. Ou para mesas muito grandes, mas não há filmes suficientes em nosso sistema solar para isso.

Prós e contras

Uma restrição exclusiva é implementada com um índice exclusivo nas colunas envolvidas. Coloque as colunas relevantes na definição de restrição primeiro e você terá um índice útil para outros fins como benefício colateral.

Existem outros benefícios específicos, aqui está uma lista:

O índice exclusivo funcional é (potencialmente muito) menor em tamanho, o que pode torná-lo substancialmente mais rápido. Se suas colunas não forem muito grandes, a diferença não será grande. Há também o pequeno custo indireto para o cálculo.

Concatenar todas as colunas pode introduzir falsos positivos ( 'foo ' || 'bar' = 'foob ' || 'ar', mas isso parece muito improvável para este caso. Os erros de digitação são muito mais prováveis ​​que você pode ignorá-la com segurança aqui.

Exclusividade e matrizes

As matrizes teriam que ser classificadas de forma consistente para fazer sentido em qualquer arranjo exclusivo que dependa do =operador '{1,2}' <> '{2,1}'. Sugiro tabelas de pesquisa para genre, tage origincom serialPK e entradas exclusivas, que permitem a pesquisa difusa de elementos de matriz. Então:

De qualquer forma, trabalhando com matrizes diretamente ou com um esquema normalizado e uma visualização materializada, a pesquisa pode ser muito eficiente com o índice e os operadores certos:

a parte, de lado

Se você estiver usando o Postgres 9.4 ou posterior, considere em jsonbvez dejson .

Erwin Brandstetter
fonte
6

Imagine que você está com um grupo de amigos e a conversa se transforma em filmes. Alguém pergunta: "O que você achou dos 'Os Três Mosqueteiros'?" Você responde: "Qual?"

Quais informações adicionais você precisaria para ter certeza absoluta de que ambos estão pensando no mesmo filme? O nome do diretor? O estúdio de produção? O ano em que foi lançado? Um dos nomes da estrela? Alguma combinação de dois ou mais?

A resposta para a minha pergunta e a sua são as mesmas.

No entanto, eu não acho que esse gênero seja um bom candidato. Uma razão, o gênero é um critério subjetivo demais. A ação dos três mosqueteiros? drama? aventura? comédia? ação e aventura? comédia romântica? Costumo ver o mesmo filme listado em diferentes gêneros. Mesmo quando você permite vários gêneros, o usuário pode selecionar um totalmente diferente, não listado no filme que está procurando.

Até os tempos de execução podem diferir, especialmente entre as versões de teatro e VCR / DVD / b-ray.

Portanto, você precisa de atributos objetivos e rígidos que não mudam de um release para outro. Infelizmente, isso pode excluir o nome do filme, já que se sabe que os filmes foram renomeados, especialmente após o lançamento de uma sequência.

E a data de lançamento? O lançamento teatral de 1993? O lançamento do videocassete de 1999? O lançamento em DVD de 2004? Você entendeu a ideia.

Venha para pensar sobre o assunto, e todos os filmes dirigidos por Alan Smithee? O real diretor finalmente se adiantou para colocar seu nome no projeto após o fato? Eu não sei.

É melhor parar enquanto ainda existem alguns critérios.

Alguns pontos adicionais:

  • Sim, mantenha a chave substituta e crie um índice exclusivo nos campos de chave natural (se você puder finalmente defini-los). A chave substituta é melhor para referências de chave estrangeira. Você não deseja duplicar todos os campos de chave natural em todas as tabelas que contêm uma referência a um filme.
  • Solte os campos da matriz (gêneros, tags, origens). Vá em frente e normalize adequadamente esses atributos. Eu nunca vi um campo de matriz que não apresentasse muito mais problemas do que valia, especialmente se você deseja que eles sejam pesquisáveis ​​("... where genre = 'horror' ..."). Observe que isso não eliminará automaticamente quaisquer problemas com diferenças de maiúsculas e minúsculas ("Ficção científica" vs "SciFi") - a menos que você mantenha adequadamente as tabelas de pesquisa . Mas é muito mais fácil verificar essas diferenças em um campo de uma tabela pequena do que em todas as células da matriz de todas as linhas de uma tabela grande.
TommCatt
fonte
4

A coluna ID não tem nenhuma vantagem quando se trata da exclusividade que você deseja / precisa aplicar. A exclusividade de qualquer combinação de atributos nunca será imposta pela adição de um ID sem sentido. Sua "vantagem" mostra apenas quando você chega ao ponto em que precisa de uma nova tabela que precise de uma chave estrangeira para esta. Nesse caso, e se você incluiu o ID, poderá usá-lo como o FK em sua nova tabela. (Mas não pense que será um almoço grátis. A desvantagem dessa abordagem é que você provavelmente se encontrará escrevendo mais junções com o simples objetivo de buscar informações que poderiam perfeitamente fazer parte da nova tabela que você criou. )

Erwin Smout
fonte
11
Se as regras de negócios disserem que a combinação de valores nos atributos FOO e BAR deve ser única, a adição de um ID não será possível. A adição do ID apenas evita a necessidade de incluir FOO e BAR como tal nas tabelas de referência. O que, por sua vez, exige mais junções porque os atributos FOO e BAR (que carregam identificadores de NEGÓCIOS) não estão onde deveriam estar (e onde é muito provável que EXPECTAMOS estar, pelo menos do ponto de vista comercial).
Erwin Smout
11
NÃO são as "linhas" que devem ser únicas; é o que a empresa diz que devem ser seus identificadores. Se essa é uma combinação dos atributos FOO e BAR, é a combinação dos atributos FOO e BAR.
Erwin Smout
2
Ter o ID ou não não resolve nenhum problema de imposição da exclusividade das colunas "comerciais" em sua tabela. A imposição da exclusividade deve ser feita declarando as chaves apropriadas (o que você faz - o fato de ter usado a palavra sintática "CONSTRAINT" em vez de "KEY" não significa que não é uma chave).
Erwin Smout