SQL Server 2008 y hasta
Simplemente filtre un índice único:
CREATE UNIQUE NONCLUSTERED INDEX UQ_Party_SamAccountName
ON dbo.Party(SamAccountName)
WHERE SamAccountName IS NOT NULL;
En versiones inferiores, todavía no se requiere una vista materializada
Para SQL Server 2005 y versiones anteriores, puede hacerlo sin una vista. Acabo de agregar una restricción única como la que estás pidiendo a una de mis tablas. Dado que quiero unicidad en la columna SamAccountName
, pero quiero permitir múltiples NULL, utilicé una columna materializada en lugar de una vista materializada:
ALTER TABLE dbo.Party ADD SamAccountNameUnique
AS (Coalesce(SamAccountName, Convert(varchar(11), PartyID)))
ALTER TABLE dbo.Party ADD CONSTRAINT UQ_Party_SamAccountName
UNIQUE (SamAccountNameUnique)
Simplemente tiene que poner algo en la columna calculada que se garantizará como único en toda la tabla cuando la columna única deseada real sea NULL. En este caso, PartyID
es una columna de identidad y ser numérico nunca coincidirá con ninguna SamAccountName
, por lo que funcionó para mí. Puede probar su propio método: asegúrese de comprender el dominio de sus datos para que no haya posibilidad de intersección con datos reales. Eso podría ser tan simple como anteponer un carácter diferenciador como este:
Coalesce('n' + SamAccountName, 'p' + Convert(varchar(11), PartyID))
Incluso si PartyID
algún día se volviera no numérico y pudiera coincidir con un SamAccountName
, ahora no importará.
Tenga en cuenta que la presencia de un índice que incluye la columna calculada hace que implícitamente cada resultado de la expresión se guarde en el disco con los otros datos de la tabla, lo que NO requiere espacio en disco adicional.
Tenga en cuenta que si no desea un índice, aún puede guardar la CPU haciendo que la expresión se calcule previamente en el disco agregando la palabra clave PERSISTED
al final de la definición de expresión de columna.
En SQL Server 2008 y versiones posteriores, ¡definitivamente use la solución filtrada si es posible!
Controversia
Tenga en cuenta que algunos profesionales de bases de datos verán esto como un caso de "NULL sustitutos", que definitivamente tienen problemas (principalmente debido a problemas relacionados con el intento de determinar cuándo algo es un valor real o un valor sustituto para los datos faltantes) ; también puede haber problemas con el número de valores sustitutos no NULL que se multiplican como locos).
Sin embargo, creo que este caso es diferente. La columna calculada que estoy agregando nunca se usará para determinar nada. No tiene ningún significado en sí mismo y no codifica ninguna información que no se encuentre por separado en otras columnas definidas correctamente. Nunca debe seleccionarse o usarse.
Por lo tanto, mi historia es que este no es un NULL sustituto, ¡y lo estoy cumpliendo! Dado que en realidad no queremos el valor no NULL para ningún otro propósito que no sea engañar alUNIQUE
índice para que ignore los NULL, nuestro caso de uso no tiene ninguno de los problemas que surgen con la creación NULL sustituta normal.
Dicho todo esto, no tengo ningún problema con el uso de una vista indexada, pero trae algunos problemas, como el requisito de usar SCHEMABINDING
. Diviértase agregando una nueva columna a su tabla base (como mínimo, tendrá que soltar el índice y luego soltar la vista o modificar la vista para que no esté vinculada al esquema). Consulte la lista completa (larga) de requisitos para crear una vista indizada en SQL Server (2005) (también versiones posteriores), (2000) .
Actualizar
Si su columna es numérica, puede existir el desafío de garantizar que el uso exclusivo de la restricción Coalesce
no provoque colisiones. En ese caso, hay algunas opciones. Una podría ser utilizar un número negativo, poner los "NULL sustitutos" solo en el rango negativo y los "valores reales" solo en el rango positivo. Alternativamente, se podría usar el siguiente patrón. En la tabla Issue
(donde IssueID
está el PRIMARY KEY
), puede haber o no un TicketID
, pero si hay uno, debe ser único.
ALTER TABLE dbo.Issue ADD TicketUnique
AS (CASE WHEN TicketID IS NULL THEN IssueID END);
ALTER TABLE dbo.Issue ADD CONSTRAINT UQ_Issue_Ticket_AllowNull
UNIQUE (TicketID, TicketUnique);
Si el IssueID 1 tiene el ticket 123, la UNIQUE
restricción estará en los valores (123, NULL). Si IssueID 2 no tiene ticket, estará encendido (NULL, 2). Algún pensamiento mostrará que esta restricción no se puede duplicar para ninguna fila de la tabla y aún permite múltiples NULL.