¿Es razonable marcar todas las columnas excepto una como clave principal?

9

Tengo una mesa que representa películas. Los campos son:
id (PK), title, genre, runtime, released_in, tags, origin, downloads.

Mi base de datos no puede contaminarse con filas duplicadas, por lo que quiero imponer unicidad. El problema es que diferentes películas podrían tener el mismo título, o incluso los mismos campos, excepto tagsy downloads. ¿Cómo hacer cumplir la singularidad?

Pensé en dos formas:

  • hacer todos los campos excepto downloadsla clave primaria. Me mantengo downloadsalejado ya que es JSON y probablemente afectará el rendimiento.
  • mantenga solo idcomo clave principal, pero agregue una restricción única con todas las otras columnas (excepto, nuevamente, downloads).

Leí esta pregunta que es muy similar, pero no entendí bien qué debería hacer. Actualmente esta tabla no está relacionada con ninguna otra tabla, pero en el futuro podría estarlo.

Por el momento tengo un poco menos de 20,000 registros, pero espero que el número crezca. No sé si esto es algo relevante para el problema.

EDITAR: modifiqué el esquema y así es como crearía la tabla:

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

También agregué la timestampcolumna, pero eso no es un problema ya que no lo tocaré. Por lo tanto, siempre será automático y único.

rubik
fuente
Pregunta estrechamente relacionada (con respuesta) sobre SO: ¿Necesito una clave principal para mi tabla, que tiene un ÚNICO (compuesto de 4 columnas), una de las cuales puede ser NULL? . Si alguna de las columnas puede ser NULL, considere esto urgentemente: dba.stackexchange.com/q/9759/3684 .
Erwin Brandstetter

Respuestas:

4

La definición de su tabla parece razonable en todo momento. Con todas las columnas, NOT NULLla UNIQUErestricción funcionará como se esperaba, excepto los errores tipográficos y las pequeñas diferencias en la ortografía, lo que puede ser bastante común, me temo. Considere el comentario de @ a_horse .

Alternativa con índice único funcional

La otra opción sería un índice único funcional (similar a lo que comentó @Dave ). Pero usaría un uuidtipo de datos para optimizar el tamaño y el rendimiento del índice.

La conversión de la matriz al texto no es IMMUTABLE(debido a su implementación genérica):

Por lo tanto, necesita una pequeña función auxiliar para declararla inmutable:

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

Úselo para la definición del índice:

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

SQL Fiddle.

Más detalles:

Puede usar el UUID generado como PK, pero igual usaría la serialcolumna con sus 4 bytes, lo cual es simple y económico para referencias FK y otros fines. Un UUID sería una gran opción para sistemas distribuidos que necesitan generar valores PK independientemente. O para mesas muy grandes, pero no hay suficientes películas en nuestro sistema solar para eso.

Pros y contras

Se implementa una restricción única con un índice único en las columnas involucradas. Coloque primero las columnas relevantes en la definición de restricción y tendrá un índice útil para otros fines como beneficio colateral.

Hay otros beneficios específicos, aquí hay una lista:

El índice único funcional es (potencialmente mucho) más pequeño en tamaño, lo que puede hacerlo considerablemente más rápido. Si sus columnas no son demasiado grandes, la diferencia no será demasiado. También existe el pequeño costo indirecto para el cálculo.

La concatenación de todas las columnas puede introducir falsos positivos ( 'foo ' || 'bar' = 'foob ' || 'ar'pero eso parece muy poco probable para este caso. Los errores tipográficos son mucho más probables que puede ignorarlos aquí de forma segura.

Unicidad y arrays

Las matrices tendrían que clasificarse consistentemente para tener sentido en cualquier arreglo único que dependa del =operador porque '{1,2}' <> '{2,1}'. Sugiero tablas de búsqueda para genre, tagy origincon serialPK y entradas únicas, que permiten la búsqueda difusa de elementos de matriz. Entonces:

De cualquier manera, al trabajar con matrices directamente o con un esquema normalizado y una vista materializada, la búsqueda puede ser muy eficiente con el índice y los operadores correctos:

Aparte

Si está utilizando Postgres 9.4 o posterior, considere en jsonblugar dejson .

Erwin Brandstetter
fuente
6

Imagina que sales con un grupo de amigos y la conversación se convierte en películas. Alguien pregunta: "¿Qué pensaste de 'Los tres mosqueteros'?" Respondes, "¿Cuál?"

¿Qué información adicional necesitarías para estar absolutamente seguro de que ambos piensan en la misma película? ¿El nombre del director? El estudio de producción? ¿El año en que fue lanzado? ¿Uno de los nombres de la estrella? ¿Alguna combinación de dos o más?

La respuesta a mi pregunta y la suya son las mismas.

Sin embargo, no creo que ese género sea ​​un buen candidato. Una razón, el género es un criterio demasiado subjetivo. ¿Es la acción de 'Los tres mosqueteros'? ¿drama? ¿aventuras? ¿comedia? ¿acción Aventura? ¿comedia romántica? A menudo veo la misma película en diferentes géneros. Incluso cuando permite múltiples géneros, su usuario puede seleccionar uno completamente diferente que no esté en la lista con la película real que está buscando.

Incluso los tiempos de ejecución pueden diferir, especialmente entre las versiones de teatro y VCR / DVD / b-ray.

Por lo tanto, necesita atributos duros y objetivos que no cambien de un comunicado de prensa a otro. Desafortunadamente, eso puede excluir el nombre de la película ya que se sabe que las películas cambian de nombre, especialmente después del lanzamiento de una secuela.

¿Qué pasa con la fecha de lanzamiento? ¿El estreno teatral de 1993? El lanzamiento de VCR de 1999? ¿El lanzamiento en DVD de 2004? Tienes la idea.

Ahora que lo pienso, ¿qué hay de todas esas películas dirigidas por Alan Smithee? ¿El director real ha dado un paso adelante para poner su nombre en el proyecto después del hecho? No lo sé.

Hmm, mejor me detengo mientras todavía quedan algunos criterios.

Algunos puntos adicionales:

  • Sí, mantenga la clave sustituta y cree un índice único en los campos de clave natural (si finalmente puede precisarlos). La clave sustituta es la mejor para referencias de clave externa. No desea duplicar todos los campos de clave natural en cada tabla que contiene una referencia a una película.
  • Descarte los campos de la matriz (géneros, etiquetas, orígenes). Continúa y normaliza adecuadamente esos atributos. Nunca he visto un campo de matriz que no sea mucho más problemático de lo que valió la pena, especialmente si desea que se puedan buscar ("... where genere = 'horror' ..."). Tenga en cuenta que esto no eliminará automáticamente ningún problema con las diferencias entre mayúsculas y minúsculas ("Ciencia ficción" frente a "Ciencia ficción"), a menos que mantenga correctamente las tablas de búsqueda . Pero es mucho más fácil verificar tales diferencias en un campo de una tabla pequeña que en cada celda de matriz de cada fila de una tabla grande.
TommCatt
fuente
4

La columna de ID no tiene ninguna ventaja en lo que respecta a la singularidad que desea / necesita imponer. La singularidad de cualquier combinación de atributos nunca se aplicará mediante la adición de una ID sin sentido. Su "ventaja" solo se muestra cuando llega al punto en que necesitaría una nueva tabla que necesita una clave externa para esta. En ese caso, y SI ha incluido el Id, puede usarlo como FK en su nueva tabla. (Pero no piense que será un almuerzo gratis. La desventaja de este enfoque es que probablemente se encontrará escribiendo más uniones con el solo propósito de obtener información que bien podría haber sido parte de esa nueva tabla que hizo. )

Erwin Smout
fuente
1
Si las reglas de negocio dicen que la combinación de valores en los atributos FOO y BAR debe ser única, entonces agregar una ID no va a lograr eso. Agregar la ID simplemente facilita evitar tener que incluir FOO y BAR como tal en las tablas de referencia. Lo que a su vez requiere más uniones porque los atributos FOO y BAR (que llevan identificadores de NEGOCIOS) no están donde podrían haber estado (y donde es muy probable que estén, al menos desde el punto de vista comercial).
Erwin Smout
1
NO son las "filas" las que deben ser únicas, es lo que la empresa dice que deben ser sus identificadores. Si esa es una combinación de atributos FOO y BAR, entonces es la combinación de atributos FOO y BAR.
Erwin Smout
2
Tener el ID o no no resuelve ningún problema de aplicación de la unicidad de las columnas "comerciales" en su tabla. El cumplimiento de la unicidad se debe hacer declarando las claves apropiadas (lo cual hace, el hecho de que haya usado la palabra sintáctica "CONSTRAINT" en lugar de "KEY" no significa que no sea una clave).
Erwin Smout