Используйте функцию «LEN» в предложении «WHERE» в «CREATE UNIQUE INDEX»

12

У меня есть эта таблица:

CREATE TABLE Table01 (column01 nvarchar(100));

И я хочу создать уникальный индекс для column01 с этим условием LEN (column01)> = 5

Я старался:

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

Я получил:

Неверное предложение WHERE для отфильтрованного индекса 'UIX_01' в таблице 'Table01'.

И :

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

Производит:

Отфильтрованный индекс «UIX_01» не может быть создан для таблицы «Table01», поскольку столбец «column01_length» в выражении фильтра является вычисляемым столбцом. Перепишите выражение фильтра, чтобы оно не включало этот столбец.

Компьютерщик
источник

Ответы:

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 Таблица01 (столбец01, столбец02) ГДЕ ЛЕН (столбец01)> = 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
Дэн Гусман
источник
И я ожидаю, что это будет работать намного лучше, чем мое чудовище.
Джеймс Андерсон
@ Дан Гузман, я должен использовать 'С SCHEMABINDING'?
Компьютерщик
2
@Jalil Да, SCHEMABINDINGтребуется для индексированного представления. Смысл, конечно, в том, что вам нужно отбросить представление перед изменением таблицы. Инструменты типа SSDT автоматически позаботятся об этой зависимости.
Дан Гузман
Как мне определить представление, если у меня есть два столбца в индексе? CREATE UNIQUE INDEX UIX_01 ON Таблица01 (столбец01, столбец02) ГДЕ ЛЕН (столбец01)> = 5;
Компьютерщик
@Jalil, я добавил пример составного ключа в свой ответ.
Дан Гузман
5

Это, кажется, еще одно из многих ограничений фильтруемых индексов. Попытка обойти это с LIKEпомощью WHERE column01 LIKE '_____'тоже не работает, выдает то же сообщение об ошибке ( «Неверное предложение 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(при вставках и обновлениях). Это может быть сложно, потому что вам нужно убедиться, что длина рассчитывается так же, как это LEN()делает функция T-SQL . В частности, необходимо игнорировать конечные пробелы, что не обязательно означает, как длина вычисляется по умолчанию в различных языках программирования, на которых написаны клиентские приложения. Логика может быть проста для учета в вызывающей программе, но вам необходимо осознавая разницу в первую очередь.

Опцией может быть INSERT/UPDATEтриггер 1, чтобы указать правильное значение для столбца, поэтому оно выглядит так, как оно рассчитывается для клиентских приложений.


1 Как объясняется в разделе «Триггеры по сравнению с ограничениями» , для этого вам потребуется использовать триггер INSTEAD OF. Триггер AFTER просто никогда не будет выполнен, потому что отсутствующая длина не выполнит проверочное ограничение, а это, в свою очередь, помешает запуску триггера. Триггеры INSTEAD OF, однако, имеют свои собственные ограничения ( краткий обзор см. В Руководстве по планированию триггеров DML ).

ypercubeᵀᴹ
источник
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 Да, и это может быть даже не самое плохое в этом решении :). Я просто хотел посмотреть, сработает ли это, отсюда и предупреждение о производительности.
Джеймс Андерсон