20 de septiembre de 2010

¿Qué es una clave?

Articulo original: What is a Key?
http://weblogs.foxite.com/andykramek/archive/2008/09/03/6656.aspx
Autor: Andy Kramek
Traducido por: Luis María Guayán

La palabra "clave" tiene un sentido muy particular en el diseño de bases de datos relacionales. Se refiere a un campo cuyo valor está compartido entre las tablas. El propósito de esta duplicación de datos es permitir un registro en una tabla esté asociado con un registro a otra, lo que permite la extracción de información que está distribuida entre las tablas. Esta asociación se denomina como una "relación" y es lo que diferencia las bases de datos relacionales, de las bases de datos de archivos planos.

Hay dos tipos de claves relacionales:
  • PRIMARIA (PRIMARY) Una clave primaria es la columna, o una combinación de columnas, cuyo valor identifica de forma exclusiva un único registro dentro de una tabla. Ni la clave primaria, ni ninguna parte de ella, puede tener un valor NULO
  • FORANEA (FOREING) Una clave foránea es la columna, o una combinación de columnas en una tabla cuyo valor identifica a un solo (es decir, relacionado) registro de otra tabla. Una clave foránea puede, bajo algunas circunstancias, tener un valor NULO que indica que no hay registros relacionados.
Cuando definimos una relación entre dos tablas, la tabla que contiene la clave primaria es la "Tabla Padre" (también conocida como "tabla de referencia") y la tabla que contiene la clave foránea es la "Tabla Hija" (también conocida como el "tabla referenciada"). Por lo tanto, la clave foránea hace referencia a la primaria, y por ende, define cómo los registros en las tablas están relacionados entre sí.

Es una regla fundamental que todas las tablas deben tener una clave primaria. Esto es evidente si consideramos una tabla que contiene los nombres verdaderos de mis familiares directos que se enumeran a continuación.
Nombre      Apellido
Stanley     Kramek  
Elaine      Kramek  
Andrew      Kramek  
Richard     Kramek  
Angela      Kramek  
Esto está bien y muestra mi padre (Stanley) y mi madre (Elaine), junto con mi hermano menor (Richard) y mi hermana (Angela). La clave primaria de esta tabla es la combinación de nombre-apellido y es perfectamente permitido. Sin embargo, no es un buen diseño, porque cuando mi hermano se casó con una muchacha llamada "Angela" (y sí, realmente así fue) nos encontramos con un problema cuando intentamos agregar a mi nueva cuñada a esta tabla:
Nombre      Apellido
Stanley     Kramek  
Elaine      Kramek  
Andrew      Kramek  
Richard     Kramek  
Angela      Kramek  
Angela      Kramek  
Ahora no tenemos manera de distinguir entre mi hermana y mi cuñada! Esta tabla ya no tiene una clave primaria y en ausencia de cualquier información adicional es realmente inútil. Por supuesto, la solución obvia es añadir algo adicional, diferente, tal vez la "inicial" del segundo nombre, a pesar de que esta no es una buena opción, sobre todo porque no todo el mundo tiene uno, y el valor aún podría no ser único, y en este caso no lo es (el segundo nombre de mi hermano es "Maurice", el de mi hermana es "Mary" y el de mi cuñada es "Margaret").
Nombre      Apellido       Inicial
Stanley     Kramek    
Elaine      Kramek    
Andrew      Kramek         E
Richard     Kramek         M
Angela      Kramek         M
Angela      Kramek         M
El punto de este ejemplo, es que es sumamente importante definir una clave primaria para cada tabla, de forma de que cada registro pueda ser único e identificado inequívocamente. Hasta ahora hemos definido la clave primaria como una combinación de columnas que se utilizan para almacenar los datos. Esto se conoce como una clave "compuesta".

El primer problema con las claves compuestas (como lo muestra este ejemplo), es que es perfectamente posible terminar con valores duplicados en varias columnas y esas claves tienden a tornarse cada vez más complejas, como datos son agregados a la base de datos.

Esto plantea un segundo problema para las claves compuestas porque cualquier tabla que haga referencia a una tabla que tiene un una clave primaria compuesta debe llevar todos los elementos de la clave. Por lo tanto, a fin de vincular cualquier otra información en la tabla de mi familia, tendría que duplicar toda la información en la otra tabla, haciendo terriblemente difícil la tarea de actualizar la información.

Una opción mucho mejor sería usar algún valor único asociado a cada persona y que se pueden almacenar en una sola columna y, afortunadamente, nosotros tenemos algo disponible, nuestro Número de Seguro Social (SSN). Los SSN se pueden almacenar en una sola columna y tienen un valor que es único para cada persona. Esto, finalmente, nos permite identificar cada registro en la tabla sin ambigüedades. Por lo tanto, es una elección perfectamente natural para la clave primaria, en cualquir tabla que contiene información sobre personas.

De hecho, el Número de Seguro Social es un ejemplo de lo que se llama clave "Natural" (también conocida como clave "inteligente" o clave de "Negocio") y que en este ejemplo, funcionará muy bien y nosotros finalmente la utilizaremos como se muestra en la nueva versión de la tabla:
Nombre      Apellido       Inicial       SSN
Stanley     Kramek                       814-56-8975
Elaine      Kramek                       823-22-4578
Andrew      Kramek         E             866-41-8537
Richard     Kramek         M             866-77-5411
Angela      Kramek         M             866-23-8544
Angela      Kramek         M             822-39-6425
¡Pero aguarde! ¿Los Números de Seguro Social son realmente únicos? La respuesta es "NO". De hecho hay reglas que definen lo que constituye un SSN válido y, por consiguiente, hay un límite finito sobre el número de SSN realmente únicos que pueden existir, además, hay ciertos casos de registros duplicados de SSN que se emitieron.

Esta situación pone de manifiesto el primero de varios problemas asociados con el uso de claves naturales ¿son realmente únicas? Por lo tanto, con el fin de poder ser utilizada como una clave primaria, la candidata debe poseer tres atributos:
  • Unicidad: El valor debe ser único en la tabla
  • Consistencia: El valor debe ser capaz de ser validado en términos del tipo de datos a lo que está asignado. En otras palabras, el valor especificado debe ser compatible con las reglas para la base de datos. Por ejemplo, podemos estar seguros de que "35/45/2009" es una fecha no válida, pero es imposible determinar si "123456789" es un Número de Seguro Social que no tiene los guiones que lo forman, o algún otro valor.
  • Verificabilidad: El valor de la clave natural se refiere a alguna entidad real y por lo tanto tenemos que estar seguros de que la entidad realmente existe. Por ejemplo, sabemos que un Número de Seguro Social es inválido si los tres primeros dígitos son mayores que "770", pero no podemos determinar si "123-45-6789" es un Número de Seguro Social "verdadero" o no.
Sin embargo, estas no son las cuestiones más importantes asociados con el uso de claves naturales como claves primarias. El verdadero problema con estas, es que se rigen por reglas que no tienen nada que ver con su uso como una clave primaria, por lo que están sujetas a cambios. Recuerde la definición de una clave primaria, es que debe ser única en una tabla y no puede ser NULA. Sin embargo, las normas que rigen los Números de Seguro Social, números de identificación de vehículos, números de cuenta, números de factura o de orden, o cualquiera de muchas otras claves "naturales" no tienen nada que ver con "ser únicas en una tabla", que es el requisito fundamental para una clave primaria.

¿Importa esto? ¡Claro que sí! Dado que los valores se rigen por reglas que se refieren a su función principal, estos están sujetos a las restricciones que son irrelevantes en el contexto de una clave primaria. Por ejemplo, un requisito muy común para los números de Factura es que deben ser una secuencia ininterrumpida. Si un número de factura se utiliza como clave primaria, el sistema tiene que asegurarse de que los usuarios no asignen un número de factura que entre en conflicto con esa regla, y evitar que los usuarios asignen un número, y luego cancelen la factura, a pesar de que no hay ninguna diferencia en que la claves primarias tengan una secuencia ininterrumpida o no (siempre y cuando sean únicas en la tabla). Administrar una secuencia ininterrumpida para una clave primaria introduce una complejidad innecesaria (con posibilidades de error) en el sistema.

El cambio es, por supuesto, otro tema. Como las claves naturales reflejan entidades reales, estas están sujetas a cambio. Las claves primarias también se almacenan (como una clave foránea) en todas las tablas relacionadas, por lo que es imperativo que cualquier cambio en el actual valor de una clave primaria, debe reflejarse en todos los registros relacionados. Esto puede tener un gran impacto en una base de datos, ya que requerirá que todos los índices que participen en la clave deben ser reconstruidos, e incluso se puede forzar una reordenación física de los datos en la base de datos. Ahora, usted puede estar preguntándose, en este punto, ¿por qué una clave natural usada como una clave primaria se cambiaría alguna vez?. La razón más común es, por supuesto, "error humano". Esto nos lleva a un último tema con las claves naturales, que es la forma en que se capturan en el sistema.

Las claves naturales por definición, no pueden ser generadas por la base de datos. Estas son, casi siempre, ejemplos de datos que ni siquiera pueden ser derivados y deben ser ingresados directamente por algún usuario (el Número de Seguro Social es un buen ejemplo de esto, ya que el último Número de Seguro Social ingresado en el sistema es "876-11-1655" ¿Cuál será el siguiente? Por supuesto, no hay manera de saberlo!).

La cuestión es que hay un límite en la cantidad de validaciones y verificaciones que se pueden realizar, y por lo tanto, es perfectamente posible que se puedan introducir errores en el sistema. Es axiomático que en el contexto de datos, no existe ninguna solución de software para "válido pero erroneo". En otras palabras, no importa cuantas validaciones y comprobaciones hagamos, no podemos detectar que el SSN "123-45-6789" realmente se han introducido como "123-45-6798"!

Por supuesto, es probable que estos errores, a la larga puedan ser capturados. Entonces estos tienen que ser corregidos con todos los problemas que esto plantea. De hecho, en aplicaciones basadas en claves naturales, las rutinas para el manejo de cambios y modificaciones a los valores claves suelen ser los códigos más complejos y difícilesde todo el sistema. Entonces, ¿cuál es la alternativa? Utilice una "clave sustituta"

Una clave sustituta es simplemente una columna añadida a la tabla, cuya única función es la de ser la clave primaria para la tabla. Tipicamente, estas columnas son simples números enteros y la mayoria de las principales bases de datos facilitan la auto-generación de estos valores. En SQL Server estas columnas se llaman "Identity", en VFP "campo autoincrementale" y en otras bases de datos usan otros nombres, pero los principios son los mismos.

Todas las cuestiones señaladas anteriormente respecto de las claves compuestas y claves naturales, desaparecen cuando una clave primaria sustituta se utiliza. Como los registros se añaden a una tabla, la propia base de datos genera el siguiente valor para que la tabla lo asigne al nuevo registro. No hay problemas porque el valor actual no tiene importancia sea cual fuera, simplemente tiene que ser único en la tabla y la propia base de datos garantiza que esto se haga correctamente.

Esto también significa que la claves sutitutas nunca necesiten cambiar. Dado que el valor actual nunca importa, no hay ninguna razón para cambiarlo. Además, dado que el valor se genera como un entero, se requiere muy poco espacio (4 bytes) y es de fácil indexarlo y accederlo, con lo que la creación y el mantenimiento de las relaciones es mucho más eficiente.

En el contexto de normalización. Las claves sustitutas tienen la ventaja de no requerir ningún análisis más profundo que la Forma Normal de Boyce-Codd (FNBC), extensión de la tercera forma normal. (Esto se debe a que todas las reglas de normalización adicionales se aplican sólo a los casos en que la clave principal está compuesta por más de una columna).

En resumen, NO hay realmente razones para no usar claves sustitutas como claves primarias en todas las tablas en una base de datos relacional. En aquellos casos especiales, en que los datos deben generarse en distintos lugares o combinados más tarde, o cuando las claves deben ser generadas en el cliente, en lugar de en el servidor, Los GUID proporciona una mejor opción que los simples enteros como claves primarias. Pero el cambio del tipo de datos, no afecta el principio de que las claves sustitutas son la mejor manera de definir las claves primarias.


Comentarios (NdR: a la fecha de traducción del artículo)

03/09/2008 15:41 por Dale
Hola Andy, en mi tabla, uso SYS(2015) para generar claves sustitutas. Esto es mi granito de arena. Saludos.
Bueno, si eso funciona para usted, está muy bien, pero, con todo el debido respeto, no es realmente una muy buena idea y desde luego, va en contra de las mejores prácticas aceptadas. Aparte de las cuestiones de utilizar una cadena de caracteres como clave primaria (más espacio de almacenamiento y complica la creación de JOINS, sobre todo en VFP con su lógica difusa), no hay una funcionalidad equivalente a SYS(2015) en otras bases de datos, de modo que no será capaz de llevar su modelo fácilmente a cualquier otra interfaz final.

03/09/2008 23:47 por RVBoy
Un problema con las columnas Identity es que al guardar un nuevo registro de VFP, usted no sabe que valor Identity se ha generado para éste. Lo que significa que no puede identificar de manera inequívoca cual registro es en la base de datos sin un ida y vuelta basado en alguna otra clave simple o compuesta para identificar el registro en particular.

Mmmm... ¿Tal vez usted no conoce acerca de @@IDENTIY y SCOPE_IDENTITY() en SQL Server? Estos retornan el último valor Identity entregado por la conexión, y por el comando inicial, respectivamente. Usted necesita una segunda llamada al servidor, pero desde luego no es necesario ninguna otra clave simple o compuesta para identificar al registro. 
Este tomará el último Identity que se ha concedido a un tabla en la tu conexión: 
SQLEXEC( nCon, 'SELECT scope_identity() AS lastkey', 'cur_lastkey' ) 
Algunas personas le buscan la vuelta a esto mediante la utilización de un procedimiento almacenado para generar un valor auto incrementedo que puede ser guardado con el nuevo registro.

Sé que usted ya lo sabe ;-), pero pensé que era bueno de mencionarlo en el contexto de su excelente resumen.

Como ya lo dije, use SCOPE_IDENTITY() y no necesitará nada de eso. Pero de todas formas le agradezco las amables palabras.

04/09/2008 14:11 por Craig Berntson
Fui a la universidad con alguien que tenía el mismo Número de Seguro Social que otro estudiante en la misma universidad. El gobierno había emitido el mismo número a dos personas y ninguno de ellos lo sabía que hasta que lleguaron a la universidad.

Oh sí, esto no es realmente tan inusual, aparte de cualquier otra cosa, los SSN han sido reutilizados. Se registran casos en que los números expedidos a las personas que se cree que han muerto fueron reemitidos, pero el propietario original en realidad sigue vivo ... Resultado: confusión!

04/09/2008 15:48 por Mike Yearwood
Andy
Esta es una muy buena explicación escrita!

Gracias Mike.
Lo que me sorprende es que muchas personas no utilizan GUID. Como le ha señalado a RVBoy, en caso de la necesidad de conocer la última clave, uno tiene que hacer otro viaje al servidor con algo así como SQLEXEC (nCon, 'SELECT scope_identity () AS lastkey', 'cur_lastkey'). Los usuarios que no usan GUID siempre parecen preocuparse por el tamaño de un GUID (16 bytes o 128 bits) frente a los 4 bytes / 32 bits de número entero, pero el número de bytes y el tiempo necesario para obtener la última clave parece que superan la diferencia entre GUID y entero, no?

Bueno, yo no suele utilizar GUID como claves primarias, pero no por las razones que da aquí. Siempre he sido un firme creyente en la separación de responsabilidades en un entorno de aplicación. Visto desde esa perspectiva, es evidente que la definición y asignación de claves primarias no tiene nada que ver con el cliente, que pertenece totalmente a la base de datos y debe ser manejado por la base de datos. Ya sea que usted utilice enteros, GUID o alguna otra cosa es (conceptualmente de todos modos) irrelevante. Sin embargo, en mi opinión, el autoincrementar enteros es la manera más eficaz de gestionar claves primarias (aunque sólo sea porque la mayoría de bases de datos están optimizadas para el uso de JOINS basados en claves enteras) a menos que existan circunstancias especiales que dicten un enfoque alternativo. 
En cuanto al tiempo, me parece que es que es una pequeña parte del procesamiento que es realmente irrelevante. Lo que sí veo es que tratando de trabajar interactivamente con GUIDs es un real dolor de cuello! Sólo intentar ver en una cuadrícula es difícil, y mas para tratar de compararlos visualmente ..... Aaaaaagh!
Cuando usted necesita un GUID entonces no hay nada nada más que hacer, estoy de acuerdo. Sin embargo usar un GUID de forma rutinaria para claves primarias es, en mi opinión, una mala práctica.

04/09/2008 18:25 por Mike Yearwood
Muchos consideran el uso GUID como "buena práctica". En esta época de servicios Web es el momento adecuado. "La separación de responsabilidades" implica que la reponsibilides se pueden poner donde sea necesario para el balanceo de cargas. Que quede claro. Cada responsabilidad tiene que ser abordada. Garantizar que las claves sean únicas, sólo puede garantizarse en el servidor. Las claves se pueden generar en el servidor o en el cliente. Una vez que los valores enteros son lo suficientemente grandes, también, se convierten en un dolor de cuello. Por lo tanto, para mí, hay poca razón para evitar el GUID.
Gracias!

Su opinión es, por supuesto, suya y tienen pleno derecho a ella, al igual que yo con la mía. Si usted lo elige, de acuerdo, ya que es su prerrogativa. Ya he dicho, en repetidas ocasiones, que hay situaciones en las que las GUID son apropiadas e incluso necesarias. Sin embargo, eso no quiere decir que son apropiadas, todas las hipótesis. Todo lo que estoy diciendo es que "pueden hacer", que no es lo mismo que "DEBE HACERSE".

04/09/2008 23:52 por Joey
Como siempre, muy esclarecedor. Yo se que Ud. esta publicando estas "lecciones" gratuitamente, pero yo deseo que usted tenga tiempo para más.
Gracias.

De nada. Comentarios como este hacen que todo esto valga la pena. Has dado en el clavo con respecto al tiempo. Nunca hay lo suficiente como para cubrir todas las cosas que me gustaría, pero voy a tratar de publicar ocasionalmente mas en el futuro.

08/09/2008 02:23 por RVBoy
Andy, scope_identity() está bien para SQL Server Standard / Express ... pero no para SQL Server Compact Edition (con el que hemos venido experimentando como reemplazo para los cursores de VFP como consejo de otro MVP) u otras bases de datos.

Lo siento, pero usted sólo se refirio a "SQL". Si usted quiere un SQL Server gratuito para sus datos, por qué demonios no usa SQL 2005 Express (que no tiene las limitaciones de Compact Edition)? De todos modos, Compact Edición soporta @@IDENTITY, por lo que aun puede retornar el último valor de Identity usado.

08/09/2008 02:29 por RVBoy
Además del mensaje anterior: La administración de Identity en varias opciones de base de datos (por ejemplo, SQL Server/SSCE/MySQL/Oracle) puede ser muy interesante para seguir!

Interesante idea. Cuando tengo un par de años libres, podría ponerme a trabajar en ello. :-)

09/09/2008 12:48 por RVBoy

  • @@Identity no es fiable para encontrar un Identity en SQL Standard o Express debido a que los desencadenantes pueden causar el retorno de valores erróneos.
  • @@Identity puede ser utilizado con seguridad en SSCE porque no tiene desencadenantes.
  • Las demás bases de datos tienen diferentes mecanismos para lograr un resultado similar.
  • Si sólo selecciona una base de datos como backend, puede utilizar un mecanismo específico para obtener un Identity, en algunos casos sin necesidad de un viaje de ida y vuelta.
  • Sin embargo, si usted está creando un código VFP genérico que tiene que trabajar con diferentes backends, puede crear su propio autoincremental o un GUID como lo planteado por Mike Y, o tal vez el mecanismo de clave compuesta que he mencionado, sin embargo, esta sujeto a riesgos de los tipos identificados por Craig B.
Eso es todo. En absoluto esto es una crítica, yo pienso que su artículo fue muy directo y pertinente.

Gracias.

No hay comentarios. :

Publicar un comentario