在“创建唯一索引”的“ WHERE”子句中使用“ LEN”函数


12

我有这张桌子:

CREATE TABLE Table01 (column01 nvarchar(100));

我想在条件为LEN(column01)> = 5的情况下在column01上创建唯一索引

我试过了:

CREATE UNIQUE INDEX UIX_01 ON Table01(column01) WHERE LEN(column01) >= 5;

我有:

表'Table01'上筛选索引'UIX_01'的WHERE子句不正确。

和:

ALTER TABLE Table01 ADD column01_length AS (LEN(column01));
CREATE UNIQUE INDEX UIX_01 ON Table01(column01) WHERE column01_length >= 5;

产生:

无法在表“ Table01”上创建过滤索引“ UIX_01”,因为过滤表达式中的“ column01_length”列是计算列。重写过滤器表达式,使其不包括此列。

Answers:


15

解决索引索引限制的一种方法是使用索引视图:

CREATE TABLE dbo.Table01 (
  Column01 NVARCHAR(100)
);
GO

CREATE VIEW dbo.vw_Table01_Column01_LenOver5Unique
WITH SCHEMABINDING AS
SELECT Column01
FROM dbo.Table01
WHERE LEN(Column01) >= 5;
GO

CREATE UNIQUE CLUSTERED INDEX cdx
    ON dbo.vw_Table01_Column01_LenOver5Unique(Column01);
GO

INSERT INTO dbo.Table01 VALUES('1'); --success
INSERT INTO dbo.Table01 VALUES('1'); --success
INSERT INTO dbo.Table01 VALUES('55555'); --success
INSERT INTO dbo.Table01 VALUES('55555'); --duplicate key error
GO

编辑:

如果索引中有两列,应该如何定义视图?CREATE UNIQUE INDEX UIX_01 ON Table01(column01,column02)WHERE LEN(column01)> = 5

通过向视图定义和索引添加其他键列,可以为复合键扩展索引视图方法。视图定义中应用了相同的过滤器,但是由复合键而不是单列值强制的限定行的唯一性:

CREATE TABLE dbo.Table01 (
   Column01 NVARCHAR(100)
  ,Column02 NVARCHAR(100)
);
GO

CREATE VIEW dbo.vw_Table01_Column01_LenOver5Unique
WITH SCHEMABINDING AS
SELECT Column01, Column02
FROM dbo.Table01
WHERE LEN(Column01) >= 5;
GO

CREATE UNIQUE CLUSTERED INDEX cdx
    ON dbo.vw_Table01_Column01_LenOver5Unique(Column01, Column02)
GO

INSERT INTO dbo.Table01 VALUES('1','A'); --success
INSERT INTO dbo.Table01 VALUES('1','A'); --success
INSERT INTO dbo.Table01 VALUES('55555','A'); --success
INSERT INTO dbo.Table01 VALUES('55555','B'); --success
INSERT INTO dbo.Table01 VALUES('55555','B'); --duplicate key error
GO

我希望这会比我的怪兽做得更好。
詹姆斯·安德森

@Dan Guzman我应该使用“ WITH SCHEMABINDING”吗?
geek

2
@Jalil是的,SCHEMABINDING对于索引视图是必需的。当然,这意味着您需要在更改表之前先删除视图。像SSDT这样的工具将自动处理这种依赖性。
丹·古兹曼

如果索引中有两列,应该如何定义视图?在Table01(column01,column02)WHERE LEN(column01)> = 5上创建唯一索引UIX_01;
geek

@Jalil,我在答案中添加了一个复合键示例。
丹·古兹曼

5

这似乎是筛选索引的许多限制中的另一个。尝试LIKE使用using 绕过它WHERE column01 LIKE '_____'也不起作用,产生相同的错误消息(“ Incorrect WHERE子句...”)。

VIEW解决方案外,另一种方法是将计算列转换为常规列并添加CHECK约束,使其始终具有有效数据:

CREATE TABLE Table01 (column01 nvarchar(100),
                      column01_length int,
                      CHECK ( column01_length = len(column01)
                              AND column01 IS NOT NULL 
                              AND column01_length IS NOT NULL
                           OR column01 IS NULL 
                              AND column01_length IS NULL )
                     ) ;


CREATE UNIQUE INDEX UIX_01 ON Table01 (column01) WHERE column01_length >= 5 ;

经过rextester.com测试

自然,这意味着您column01_length每次填充时column01(在插入和更新时)都需要使用正确的长度显式填充。这可能很棘手,因为您需要确保以与T-SQL LEN()函数相同的方式计算长度。特别是,需要忽略尾随空格,这不一定是在编写客户端应用程序时所用的各种编程语言中默认情况下如何计算长度。逻辑在调用方中可能很容易解释,但是您需要首先意识到差异。

一个选项将是INSERT/UPDATE触发器1,以为该列提供正确的值,因此它看起来像是对客户端应用程序计算的。


1如“ 触发器与约束条件比较”中所述,您需要为此使用INSTEAD OF触发器。AFTER触发器将永远不会执行,因为缺少长度将无法通过检查约束,进而会阻止触发器运行。但是,INSTEAD OF触发器有其自身的限制(有关快速概述,请参见DML触发器计划指南)。


1

我不确定这将如何执行,并且可能有一种更容易实现的方法,但我却忽略了这一点,但是如果您仅对强制执行唯一性感兴趣,那么这应该可以满足您的需求。

CREATE TABLE dbo.Table01 
(
  Column01 NVARCHAR(100)
);
GO

CREATE FUNCTION dbo.ChkUniqueColumn01OverLen5()
RETURNS BIT
AS
BEGIN
DECLARE @Result BIT, @Count BIGINT, @DistinctCount BIGINT

SELECT  @Count = COUNT(Column01),
        @DistinctCount = COUNT(DISTINCT Column01)
FROM    Table01
WHERE   LEN(Column01) >= 5 

SELECT @Result = CASE WHEN @Count = @DistinctCount THEN 1 ELSE 0 END

RETURN @Result

END;
GO

ALTER TABLE dbo.Table01
ADD CONSTRAINT 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

DROP TABLE Table01;
DROP FUNCTION dbo.ChkUniqueColumn01OverLen5;

2
在检查约束或计算列定义中使用标量值函数将强制所有接触表的查询以串行方式运行,即使它们未引用该列也是如此。
埃里克·达林

2
@sp_BlitzErik Yep,对于该解决方案来说,这甚至可能不是最糟糕的事情:)。我只是想看看它是否可以工作,因此出现了性能警告。
詹姆斯·安德森
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.