¿Por qué no deberíamos permitir NULL?

125

Recuerdo haber leído este artículo sobre diseño de bases de datos y también recuerdo que decía que debería tener propiedades de campo de NOT NULL. Sin embargo, no recuerdo por qué este fue el caso.

Todo lo que puedo pensar es que, como desarrollador de aplicaciones, no tendrías que probar NULL y un posible valor de datos inexistente (por ejemplo, una cadena vacía para cadenas).

Pero, ¿qué hace en el caso de fechas, fecha y hora (SQL Server 2008)? Tendría que usar una fecha histórica o de fondo.

¿Alguna idea sobre esto?

Thomas Stringer
fuente
44
Esta respuesta tiene una idea sobre el uso de NULL dba.stackexchange.com/questions/5176/…
Derek Downey
10
De Verdad? ¿Por qué RDBMS nos permite usar NULL en absoluto, si no deberíamos usarlos? No hay nada de malo en NULL siempre que sepa cómo lidiar con ellos.
Fr0zenFyr
3
¿Fue este un modelado de datos de BI? Por lo general, no debe permitir nulos en las tablas de hechos ... de lo contrario, los nulos son sus amigos cuando se usan correctamente. =)
sam yi
2
@ Fr0zenFyr, solo porque un RDBMS nos permita hacer algo, no es necesariamente una buena idea hacerlo. Nada nos obliga a declarar una clave primaria o una clave única en una tabla, pero con algunas excepciones lo hacemos de todos modos.
Lennart
3
Creo que un tratamiento completo de este tema debería hacer referencia al requisito original de Codd de que un RDBMS debe tener una forma sistemática de tratar los datos faltantes. En el mundo real, hay situaciones en las que se crea una ubicación para los datos, pero no hay datos para incluir. El Arquitecto de datos tiene que dar alguna respuesta a esto, ya sea que se trate de diseño de bases de datos, programación de aplicaciones o ambos. El SQL NULL es menos que perfecto para cumplir este requisito, pero es mejor que nada.
Walter Mitty

Respuestas:

230

Creo que la pregunta está mal formulada, ya que la redacción implica que ya has decidido que los NULL son malos. Quizás quisiste decir "¿Deberíamos permitir NULL?"

De todos modos, aquí está mi opinión: creo que los NULL son algo bueno. Cuando comienzas a prevenir NULLs solo porque "NULLs son malos" o "NULLs son difíciles", comienzas a inventar datos. Por ejemplo, ¿qué pasa si no sabes mi fecha de nacimiento? ¿Qué vas a poner en la columna hasta que lo sepas? Si eres como mucha gente anti-NULL, vas a ingresar 1900-01-01. Ahora me colocarán en la sala geriátrica y probablemente recibiré una llamada de mi estación de noticias local felicitándome por mi larga vida, preguntándome mis secretos para vivir una vida tan larga, etc.

Si se puede ingresar una fila donde es posible que no se conozca el valor de una columna, creo que NULL tiene mucho más sentido que elegir un valor de token arbitrario para representar el hecho de que es desconocido, un valor que otros tendrán ya tiene que saber, realizar ingeniería inversa o preguntar para averiguar qué significa.

Sin embargo, hay un equilibrio: no todas las columnas de su modelo de datos deben ser anulables. A menudo hay campos opcionales en un formulario, o piezas de información que de otro modo no se recopilarían en el momento en que se crea la fila. Pero eso no significa que pueda diferir el llenado de todos los datos. :-)

Además, la capacidad de usar NULL puede verse limitada por requisitos cruciales en la vida real. En el campo médico, por ejemplo, puede ser un asunto de vida o muerte saber por qué se desconoce un valor. ¿La frecuencia cardíaca es NULA porque no había pulso o porque aún no la hemos medido? En tal caso, ¿podemos poner NULL en la columna de frecuencia cardíaca y tener notas o una columna diferente con un motivo NULL porque?

No tenga miedo de los NULL, pero esté dispuesto a aprender o dictar cuándo y dónde deben usarse, y cuándo y dónde no deben usarse.

Aaron Bertrand
fuente
3
"algún valor simbólico arbitrario para representar el hecho de que es desconocido" esto se conoce como un valor centinela
Alexander
44
Pero, ¿qué le impide crear una tabla separada birth_datedonde almacena las fechas de nacimiento? Si se desconoce la fecha de nacimiento, simplemente no inserte la fecha de nacimiento birth_date. Los nulos son un desastre.
Eldar Agalarov
66
@EldarAgalarov Eso suena como el razonamiento de Trump (¿"desastre" por qué? ¿Cómo? ¿Para quién? Tu opinión de que algo es un "desastre" no lo hace así). De todos modos, la fecha de nacimiento es solo un ejemplo. Si tiene personal o miembros o clientes que tienen 15 columnas potencialmente anulables, ¿va a crear 15 tablas secundarias? ¿Qué pasa si tienes 50? ¿Qué pasa si su tabla de hechos DW tiene 500? El mantenimiento para mantener grandes NULLs de miedo y nulos fuera de su base de datos se vuelve 10 veces más malo que cualquier "desastre" al que le tenga miedo ...
Aaron Bertrand
3
@AaronBertrand si su tabla tiene 15 columnas potencialmente anulables, huele realmente mal ^^ No es que una gran cantidad de columnas sea inherentemente mala, pero puede indicar un mal diseño O una desnormalización requerida. Pero planteará preguntas.
programaths
2
@Wildcard ¿Entonces nunca ha visto a la gente almacenar 1900-01-01para evitar tener un valor de fecha / hora NULL? OK entonces. Además, NULL = desconocido y desconocido = falso. No estoy seguro de qué problemas podría causar esto, aparte de que las personas no nacen sabiendo eso (como si no nacieran sabiendo muchas cosas inherentes a un RDBMS complejo). Nuevamente, agitando las manos y diciendo "¡Problema! ¡Desastre!" No lo hace así.
Aaron Bertrand
57

Las razones establecidas son:

  • NULL no es un valor y, por lo tanto, no tiene ningún tipo de datos intrínsecos. Los nulos necesitan un manejo especial en todo el lugar cuando el código que de otra manera se basa en tipos reales también podría recibir el NULL sin escribir.

  • NULL rompe la lógica de dos valores (familiar verdadero o falso) y requiere una lógica de tres valores. Esto es mucho más complejo de implementar incluso correctamente, y ciertamente la mayoría de los DBA y casi todos los que no son DBA lo entienden mal. Como consecuencia, invita positivamente a muchos errores sutiles en la aplicación.

  • El significado semántico de cualquier NULL específico se deja a la aplicación , a diferencia de los valores reales.

    Semánticas como "no aplicable" y "desconocido" y "centinela" son comunes, y también hay otras. Con frecuencia se usan simultáneamente dentro de la misma base de datos, incluso dentro de la misma relación; y, por supuesto, son significados no explícitos, indistinguibles e incompatibles .

  • No son necesarios para las bases de datos relacionales , como se argumenta en "Cómo manejar la información que falta sin nulos" . Una mayor normalización es un primer paso obvio para intentar deshacerse de una tabla de NULL.

Esto no significa que NULL nunca deba permitirse. Se tiene que recordar que hay muchas buenas razones para no permitir NULL siempre que sea posible.

Significativamente, aboga por esforzarse mucho, a través de un mejor diseño de esquema y mejores motores de base de datos, e incluso mejores lenguajes de bases de datos, para hacer posible evitar NULL con más frecuencia.

Fabian Pascal responde a una serie de argumentos, en "Nulls Nullified" .

nariz grande
fuente
3
Su enlace a "Cómo manejar la información faltante sin nulos" muestra muy bien por qué no podemos prescindir de los nulos: varias de las sugerencias serían imposibles de implementar de manera racional en los principales RDBMS tal como están actualmente.
Jack Douglas
77
Jack: Sí, pero “las implementaciones actuales no puede hacerlo” no es un argumento a favor del status quo :-)
bignose
17
¿Es como decir que no debemos volar porque los aviones no son perfectos?
Aaron Bertrand
11
No, está diciendo que los vendedores deberían dejar de invocar excusas para los nulos que podrían haber sido válidos hace cuarenta años, pero que han sobrevivido a su período de retención razonable. Los tiempos de E / S ya no están en el orden de magnitud de 80 ms. Los ciclos de CPU individuales ya no están en el orden de magnitud de microsegundos. Los límites de memoria ya no son del orden de magnitud de unos pocos Megs. A diferencia de hace cuarenta años, las velocidades y capacidades de hardware necesarias para trabajar sin valores nulos ahora sí existen y el costo no es prohibitivo. Él dice que es hora de seguir adelante.
Erwin Smout
2
El enlace "NULL confusion" está muerto.
jpmc26
32

No estoy de acuerdo, los nulos son un elemento esencial del diseño de la base de datos. La alternativa, como también aludiste, sería una proliferación de valores conocidos para representar lo que falta o lo desconocido. El problema radica en que nulo es tan ampliamente incomprendido y, como resultado, se usa de manera inapropiada.

IIRC, Codd sugirió que la implementación actual de nulo (es decir, no presente / faltante) podría mejorarse con dos marcadores nulos en lugar de uno, "no presente pero aplicable" y "no presente y no aplicable". No puedo imaginar cómo los diseños relacionales mejorarían personalmente.

Mark Storey-Smith
fuente
2
Sugiero tener un conjunto definido por el usuario de diferentes tipos de null, y una lógica multivalor definida por el usuario para acompañarlos: p
Jack Douglas
13
Esas no son las únicas opciones. Excluye la alternativa de normalización: en lugar de columnas que pueden tener o no un valor, use otra tabla que pueda tener o no una fila correspondiente para la primera tabla. El significado de la presencia o ausencia de una fila se supuso en el sentido de las tablas, y no hay especial-carcasa de nulo o centinela valores etc.
bignose
77
La presencia de NULL no requiere valores especiales de carcasa o centinela. Esos son solo síntomas de cómo algunas personas deciden lidiar con NULL.
Aaron Bertrand
Vale la pena señalar que '' es distinto de nulo en PostgreSQL (aunque no Oracle) y, por lo tanto, le da un marcador doble, y podría usar 0 para columnas numéricas. Sin embargo, el problema con 0 es que no funciona para claves externas.
Chris Travers
13

Permítanme comenzar diciendo que no soy un DBA, soy un desarrollador de memoria y mantengo y actualizo nuestras bases de datos en función de nuestras necesidades. Dicho esto, tuve la misma pregunta por varias razones.

  1. Los valores nulos hacen que el desarrollo sea más difícil y propenso a errores.
  2. Los valores nulos hacen que las consultas, los procedimientos almacenados y las vistas sean más complejas y propensas a errores.
  3. Los valores nulos ocupan espacio (? Bytes basados ​​en una longitud de columna fija o 2 bytes para una longitud de columna variable).
  4. Los valores nulos pueden y a menudo afectan la indexación y las matemáticas.

Paso mucho tiempo analizando las numerosas respuestas, comentarios, artículos y consejos en Internet. No hace falta decir que la mayor parte de la información era casi la misma que la respuesta de @ AaronBertrand. Por eso sentí la necesidad de responder a esta pregunta.

En primer lugar, quiero aclarar algo para todos los futuros lectores ... Los valores NULL representan datos desconocidos, NO datos no utilizados. Si tiene una tabla de empleados que tiene un campo de fecha de finalización. Un valor nulo en la fecha de finalización se debe a que es un campo requerido en el futuro que actualmente se desconoce. Cada empleado, ya sea activo o despedido, en algún momento tendrá una fecha agregada a ese campo. Esa es, en mi opinión, la única razón para un campo Nullable.

Dicho esto, la misma tabla de empleados probablemente contenga algún tipo de datos de autenticación. Es común en un entorno empresarial que los empleados aparezcan en la base de datos de recursos humanos y contabilidad, pero no siempre tienen o necesitan detalles de autenticación. La mayoría de las respuestas lo llevarán a creer que está bien anular esos campos o, en algunos casos, crear una cuenta para ellos, pero nunca enviarles las credenciales. ¡El primero hará que su equipo de desarrollo escriba código para verificar NULL y tratarlos en consecuencia y el segundo representa un gran riesgo de seguridad! Las cuentas que aún no se usan en el sistema solo aumentan la cantidad de puntos de acceso posibles para un hacker, además de que ocupan un valioso espacio en la base de datos para algo que nunca se usa.

Dada la información anterior, la mejor manera de lidiar con los datos anulables que se utilizarán es permitir valores anulables. Es triste pero cierto y tus desarrolladores te odiarán por ello. El segundo tipo de datos anulables debe colocarse en una tabla relacionada (IE: cuenta, credenciales, etc.) y tener una relación uno a uno. Esto permite que un usuario exista sin credenciales a menos que sean necesarias. Esto elimina el riesgo adicional de seguridad, el valioso espacio de la base de datos y proporciona una base de datos mucho más limpia.

A continuación se muestra una estructura de tabla muy simplista que muestra tanto la columna anulable requerida como una relación uno a uno.

Relación anulable y uno a uno desconocida

Sé que llego un poco tarde a la fiesta ya que esta pregunta se hizo hace años, pero espero que esto ayude a arrojar algo de luz sobre este tema y la mejor manera de tratarlo.

Nicholas Aguirre
fuente
2
Simplemente lo cambiaría para que no haya ninguno TerminationDateen los registros de los empleados, pero tenga una tabla para la TerminatedEmployeecual los empleados sean trasladados (no copiados) por la aplicación cuando sean despedidos. Obviamente, esto funciona bien con la tabla Cuenta porque no habrá una cuenta vinculada en la TerminatedEmployeetabla. Si todavía necesita los números de teléfono, invertiría las claves foráneas para que las tablas de empleados y empleados despedidos tengan la identificación del número de teléfono en lugar de lo contrario.
Programador
2
Literalmente podría continuar por días sobre por qué esto sería malo. Tablas redundantes, malas prácticas de SQL, lo que hace que sus desarrolladores tengan que buscar en dos lugares los datos de los empleados, problemas con los informes, problemas con URI directos a un empleado que no existe (se ha movido), y la lista continúa y en. Está completamente bien tener NULLS para los campos que algún día tendrán un valor, es otra historia tener campos que nunca se llenan y nunca tienen un uso. Una serie de posibles problemas y soluciones para hacer que esto funcione no valdría la pena el pequeño problema de verificar NULL en un campo.
Nicholas Aguirre
1
Estoy en desacuerdo. Lo único redundante es ese campo nulo para la fecha de finalización que tal vez nunca se complete. Los desarrolladores solo tienen que buscar en la tabla adecuada los datos que desean y podrían mejorar el rendimiento. Si, por alguna razón, desea empleados despedidos y no despedidos, se resuelve mediante una unión, pero el 90% de las veces su aplicación probablemente querrá uno u otro. Creo que el diseño que especifiqué es mejor porque sería imposible tener una fecha de finalización para un empleado y que todavía tenga una cuenta.
Programador
2
No dije datos redundantes, dije tablas redundantes. Además, cualquier cambio en las tablas de empleados debe llegar a las tablas terminadas; Esto hace que la aplicación sea propensa a errores y hace que el trabajo del desarrollador sea mucho más difícil. Además, se completará un campo de Fecha de finalización para casi todos. Es inútil y problemático crear una segunda estructura de tabla idéntica y también mover datos. No incluir la prueba cada vez para asegurarse de que los datos de la tabla se movieron y limpiaron. Es una mala práctica eliminar datos de una tabla, aunque solo sea para moverlos. Si le preocupa tanto un solo campo que ...
Nicholas Aguirre
1
... que casi siempre se completará a tiempo, luego haga una tabla terminada con una relación 1to1 de regreso al empleado. Trabajo con una variedad de bases de datos todo el día, tanto como DBA como desarrollador, y me alegra no haber encontrado una con la estructura que usted propuso. Especialmente desde el punto de vista de un desarrollador, sería una pesadilla escribir y verificar todos los errores porque no sabría de qué tabla provenía. Incluso al escribir una unión, los datos devueltos al software tendrían un campo con datos nulos que aún requeriría que lo pruebe también.
Nicholas Aguirre
13

Además de todos los problemas con los desarrolladores confusos de NULL, los NULL tienen otro inconveniente muy serio: el rendimiento

Las columnas NULL'able son un desastre desde una perspectiva de rendimiento. Considere la aritmética de enteros como un ejemplo. En un mundo sano sin NULL, es "fácil" vectorizar la aritmética de enteros en el código del motor de la base de datos utilizando las instrucciones SIMD para realizar prácticamente cualquier cálculo a velocidades superiores a 1 fila por ciclo de CPU. Sin embargo, en el momento en que introduce NULL, debe manejar todos los casos especiales que crea NULL. Los conjuntos de instrucciones de CPU modernos (léase: x86 / x64 / ARM y lógica de GPU también) simplemente no están equipados para hacer esto de manera eficiente.

Considere la división como un ejemplo. En un nivel muy alto, esta es la lógica que necesita con un entero no nulo:

if (b == 0)
  do something when dividing by error
else
  return a / b

Con NULL, esto se vuelve un poco más complicado. Junto con busted necesitará un indicador si bes nulo y de manera similar para a. El cheque ahora se convierte en:

if (b_null_bit == NULL)
   return NULL
else if (b == 0) 
   do something when dividing by error
else if (a_null_bit == NULL)
   return NULL
else 
   return a / b

La aritmética NULL es significativamente más lenta para ejecutarse en una CPU moderna que la aritmética no nula (por un factor de alrededor de 2-3x).

Empeora cuando presentas SIMD. Con SIMD, una CPU Intel moderna puede realizar 4 divisiones enteras de 32 bits en una sola instrucción, como esta:

x_vector = a_vector / b_vector
if (fetestexception(FE_DIVBYZERO))
   do something when dividing by zero
return x_vector;

Ahora, también hay formas de manejar NULL en SIMD land, pero esto requiere el uso de más vectores y registros de CPU y hacer un poco de enmascaramiento de bits inteligente. Incluso con buenos trucos, la penalización de rendimiento de la aritmética de enteros NULL se desliza en el rango más lento 5-10x incluso para expresiones relativamente simples.

Algo como lo anterior vale para los agregados y, en cierta medida, también para las uniones.

En otras palabras: la existencia de NULL en SQL es un desajuste de impedancia entre la teoría de la base de datos y el diseño real de las computadoras modernas. Hay una razón bastante buena por la que NULL confunde a los desarrolladores, porque un número entero no puede ser NULL en la mayoría de los lenguajes de programación sanos, así no es cómo funcionan las computadoras.

Thomas Kejser
fuente
10

Preguntas interesantes

Todo lo que puedo pensar es que, como desarrollador de aplicaciones, no tendrías que probar NULL y un posible valor de datos inexistente (por ejemplo, una cadena vacía para cadenas).

Es más complicado que eso. Nulo tiene varios significados distintos y una razón realmente importante para no permitir nulos en muchas columnas es que cuando la columna es nula, esto significa una y solo una cosa (es decir, que no apareció en una unión externa). Además, le permite establecer estándares mínimos de entrada de datos, lo cual es realmente útil.

Pero, ¿qué hace en el caso de fechas, fecha y hora (SQL Server 2008)? Tendría que usar una fecha histórica o de fondo.

Eso ilustra un problema con los valores nulos de inmediato, a saber, que un valor almacenado en una tabla puede significar "este valor no se aplica" o "no lo sabemos". Con las cadenas, una cadena vacía puede servir como "esto no se aplica" pero con fechas y horas, no existe tal convención porque no hay un valor válido que convencionalmente signifique esto. Por lo general, estará atascado con NULL.

Hay formas de evitar esto (agregando más relaciones y uniéndose), pero plantean exactamente los mismos problemas de claridad semántica que tener NULL en la base de datos. Para estas bases de datos no me preocuparía por esto. Simplemente no hay nada que puedas hacer al respecto realmente.

EDITAR: Un área donde los NULL son indispensables es en las claves externas. Aquí generalmente tienen un solo significado, idéntico al nulo en el significado de unión externa. Esta es una excepción al problema, por supuesto.

Chris Travers
fuente
10

El artículo de Wikipedia sobre SQL Null tiene algunas observaciones interesantes sobre el valor NULL, y como respuesta independiente de la base de datos, siempre y cuando conozca los posibles efectos de tener valores NULL para su RDBMS específico, son aceptables en su diseño. Si no lo fueran, no podría especificar columnas como anulables.

Solo tenga en cuenta cómo su RDBMS los maneja en operaciones SELECT, como las matemáticas, y también en los Índices.

Derek Downey
fuente
-12

Wow, la respuesta correcta "No permitir NULLs cuando no es necesario porque degradan el rendimiento" es de alguna manera la última respuesta calificada. Lo votaré y elaboraré. Cuando un RDBMS permite NULL para una columna no dispersa, esa columna se agrega a un mapa de bits que rastrea si el valor es NULL para cada fila individual. Por lo tanto, al agregar la capacidad NULL a una columna en una tabla donde todas las columnas no permiten NULL, está aumentando el espacio de almacenamiento requerido para guardar la tabla. Además, necesita que el RDBMS lea y escriba en el mapa de bits, lo que degrada el rendimiento en todas las operaciones.

Además, en varios casos, permitir NULLs romperá 3NF. Si bien no soy un fanático de 3NF como muchos de mis colegas, considere el siguiente escenario:

En la tabla Persona hay una columna, llamada DateOfDeath, que es anulable. Si una persona ha fallecido, se completará con su DateOfDeath; de lo contrario, se dejará como NULL. También hay una columna de bits no anulable llamada IsAlive. Esta columna se establece en 1 si la persona está viva y 0 si la persona está muerta. La gran mayoría de los procedimientos almacenados usan la columna IsAlive, solo les importa si una persona está viva, no su DateOfDeath.

Sin embargo, la columna IsAlive rompe la normalización de la base de datos, porque es completamente derivable de DateOfDeath. Pero dado que IsAlive está conectado a la mayoría de los SP, la solución directa es hacer que DateOfDeath no sea anulable y asignar un valor predeterminado a la columna en caso de que la persona aún esté viva. Los pocos SP que usan DateOfDeath pueden reescribirse para verificar la columna IsAlive, y solo honran a DateOfDeath si la persona no está viva. Nuevamente, dado que la mayoría de los SP solo se preocupan por IsAlive (un poco) y no por DateOfDeath (una fecha), el uso de este patrón acelera considerablemente el acceso.

Una secuencia de comandos T-SQL útil para encontrar columnas anulables sin NULL en todos los esquemas es:

select 'IF NOT EXISTS (SELECT 1 FROM ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ' WHERE ' + QUOTENAME(c.name) + ' IS NULL)
    AND (SELECT COUNT(*) FROM ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ') > 1 PRINT ''' + s.name + '.' + t.name + '.' + REPLACE(c.name, '''', '''''') + ''''
    from sys.columns c
    inner join sys.tables t ON c.object_id = t.object_id
    inner join sys.schemas s ON s.schema_id = t.schema_id
    where c.is_nullable = 1 AND c.is_computed = 0
    order by s.name, t.name, c.name;

Si ejecuta esto en una copia de su base de datos de producción, puede encontrar las columnas marcadas por los desarrolladores que permiten NULL que no tienen NULL en la práctica. La gran mayoría de estos pueden marcarse como NO NULOS, lo que aumenta el rendimiento y reduce el espacio de almacenamiento.

Puede que no sea posible eliminar todos los NULL en todas las tablas y aún así tener un diseño limpio, pero existe una ventaja considerable en eliminar tantos NULL como sea posible. El optimizador funciona mucho más rápido con esta información, y si puede eliminar todos los NULL en una tabla, puede recuperar una cantidad considerable de espacio de almacenamiento.

Sé que el rendimiento no es algo en lo que los DBA piensen demasiado, pero solo puede arrojar una cantidad limitada de memoria y potencia de procesador en una solución, en algún momento tendrá que comenzar a pensar en un diseño lógico y físico. .

También tenga en cuenta que esto es solo para verdaderos RDBMS y estoy basando la parte técnica de mis respuestas en SQL Server. El T-SQL listado para encontrar columnas anulables sin nulos también es de SQL Server.

Matthew Sontum
fuente
1
Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
Paul White