Используйте функцию «LEN» в предложении «WHERE» в «CREATE UNIQUE INDEX»
12
У меня есть эта таблица:
CREATETABLE Table01 (column01 nvarchar(100));
И я хочу создать уникальный индекс для column01 с этим условием LEN (column01)> = 5
Я старался:
CREATEUNIQUEINDEX UIX_01 ON Table01(column01)WHERE LEN(column01)>=5;
Я получил:
Неверное предложение WHERE для отфильтрованного индекса 'UIX_01' в таблице 'Table01'.
И :
ALTERTABLE Table01 ADD column01_length AS(LEN(column01));CREATEUNIQUEINDEX UIX_01 ON Table01(column01)WHERE column01_length >=5;
Производит:
Отфильтрованный индекс «UIX_01» не может быть создан для таблицы «Table01», поскольку столбец «column01_length» в выражении фильтра является вычисляемым столбцом. Перепишите выражение фильтра, чтобы оно не включало этот столбец.
Один из способов обойти ограничение отфильтрованного индекса - это индексированное представление:
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
РЕДАКТИРОВАТЬ:
Как мне определить представление, если у меня есть два столбца в индексе? CREATE UNIQUE INDEX UIX_01 ON Таблица01 (столбец01, столбец02) ГДЕ ЛЕН (столбец01)> = 5
Подход с индексированным представлением может быть расширен для составного ключа путем добавления других ключевых столбцов к определению и индексу представления. Тот же фильтр применяется в определении представления, но уникальность подходящих строк обеспечивается составным ключом, а не значением одного столбца:
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
И я ожидаю, что это будет работать намного лучше, чем мое чудовище.
Джеймс Андерсон
@ Дан Гузман, я должен использовать 'С 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ограничение, чтобы оно всегда имело правильные данные:
Естественно, это означает, что вам нужно явно указывать column01_lengthправильную длину при каждом заполнении column01(при вставках и обновлениях). Это может быть сложно, потому что вам нужно убедиться, что длина рассчитывается так же, как это LEN()делает функция T-SQL . В частности, необходимо игнорировать конечные пробелы, что не обязательно означает, как длина вычисляется по умолчанию в различных языках программирования, на которых написаны клиентские приложения. Логика может быть проста для учета в вызывающей программе, но вам необходимо осознавая разницу в первую очередь.
Опцией может быть INSERT/UPDATEтриггер 1, чтобы указать правильное значение для столбца, поэтому оно выглядит так, как оно рассчитывается для клиентских приложений.
1 Как объясняется в разделе «Триггеры по сравнению с ограничениями» , для этого вам потребуется использовать триггер INSTEAD OF. Триггер AFTER просто никогда не будет выполнен, потому что отсутствующая длина не выполнит проверочное ограничение, а это, в свою очередь, помешает запуску триггера. Триггеры INSTEAD OF, однако, имеют свои собственные ограничения ( краткий обзор см. В Руководстве по планированию триггеров DML ).
Я не уверен, как это будет работать, и, возможно, есть гораздо более простой способ добиться этого, который я упустил из виду, но это должно делать то, что вам нужно, если вы заинтересованы только в обеспечении уникальности.
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;
Использование скалярной функции в проверочном ограничении или определении вычисляемого столбца заставит все запросы, которые касаются таблицы, выполняться последовательно, даже если они не ссылаются на столбец.
Эрик Дарлинг
2
@sp_BlitzErik Да, и это может быть даже не самое плохое в этом решении :). Я просто хотел посмотреть, сработает ли это, отсюда и предупреждение о производительности.
SCHEMABINDING
требуется для индексированного представления. Смысл, конечно, в том, что вам нужно отбросить представление перед изменением таблицы. Инструменты типа SSDT автоматически позаботятся об этой зависимости.Это, кажется, еще одно из многих ограничений фильтруемых индексов. Попытка обойти это с
LIKE
помощьюWHERE column01 LIKE '_____'
тоже не работает, выдает то же сообщение об ошибке ( «Неверное предложение WHERE ...» ).Помимо
VIEW
решения, другим способом было бы преобразовать вычисляемый столбец в обычный столбец и добавитьCHECK
ограничение, чтобы оно всегда имело правильные данные:Проверено на rextester.com
Естественно, это означает, что вам нужно явно указывать
column01_length
правильную длину при каждом заполненииcolumn01
(при вставках и обновлениях). Это может быть сложно, потому что вам нужно убедиться, что длина рассчитывается так же, как этоLEN()
делает функция T-SQL . В частности, необходимо игнорировать конечные пробелы, что не обязательно означает, как длина вычисляется по умолчанию в различных языках программирования, на которых написаны клиентские приложения. Логика может быть проста для учета в вызывающей программе, но вам необходимо осознавая разницу в первую очередь.Опцией может быть
INSERT/UPDATE
триггер 1, чтобы указать правильное значение для столбца, поэтому оно выглядит так, как оно рассчитывается для клиентских приложений.1 Как объясняется в разделе «Триггеры по сравнению с ограничениями» , для этого вам потребуется использовать триггер INSTEAD OF. Триггер AFTER просто никогда не будет выполнен, потому что отсутствующая длина не выполнит проверочное ограничение, а это, в свою очередь, помешает запуску триггера. Триггеры INSTEAD OF, однако, имеют свои собственные ограничения ( краткий обзор см. В Руководстве по планированию триггеров DML ).
источник
Я не уверен, как это будет работать, и, возможно, есть гораздо более простой способ добиться этого, который я упустил из виду, но это должно делать то, что вам нужно, если вы заинтересованы только в обеспечении уникальности.
источник