Sử dụng chức năng của LEN và sử dụng trong chức năng của WHEN


12

Tôi có bảng này:

CREATE TABLE Table01 (column01 nvarchar(100));

Và tôi muốn tạo một chỉ mục duy nhất trên cột01 với điều kiện này LEN (cột01)> = 5

Tôi đã thử:

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

Tôi đã nhận:

Mệnh đề WHERE không chính xác cho chỉ mục được lọc 'UIX_01' trên bảng 'Bảng01'.

Và:

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

Sản xuất:

Chỉ mục được lọc 'UIX_01' không thể được tạo trên bảng 'Bảng01' vì cột 'cột01_length' trong biểu thức lọc là cột được tính toán. Viết lại biểu thức lọc để nó không bao gồm cột này.

Câu trả lời:


15

Một phương pháp để khắc phục hạn chế chỉ mục được lọc là với chế độ xem được lập chỉ mục:

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

BIÊN TẬP:

Làm cách nào để xác định chế độ xem nếu tôi có hai cột trong chỉ mục? TẠO INDEX UNIITE UIX_01 TRÊN Bảng01 (cột01, cột02) WHERE LEN (cột01)> = 5

Cách tiếp cận khung nhìn được lập chỉ mục có thể được mở rộng cho khóa tổng hợp bằng cách thêm các cột khóa khác vào định nghĩa và chỉ mục khung nhìn. Bộ lọc tương tự được áp dụng trong định nghĩa chế độ xem nhưng tính duy nhất của các hàng đủ điều kiện được thi hành bởi khóa tổng hợp thay vì giá trị cột đơn:

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

Và tôi hy vọng điều này sẽ thực hiện tốt hơn nhiều so với sự quái dị của tôi.
James Anderson

@Dan Guzman tôi có nên sử dụng 'VỚI SCHEMABINDING' không?
đam mê

2
@Jalil Có, SCHEMABINDINGlà bắt buộc cho chế độ xem được lập chỉ mục. Hàm ý tất nhiên là bạn sẽ cần bỏ chế độ xem trước khi thay đổi bảng. Công cụ như SSDT sẽ tự động xử lý sự phụ thuộc đó.
Dan Guzman

Làm cách nào để xác định chế độ xem nếu tôi có hai cột trong chỉ mục? TẠO ĐỘC LẬP ĐỘC ĐÁO UIX_01 TRÊN Bảng01 (cột01, cột02) WHERE LEN (cột01)> = 5;
đam mê

@Jalil, tôi đã thêm một ví dụ khóa tổng hợp vào câu trả lời của tôi.
Dan Guzman

5

Đây dường như là một trong nhiều hạn chế của các chỉ mục được lọc. Đang cố gắng bỏ qua nó vớiLIKE cách sử dụng WHERE column01 LIKE '_____'cũng không hoạt động, tạo ra cùng một thông báo lỗi ( "Mệnh đề WHERE không chính xác ..." ).

Bên cạnh VIEW giải pháp, một cách khác là chuyển đổi cột được tính thành cột thông thường và thêm một CHECKràng buộc để nó luôn có dữ liệu hợp lệ:

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 ;

Đã thử nghiệm tại rextester.com

Đương nhiên, điều đó có nghĩa là bạn cần phải cư trú rõ ràng column01_length với độ dài chính xác mỗi lần bạn nhập column01(trên các bản chèn và cập nhật). Điều đó có thể khó khăn, bởi vì bạn cần đảm bảo rằng độ dài được tính giống như hàm T-SQL LEN()thực hiện. Cụ thể, các khoảng trắng theo sau cần được bỏ qua, không nhất thiết là cách tính độ dài theo mặc định trong các ngôn ngữ lập trình khác nhau mà ứng dụng khách được viết. Logic có thể dễ dàng được giải thích trong trình gọi, nhưng bạn cần phải có nhận thức được sự khác biệt ở nơi đầu tiên.

Một tùy chọn sẽ là một INSERT/UPDATEkích hoạt 1 để cung cấp giá trị chính xác cho cột, vì vậy nó xuất hiện dưới dạng tính toán cho các ứng dụng khách.


1 Như đã giải thích trong Triggers So với các ràng buộc , bạn sẽ cần sử dụng trình kích hoạt INSTEAD OF cho việc này. Một trình kích hoạt SAU đơn giản sẽ không bao giờ thực thi, bởi vì độ dài vắng mặt sẽ không thực hiện được ràng buộc kiểm tra và do đó, sẽ ngăn chặn trình kích hoạt chạy. Tuy nhiên, INSTEAD OF kích hoạt có những hạn chế riêng (xem Nguyên tắc lập kế hoạch kích hoạt DML để biết tổng quan nhanh).


1

Tôi không chắc điều này sẽ thực hiện như thế nào và có thể có một cách dễ dàng hơn để đạt được điều này mà tôi đã bỏ qua, nhưng điều này sẽ làm những gì bạn cần nếu bạn chỉ quan tâm đến việc thực thi tính duy nhất.

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
Sử dụng hàm có giá trị vô hướng trong ràng buộc kiểm tra hoặc định nghĩa cột được tính toán sẽ buộc tất cả các truy vấn chạm vào bảng để chạy một cách an toàn, ngay cả khi chúng không tham chiếu cột.
Erik Darling

2
@sp_BlitzErik Yep và đó thậm chí có thể không phải là điều tồi tệ nhất về giải pháp này :). Tôi chỉ muốn xem nếu nó sẽ làm việc, do đó cảnh báo hiệu suất.
James Anderson
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.