Use a função "LEN" na cláusula "WHERE" em "CREATE UNIQUE INDEX"
12
Eu tenho esta tabela:
CREATETABLE Table01 (column01 nvarchar(100));
E eu quero criar um índice exclusivo na coluna01 com esta condição LEN (coluna01)> = 5
Eu tentei:
CREATEUNIQUEINDEX UIX_01 ON Table01(column01)WHERE LEN(column01)>=5;
Eu tenho:
Cláusula WHERE incorreta para o índice filtrado 'UIX_01' na tabela 'Tabela01'.
E:
ALTERTABLE Table01 ADD column01_length AS(LEN(column01));CREATEUNIQUEINDEX UIX_01 ON Table01(column01)WHERE column01_length >=5;
Produz:
O índice filtrado 'UIX_01' não pode ser criado na tabela 'Table01' porque a coluna 'column01_length' na expressão de filtro é uma coluna calculada. Reescreva a expressão de filtro para que ela não inclua esta coluna.
Um método para solucionar a restrição do índice filtrado é com uma exibição indexada:
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:
Como devo definir a exibição se tenho duas colunas no índice? CRIAR ÍNDICE ÚNICO UIX_01 NA Tabela01 (coluna01, coluna02) ONDE LEN (coluna01)> = 5
A abordagem de exibição indexada pode ser estendida para uma chave composta adicionando outras colunas de chave à definição e ao índice da exibição. O mesmo filtro é aplicado na definição de exibição, mas a exclusividade das linhas qualificadas aplicadas pela chave composta em vez do valor da coluna ú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
E espero que isso tenha um desempenho muito melhor do que minha monstruosidade.
James Anderson
@Dan Guzman devo usar 'WITH SCHEMABINDING'?
nerd
2
@Jalil Sim, SCHEMABINDINGé necessário para uma exibição indexada. É claro que a implicação é que você precisará interromper a exibição antes de alterar a tabela. Ferramentas como SSDT cuidam dessa dependência automaticamente.
Dan Guzman
Como devo definir a exibição se tenho duas colunas no índice? CRIAR ÍNDICE ÚNICO UIX_01 NA Tabela01 (coluna01, coluna02) WHERE LEN (coluna01)> = 5;
Geek
@ Dalil, adicionei um exemplo de chave composta à minha resposta.
Dan Guzman
5
Essa parece ser outra das muitas limitações dos índices filtrados. Tentar contorná-lo com o LIKEuso WHERE column01 LIKE '_____'também não funciona, produzindo a mesma mensagem de erro ( "Cláusula WHERE incorreta ..." ).
Além da VIEWsolução, outra maneira seria converter a coluna computada em uma coluna regular e adicionar uma CHECKrestrição para que ele sempre tenha dados válidos:
Naturalmente, isso significa que você precisa preencher explicitamente column01_lengthcom o tamanho correto sempre que preencher column01(em inserções e atualizações). Isso pode ser complicado, porque você precisa garantir que o comprimento seja calculado da mesma maneira que o T-SQLLEN() função . Em particular, os espaços à direita precisam ser ignorados, o que não é necessariamente o modo como o comprimento é calculado por padrão em várias linguagens de programação nas quais os aplicativos clientes são gravados. A lógica pode ser fácil de explicar no chamador, mas é necessário ciente da diferença em primeiro lugar.
Uma opção seria um INSERT/UPDATEgatilho 1 para fornecer o valor correto para a coluna, para que apareça como calculado para aplicativos clientes.
1 Conforme explicado em Triggers Comparated to Constraints , você precisaria usar um gatilho INSTEAD OF para isso. Um gatilho AFTER nunca seria executado, porque o comprimento ausente falharia na restrição de verificação e isso, por sua vez, impediria a execução do gatilho. No entanto, gatilhos em vez de gatilhos têm suas próprias restrições (consulte Diretrizes de planejamento do gatilho DML para uma visão geral rápida).
Não tenho certeza de como isso vai funcionar e pode haver uma maneira muito mais fácil de conseguir isso que eu negligenciei, mas isso deve fazer o que você precisa se estiver interessado apenas em reforçar a exclusividade.
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;
O uso de uma função escalar com valor em uma restrição de verificação ou em uma definição de coluna calculada forçará todas as consultas que tocam na tabela a serem executadas em série, mesmo que não façam referência à coluna.
Erik Darling
2
@sp_BlitzErik Sim, e isso pode até não ser a pior coisa dessa solução :). Eu só queria ver se funcionaria, daí o aviso de desempenho.
SCHEMABINDING
é necessário para uma exibição indexada. É claro que a implicação é que você precisará interromper a exibição antes de alterar a tabela. Ferramentas como SSDT cuidam dessa dependência automaticamente.Essa parece ser outra das muitas limitações dos índices filtrados. Tentar contorná-lo com o
LIKE
usoWHERE column01 LIKE '_____'
também não funciona, produzindo a mesma mensagem de erro ( "Cláusula WHERE incorreta ..." ).Além da
VIEW
solução, outra maneira seria converter a coluna computada em uma coluna regular e adicionar umaCHECK
restrição para que ele sempre tenha dados válidos:Testado em rextester.com
Naturalmente, isso significa que você precisa preencher explicitamente
column01_length
com o tamanho correto sempre que preenchercolumn01
(em inserções e atualizações). Isso pode ser complicado, porque você precisa garantir que o comprimento seja calculado da mesma maneira que o T-SQLLEN()
função . Em particular, os espaços à direita precisam ser ignorados, o que não é necessariamente o modo como o comprimento é calculado por padrão em várias linguagens de programação nas quais os aplicativos clientes são gravados. A lógica pode ser fácil de explicar no chamador, mas é necessário ciente da diferença em primeiro lugar.Uma opção seria um
INSERT/UPDATE
gatilho 1 para fornecer o valor correto para a coluna, para que apareça como calculado para aplicativos clientes.1 Conforme explicado em Triggers Comparated to Constraints , você precisaria usar um gatilho INSTEAD OF para isso. Um gatilho AFTER nunca seria executado, porque o comprimento ausente falharia na restrição de verificação e isso, por sua vez, impediria a execução do gatilho. No entanto, gatilhos em vez de gatilhos têm suas próprias restrições (consulte Diretrizes de planejamento do gatilho DML para uma visão geral rápida).
fonte
Não tenho certeza de como isso vai funcionar e pode haver uma maneira muito mais fácil de conseguir isso que eu negligenciei, mas isso deve fazer o que você precisa se estiver interessado apenas em reforçar a exclusividade.
fonte