Використовуйте функцію "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.