Utilice la función "LEN" en la cláusula "WHERE" en "CREATE INDIQUE ÚNICO"
12
Tengo esta tabla:
CREATETABLE Table01 (column01 nvarchar(100));
Y quiero crear un índice único en column01 con esta condición LEN (column01)> = 5
Lo intenté:
CREATEUNIQUEINDEX UIX_01 ON Table01(column01)WHERE LEN(column01)>=5;
Tengo:
Cláusula WHERE incorrecta para el índice filtrado 'UIX_01' en la tabla 'Table01'.
Y:
ALTERTABLE Table01 ADD column01_length AS(LEN(column01));CREATEUNIQUEINDEX UIX_01 ON Table01(column01)WHERE column01_length >=5;
Produce:
El índice filtrado 'UIX_01' no se puede crear en la tabla 'Tabla01' porque la columna 'column01_length' en la expresión del filtro es una columna calculada. Vuelva a escribir la expresión del filtro para que no incluya esta columna.
Un método para solucionar la restricción del índice filtrado es con una vista indizada:
CREATETABLE dbo.Table01 (
Column01 NVARCHAR(100));
GO
CREATEVIEW dbo.vw_Table01_Column01_LenOver5Unique
WITH SCHEMABINDING ASSELECT Column01
FROM dbo.Table01
WHERE LEN(Column01)>=5;
GO
CREATEUNIQUECLUSTEREDINDEX cdx
ON dbo.vw_Table01_Column01_LenOver5Unique(Column01);
GO
INSERTINTO dbo.Table01 VALUES('1');--successINSERTINTO dbo.Table01 VALUES('1');--successINSERTINTO dbo.Table01 VALUES('55555');--successINSERTINTO dbo.Table01 VALUES('55555');--duplicate key error
GO
EDITAR:
¿Cómo debo definir la vista si tengo dos columnas en el índice? CREAR ÍNDICE ÚNICO UIX_01 EN Table01 (column01, column02) DONDE LEN (column01)> = 5
El enfoque de vista indexada se puede extender para una clave compuesta agregando otras columnas clave a la definición e índice de la vista. Se aplica el mismo filtro en la definición de la vista, pero la unicidad de las filas de calificación impuestas por la clave compuesta en lugar del valor de columna única:
CREATETABLE dbo.Table01 (
Column01 NVARCHAR(100),Column02 NVARCHAR(100));
GO
CREATEVIEW dbo.vw_Table01_Column01_LenOver5Unique
WITH SCHEMABINDING ASSELECT Column01, Column02
FROM dbo.Table01
WHERE LEN(Column01)>=5;
GO
CREATEUNIQUECLUSTEREDINDEX cdx
ON dbo.vw_Table01_Column01_LenOver5Unique(Column01, Column02)
GO
INSERTINTO dbo.Table01 VALUES('1','A');--successINSERTINTO dbo.Table01 VALUES('1','A');--successINSERTINTO dbo.Table01 VALUES('55555','A');--successINSERTINTO dbo.Table01 VALUES('55555','B');--successINSERTINTO dbo.Table01 VALUES('55555','B');--duplicate key error
GO
Y espero que esto funcione mucho mejor que mi monstruosidad.
James Anderson
@Dan Guzman, ¿debería usar 'WITH SCHEMABINDING'?
geek
2
@Jalil Sí, SCHEMABINDINGse requiere para una vista indizada. La implicación es, por supuesto, que deberá soltar la vista antes de modificar la tabla. Herramientas como SSDT se encargarán de esa dependencia automáticamente.
Dan Guzman el
¿Cómo debo definir la vista si tengo dos columnas en el índice? CREAR ÍNDICE ÚNICO UIX_01 EN Table01 (column01, column02) DONDE LEN (column01)> = 5;
geek
@Jalil, agregué un ejemplo de clave compuesta a mi respuesta.
Dan Guzman el
5
Esta parece ser otra de las muchas limitaciones de los índices filtrados. Intentar evitarlo con el LIKEuso WHERE column01 LIKE '_____'tampoco funciona, produciendo el mismo mensaje de error ( "Cláusula WHERE incorrecta ..." ).
Además de la VIEWsolución, otra forma sería convertir la columna calculada en una columna normal y agregar una CHECKrestricción para que siempre tenga datos válidos:
Naturalmente, eso significa que debe llenar explícitamente column01_lengthcon la longitud correcta cada vez que complete column01(en inserciones y actualizaciones). Eso puede ser complicado, porque debe asegurarse de que la longitud se calcule de la misma manera que lo hace la función T-SQL LEN(). En particular, los espacios finales deben ignorarse, lo cual no es necesariamente cómo se calcula la longitud de forma predeterminada en varios lenguajes de programación en los que se escriben las aplicaciones cliente. La lógica puede ser fácil de explicar en la persona que llama, pero debe ser consciente de la diferencia en primer lugar.
Una opción sería un INSERT/UPDATEdesencadenador 1 para proporcionar el valor correcto para la columna, por lo que aparece como calculado para las aplicaciones del cliente.
1 Como se explica en Disparadores en comparación con las restricciones , necesitaría usar un disparador EN LUGAR DE PARA esto. Un disparador DESPUÉS simplemente nunca se ejecutaría, porque la longitud ausente fallaría la restricción de verificación y eso, a su vez, evitaría que el disparador se ejecute. Sin embargo, los desencadenadores INSTEAD OF tienen sus propias restricciones (consulte las Pautas de planificación de activación de DML para obtener una descripción general rápida).
No estoy seguro de cómo funcionará esto y puede haber una manera mucho más fácil de lograr esto que he pasado por alto, pero esto debería hacer lo que necesita si solo está interesado en hacer cumplir la unicidad.
CREATETABLE dbo.Table01
(
Column01 NVARCHAR(100));
GO
CREATEFUNCTION dbo.ChkUniqueColumn01OverLen5()
RETURNS BIT
ASBEGINDECLARE@Result BIT,@Count BIGINT,@DistinctCount BIGINT
SELECT@Count = COUNT(Column01),@DistinctCount = COUNT(DISTINCT Column01)FROM Table01
WHERE LEN(Column01)>=5SELECT@Result =CASEWHEN@Count =@DistinctCount THEN1ELSE0ENDRETURN@Result
END;
GO
ALTERTABLE dbo.Table01
ADDCONSTRAINT Chk_UniqueColumn01OverLen5
CHECK(dbo.ChkUniqueColumn01OverLen5()=1);
GO
INSERT dbo.Table01 (Column01)VALUES(N'123'),(N'1234');
GO
INSERT dbo.Table01 (Column01)VALUES(N'12345');
GO
INSERT dbo.Table01 (Column01)VALUES(N'12345');-- Will fail
GO
INSERT dbo.Table01 (Column01)VALUES(N'123');-- Will pass
GO
UPDATE dbo.Table01
SET Column01 ='12345'WHERE Column01 ='1234'-- Will fail
GO
SELECT*FROM dbo.Table01;
GO
DROPTABLE Table01;DROPFUNCTION dbo.ChkUniqueColumn01OverLen5;
El uso de una función de valor escalar en una restricción de verificación o definición de columna calculada obligará a todas las consultas que tocan la tabla a ejecutarse en serie, incluso si no hacen referencia a la columna.
Erik Darling
2
@sp_BlitzErik Sí, y eso puede no ser lo peor de esta solución :). Solo quería ver si funcionaría, de ahí la advertencia de rendimiento.
SCHEMABINDING
se requiere para una vista indizada. La implicación es, por supuesto, que deberá soltar la vista antes de modificar la tabla. Herramientas como SSDT se encargarán de esa dependencia automáticamente.Esta parece ser otra de las muchas limitaciones de los índices filtrados. Intentar evitarlo con el
LIKE
usoWHERE column01 LIKE '_____'
tampoco funciona, produciendo el mismo mensaje de error ( "Cláusula WHERE incorrecta ..." ).Además de la
VIEW
solución, otra forma sería convertir la columna calculada en una columna normal y agregar unaCHECK
restricción para que siempre tenga datos válidos:Probado en rextester.com
Naturalmente, eso significa que debe llenar explícitamente
column01_length
con la longitud correcta cada vez que completecolumn01
(en inserciones y actualizaciones). Eso puede ser complicado, porque debe asegurarse de que la longitud se calcule de la misma manera que lo hace la función T-SQLLEN()
. En particular, los espacios finales deben ignorarse, lo cual no es necesariamente cómo se calcula la longitud de forma predeterminada en varios lenguajes de programación en los que se escriben las aplicaciones cliente. La lógica puede ser fácil de explicar en la persona que llama, pero debe ser consciente de la diferencia en primer lugar.Una opción sería un
INSERT/UPDATE
desencadenador 1 para proporcionar el valor correcto para la columna, por lo que aparece como calculado para las aplicaciones del cliente.1 Como se explica en Disparadores en comparación con las restricciones , necesitaría usar un disparador EN LUGAR DE PARA esto. Un disparador DESPUÉS simplemente nunca se ejecutaría, porque la longitud ausente fallaría la restricción de verificación y eso, a su vez, evitaría que el disparador se ejecute. Sin embargo, los desencadenadores INSTEAD OF tienen sus propias restricciones (consulte las Pautas de planificación de activación de DML para obtener una descripción general rápida).
fuente
No estoy seguro de cómo funcionará esto y puede haber una manera mucho más fácil de lograr esto que he pasado por alto, pero esto debería hacer lo que necesita si solo está interesado en hacer cumplir la unicidad.
fuente