2012-02-14 10 views
10

Mi esquema SQL esparada MySQL tolerar múltiples valores NULL en una restricción UNIQUE

CREATE TABLE Foo (
`bar` INT NULL , 
`name` VARCHAR (59) NOT NULL , 
UNIQUE (`name`, `bar`) 
) ENGINE = INNODB; 

MySQL está permitiendo la siguiente declaración a repetirse, lo que resulta en los duplicados.

INSERT INTO Foo (`bar`, `name`) VALUES (NULL, 'abc'); 

a pesar de tener

UNIQUE (`name`, `bar`) 

Por qué es esto tolerados y cómo lo paro?

+2

la convierten en una primaria clave, que no permite nulos en ninguno de los campos. O haz que los campos no sean nulos. De acuerdo con los documentos de mysql: http://dev.mysql.com/doc/refman/5.1/en/create-index.html "Para todos los motores, un índice ÚNICO permite valores nulos múltiples para las columnas que pueden contener nulo". –

+0

'NULL' no es un valor. Como dice @MarcB, debe rechazar 'NULL' para este – JNK

+0

Parece que es un error: http://bugs.mysql.com/bug.php?id=8173. (Al menos algunas personas en el informe de errores lo creen así) –

Respuesta

12

Advertencia: Esta respuesta no está actualizada. A partir de MySQL 5.1, BDB no es compatible.

Depende de MySQL Engine Type. BDB no permite múltiples NULL valores utilizando UNIQUE pero MyISAM y InnoDB permite múltiples NULL es incluso con UNIQUE.

2

En general, dependiendo del motor de almacenamiento, NULL puede o no ser visto como un valor único. Debe utilizar un motor de almacenamiento que no reconoce NULL como un valor único, por ej. InnoDB o MyISAM.

Para evitar esto, puede crear un "valor nulo", como 99999999, que puede reconocer como NULL, ya que no hay forma de cambiar la forma en que su motor de almacenamiento decide manejar nulos en claves únicas.

+4

-1 por sugerir una solución de número mágico. La verdadera respuesta es entender 'NULL' y usarlo apropiadamente. – JNK

+0

No daré -1 porque OP parece pensar que 'NULL' es de hecho algún valor mágico. Ni +1 porque no se debe alentar la mala comprensión de los conceptos. –

0

BDB no permite valores NULL múltiples usando UNIQUE. Pero MySQL descarta el motor BDB (http://dev.mysql.com/doc/relnotes/mysql/5.1/en/news-5-1-12.html).

Así que ahora: http://dev.mysql.com/doc/refman/5.5/en/create-index.html

Para todos los motores, un índice UNIQUE permite múltiples valores NULL para columnas que pueden contener NULL. Si especifica un valor de prefijo para una columna en un índice ÚNICO, los valores de columna deben ser únicos dentro del prefijo.

1

ACTUALIZACIÓN: Se debe utilizar la idea sugerida por @greenoldman en el comentario anterior en su lugar. Cree un campo booleano con disparadores para establecer el valor según si su campo que admite nulos es NULL o no y luego combine el campo booleano en una restricción única con los otros campos que definen la exclusividad.


He encontrado una forma de evitar este problema si debe cumplir la restricción única, pero también es necesario tener una clave externa en la columna, por lo que requiere que sea anulable. Mi solución se derivó de this y requerirá un poco más de espacio. Este es un ejemplo con un campo numérico de identificación.

El concepto básico es que debe crear otro campo no anulable que tendrá el valor de su campo anulable con la clave foránea duplicada con un activador. La restricción única se aplicará en el campo duplicado no nulo.Para ello es necesario definir un campo no acepta valores NULL con un valor por defecto de 0 similar a esto:

ALTER TABLE `my_table` ADD `uniq_foo` int(10) UNSIGNED NOT NULL DEFAULT '0'; 

A continuación, sólo tiene que definir algunos factores desencadenantes de esta manera:

DROP TRIGGER IF EXISTS `my_table_before_insert`; 
DELIMITER ;; 
CREATE TRIGGER `my_table_before_insert` BEFORE INSERT ON `my_table` 
FOR EACH ROW 
BEGIN 
    SET NEW.uniq_foo = IFNULL(NEW.foo_id, 0); 
END;; 
DELIMITER ; 

DROP TRIGGER IF EXISTS `my_table_before_update`; 
DELIMITER ;; 
CREATE TRIGGER `my_table_before_update` BEFORE UPDATE ON `my_table` 
FOR EACH ROW 
BEGIN 
    SET NEW.uniq_foo = IFNULL(NEW.foo_id, 0); 
END;; 
DELIMITER ; 
+0

¡Muchas gracias! El concepto es genial, pero la ejecución es demasiado frágil para usar. Sin embargo, hay un remedio: esta columna adicional debe ser 'TINYINT (1)' (es decir, bool) que contiene '0' para nulo y' 1' para no nulo (o viceversa, no importa) para campo anulable. El índice único debe usar esta columna adicional ** adicionalmente ** no en su lugar. Y eso es todo: no hay posibilidad de que tengas conflictos debido a los valores mágicos, porque no hay ninguno. – greenoldman

+0

@greenoldman ¡Excelente idea! FWIW, no he tenido problemas para usar esta técnica en producción con conjuntos de datos muy grandes y activos, pero aún me gusta su idea. –

Cuestiones relacionadas