È ragionevole contrassegnare tutte le colonne tranne una come chiave primaria?


9

Ho un tavolo che rappresenta i film. I campi sono:
id (PK), title, genre, runtime, released_in, tags, origin, downloads.

Il mio database non può essere inquinato da righe duplicate, quindi voglio rafforzare l'univocità. Il problema è che film diversi potrebbero avere lo stesso titolo o persino gli stessi campi tranne tagse downloads. Come applicare l'unicità?

Ho pensato a due modi:

  • crea tutti i campi tranne downloadsla chiave primaria. Sto tenendo downloadsfuori dal momento che è JSON e probabilmente avrà un impatto sulle prestazioni.
  • mantieni solo idcome chiave primaria, ma aggiungi un vincolo univoco con tutte le altre colonne (tranne, di nuovo, downloads).

Ho letto questa domanda che è molto simile, ma non ho capito bene cosa dovrei fare. Attualmente questa tabella non è correlata ad altre tabelle, ma in futuro potrebbe esserlo.

Al momento ho poco meno di 20.000 dischi, ma mi aspetto che il numero cresca. Non so se questo sia in qualche modo rilevante per il problema.

EDIT: ho modificato lo schema ed ecco come avrei creato la tabella:

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

Ho anche aggiunto la timestampcolonna, ma questo non è un problema in quanto non la toccherò. Quindi sarà sempre automatico e unico.


Domanda strettamente correlata (con risposta) su SO: ho bisogno di una chiave primaria per la mia tabella, che ha un UNIQUE (composito a 4 colonne), una delle quali può essere NULL? . Se una delle colonne può essere NULL, considerare urgentemente questo: dba.stackexchange.com/q/9759/3684 .
Erwin Brandstetter,

Risposte:


4

La definizione della tabella sembra ragionevole dappertutto. Con tutte le colonne NOT NULLil UNIQUEvincolo funzionerà come previsto, ad eccezione di errori di battitura e lievi differenze di ortografia, che temo possano essere piuttosto comuni. Considera il commento di @ a_horse .

Alternativa con indice univoco funzionale

L'altra opzione sarebbe un indice univoco funzionale (simile a quello che ha commentato @Dave ). Ma vorrei utilizzare un uuidtipo di dati per ottimizzare le dimensioni e le prestazioni dell'indice.

Il cast dall'array al testo non è IMMUTABLE(a causa della sua implementazione generica):

Quindi hai bisogno di una piccola funzione di aiuto per dichiararla immutabile:

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';

Usalo per la definizione dell'indice:

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

SQL Fiddle.

Più dettagli:

Potresti usare l'UUID generato come PK, ma vorrei comunque usare la serialcolonna con i suoi 4 byte, che è semplice ed economica per riferimenti FK e altri scopi. Un UUID sarebbe un'ottima opzione per i sistemi distribuiti che devono generare valori PK in modo indipendente. O per tavoli molto grandi, ma non ci sono abbastanza film nel nostro sistema solare per quello.

Pro e contro

Un vincolo univoco viene implementato con un indice univoco sulle colonne interessate. Inserisci prima le colonne pertinenti nella definizione del vincolo e avrai un indice utile per altri scopi come vantaggio collaterale.

Ci sono altri vantaggi specifici, ecco un elenco:

L' indice univoco funzionale è (potenzialmente molto) più piccolo, il che può renderlo sostanzialmente più veloce. Se le tue colonne non sono troppo grandi, la differenza non sarà molto. C'è anche il piccolo costo generale per il calcolo.

Concatenare tutte le colonne può introdurre falsi positivi ( 'foo ' || 'bar' = 'foob ' || 'ar'ma questo sembra molto improbabile per questo caso. I Typos sono molto più probabili che puoi tranquillamente ignorarlo qui.

Unicità e matrici

Le matrici dovrebbero essere ordinate in modo coerente per avere un senso in qualsiasi accordo unico basato sull'operatore =perché '{1,2}' <> '{2,1}'. Suggerisco tabelle di ricerca per genre, tage origincon serialPK e voci uniche, che consentono la ricerca fuzzy di elementi dell'array. Poi:

In entrambi i casi, lavorando con array direttamente o con uno schema normalizzato e una vista materializzata, la ricerca può essere molto efficiente con l'indice e gli operatori giusti:

A parte

Se stai usando Postgres 9.4 o successivo, considera jsonbinvece dijson .


6

Immagina di essere fuori con un gruppo di amici e la conversazione si trasforma in film. Qualcuno chiede: "Cosa ne pensi di" I tre moschettieri "?" Tu rispondi "Quale?"

Di quali informazioni aggiuntive avresti bisogno per essere assolutamente sicuro che entrambi pensiate allo stesso film? Il nome del regista? Lo studio di produzione? L'anno in cui è stato rilasciato? Uno dei nomi della stella? Qualche combinazione di due o più?

La risposta alla mia domanda e alla tua sono le stesse.

Tuttavia, non penserei che il genere sarebbe un buon candidato. Una ragione, il genere è un criterio troppo soggettivo. L'azione di "I tre moschettieri"? Dramma? avventura? commedia? azione avventura? commedia romantica? Vedo spesso lo stesso film elencato in generi diversi. Anche quando consenti più generi, il tuo utente può selezionarne uno completamente diverso non elencato con il film che stanno cercando.

Anche l'autonomia può differire, specialmente tra le versioni teatro e VCR / DVD / b-ray.

Quindi hai bisogno di attributi duri e oggettivi che non cambieranno da un comunicato multimediale all'altro. Sfortunatamente, ciò può escludere il nome del film poiché i film sono stati rinominati, soprattutto dopo l'uscita di un sequel.

Che dire della data di uscita? L'uscita teatrale del 1993? La versione del videoregistratore del 1999? L'uscita del DVD del 2004? Ti viene l'idea.

Vieni a pensarci, che dire di tutti quei film diretti da Alan Smithee? Il vero regista ha mai finalmente fatto un passo avanti per mettere il suo nome sul progetto dopo il fatto? Non lo so.

Hmm, è meglio che mi fermi mentre ci sono ancora alcuni criteri.

Alcuni punti aggiuntivi:

  • Sì, mantieni la chiave surrogata e crea un indice univoco sui campi chiave naturali (se riesci finalmente a inchiodarli). La chiave surrogata è la migliore per i riferimenti a chiave esterna. Non vuoi duplicare tutti i campi chiave naturali in ogni tabella che contiene un riferimento a un film.
  • Rilascia i campi dell'array (generi, tag, origini). Vai avanti e normalizza correttamente quegli attributi. Non ho mai visto un campo array che non rappresentasse un problema maggiore di quanto ne valesse la pena, soprattutto se si desidera che siano ricercabili ("... dove genere =" horror "..."). Nota che questo non eliminerà automaticamente eventuali problemi con differenze tra maiuscole e minuscole ("Fantascienza" vs "SciFi"), a meno che tu non mantenga correttamente le tabelle di ricerca . Ma è molto più facile controllare tali differenze in un campo di una piccola tabella rispetto a ogni cella dell'array di ogni riga di una grande tabella.

4

La colonna ID non ha alcun vantaggio quando si tratta dell'unicità che si desidera / è necessario applicare. L'unicità di qualsiasi combinazione di attributi non verrà mai applicata aggiungendo un ID insignificante. Il suo "vantaggio" mostra solo quando arrivi al punto in cui avresti bisogno di una nuova tabella che necessita di una chiave esterna per questa. In tal caso, e SE hai incluso l'ID, puoi usarlo come FK nella tua nuova tabella. (Ma non pensare che sarà un pranzo gratuito. L'aspetto negativo di un simile approccio è che probabilmente ti ritroverai a scrivere più join per il solo scopo di recuperare informazioni che potrebbero benissimo far parte di quel nuovo tavolo che hai creato. )


1
Se le regole aziendali affermano che la combinazione di valori negli attributi FOO e BAR deve essere univoca, l'aggiunta di un ID non lo consentirà. L'aggiunta dell'ID facilita semplicemente la necessità di includere FOO e BAR come tali nelle tabelle di riferimento. Il che a sua volta richiede più join perché gli attributi FOO e BAR (che portano identificatori di BUSINESS) non sono dove avrebbero potuto essere (e dove molto probabilmente si aspettano di essere, almeno dal punto di vista commerciale).
Erwin Smout,

1
NON sono le "righe" che devono essere univoche, è ciò che l'azienda afferma che devono essere i loro identificatori. Se questa è una combinazione di attributi FOO e BAR, allora è la combinazione di attributi FOO e BAR.
Erwin Smout,

2
Avere l'ID o no non risolve alcun problema di applicazione dell'unicità delle colonne "business" nella tabella. L'applicazione dell'unicità deve essere effettuata dichiarando le chiavi appropriate (cosa che si fa - il fatto che sia stata utilizzata la parola sintattica "VINCITORE" anziché "CHIAVE" non significa che non sia una chiave).
Erwin Smout,
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.