2012-02-14 13 views
10

mio schema SQL èarresto MySQL tollerando più NULL in un vincolo UNIQUE

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

MySQL sta permettendo la seguente dichiarazione da ripetere, con conseguente duplicati.

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

pur avendo

UNIQUE (`name`, `bar`) 

Perché questo è tollerato e come posso fermarlo?

+2

Fai un primario chiave, che non consente null in nessuno dei due campi. O rendere i campi non nulli. Come da mysql docs: http://dev.mysql.com/doc/refman/5.1/en/create-index.html "Per tutti i motori, un indice UNIQUE consente più valori nulli per le colonne che possono contenere null". –

+0

'NULL' non è un valore. Come dice @MarcB, devi disabilitare 'NULL' per questo – JNK

+0

Sembra che sia un bug: http://bugs.mysql.com/bug.php?id=8173. (Almeno alcune persone nella segnalazione bug la pensano così) –

risposta

12

Attenzione: questa risposta è obsoleta. A partire da MySQL 5.1, BDB non è supportato.

Dipende da MySQL Engine Type. BDB non consente più valori utilizzando UNIQUE ma MyISAM e InnoDB consente più NULL s anche con UNIQUE.

2

In generale, a seconda del motore di archiviazione, NULL può o non può essere visto come un valore univoco. È necessario utilizzare un motore di archiviazione che non riconosce NULL come un valore univoco, ad es. InnoDB o MyISAM.

Per aggirare questo è possibile creare un "valore nullo", ad esempio 99999999, che è possibile riconoscere come NULL in quanto non c'è modo di modificare il modo in cui il tuo motore di archiviazione decide di gestire i valori null in chiavi univoche.

+4

-1 per suggerire una soluzione di numero magico. La vera risposta è capire 'NULL' e usarlo appropriatamente. – JNK

+0

Non darò -1 perché l'OP sembra pensare che 'NULL' sia davvero un valore magico. Né +1 perché la cattiva comprensione dei concetti non dovrebbe essere incoraggiata. –

0

BDB non consente più valori NULL utilizzando UNIQUE. Ma MySQL rilascia il motore BDB (http://dev.mysql.com/doc/relnotes/mysql/5.1/en/news-5-1-12.html).

Così ora: http://dev.mysql.com/doc/refman/5.5/en/create-index.html

Per tutti i motori, un indice univoco permessi multipli valori NULL per le colonne che possono contenere NULL. Se si specifica un valore prefisso per una colonna in un indice UNIQUE, i valori della colonna devono essere univoci all'interno del prefisso.

1

AGGIORNAMENTO: Si consiglia di utilizzare l'idea suggerita dal @greenoldman nel commento qui sotto invece. Creare un campo booleano con trigger per impostare il valore in base al fatto che il campo nullable sia NULL o no e quindi combinare il campo booleano in un vincolo univoco con gli altri campi che definiscono l'unicità.


Ho trovato un modo per aggirare questo problema se si deve rispettare il vincolo unico, ma anche bisogno di avere una chiave esterna per la colonna, richiedendo così che sia annullabile. La mia soluzione era derivata da this e richiede un po 'di spazio in più. Questo è un esempio con un campo ID numerico.

Il concetto di base è che è necessario creare un altro campo non nullable che avrà il valore del campo nullable con la chiave esterna duplicata in esso con un trigger. Il vincolo univoco verrà quindi applicato al campo duplicato non annullabile.Per fare questo è necessario definire un campo non annullabile con un valore predefinito di 0 simile a questo:

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

Poi devi solo definire alcuni trigger come questo:

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

Grazie mille! Il concetto è ottimo, ma l'esecuzione è troppo fragile per essere utilizzata. Comunque c'è un rimedio - questa colonna in più dovrebbe essere 'TINYINT (1)' (significato - bool) tenendo '0' per null e' 1' per not-null (o viceversa, non importa) per campo nullable. L'indice univoco dovrebbe utilizzare questa colonna aggiuntiva ** in aggiunta ** e non invece. E questo è tutto - non vi è alcuna possibilità di ottenere conflitti a causa di valori magici, perché non ce n'è. – greenoldman

+0

@greenoldman Ottima idea! FWIW, non ho avuto problemi a usare questa tecnica in produzione con dataset molto grandi e attivi, ma mi piace ancora la tua idea. –

Problemi correlati