Використовуйте функцію "LEN" у пункті "WHERE" у "CREATE UNIQUE INDEX"


12

У мене є ця таблиця:

CREATE TABLE Table01 (column01 nvarchar(100));

І я хочу створити унікальний індекс у колонці01 з цією умовою LEN (колонка01)> = 5

Я намагався:

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

Я зрозумів, я отримав:

Неправильний пункт WHERE для відфільтрованого індексу "UIX_01" у таблиці "Таблиця01".

І:

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

Виробляє:

Відфільтрований індекс 'UIX_01' неможливо створити у таблиці 'Таблиця01', оскільки стовпець 'стовпчик__ довжина' у виразі фільтра є обчисленим стовпцем. Перепишіть вираз фільтра, щоб він не включав цей стовпець.

Відповіді:


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

Редагувати:

Як мені визначити подання, якщо в індексі є два стовпці? СТВОРЮЙТЕ УНІКАЛЬНИЙ ІНДЕКС UIX_01 НА Таблицю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

І я сподіваюся, що це буде набагато краще, ніж моє чудовисько.
— Джеймс Андерсон

@ Дан Гузман повинен використовувати "З РЕЧЕННЯМ"?
— витівка

2
@Jalil Так, SCHEMABINDINGпотрібен для індексованого перегляду. Зрозуміло, що, звичайно, вам потрібно буде скинути подання, перш ніж змінювати таблицю. Інструменти, такі як SSDT, забезпечать цю залежність автоматично.
— Дан Гузман

Як мені визначити подання, якщо в індексі є два стовпці? СТВОРИТИ УНІКАЛЬНИЙ ІНДЕКС UIX_01 НА Таблицю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. ПІСЛЯ тригер просто ніколи не виконується, оскільки відсутня довжина не зможе перевірити обмеження, а це, в свою чергу, не дозволить триггеру запускатися. Однак встановити тригери мають свої обмеження ( для швидкого огляду див. Посібники з планування тригерів 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 Так, це може бути навіть не найгірше в цьому рішенні :). Я просто хотів подивитися, чи спрацює це, отже, попередження про продуктивність.
— Джеймс Андерсон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.