2009-03-16 38 views
20

Hệ thống của chúng tôi chạy trên SQL Server 2000 và chúng tôi đang trong quá trình chuẩn bị nâng cấp lên SQL Server 2008. Chúng tôi có rất nhiều mã kích hoạt nơi chúng tôi cần phát hiện một thay đổi trong một cột nhất định và sau đó hoạt động trên cột đó nếu nó đã thay đổi.Phương pháp hiệu quả nhất để phát hiện thay đổi cột trong MS SQL Server

Rõ ràng SQL Server cung cấp các chức năng UPDATE()COLUMNS_UPDATED(), nhưng các chức năng này chỉ cho bạn biết các cột đã được liên quan đến các câu lệnh SQL, không cột đã thực sự thay đổi.

Để xác định các cột đã thay đổi, bạn cần có mã tương tự như sau (đối với một cột có hỗ trợ NULLs):

IF UPDATE(Col1) 
    SELECT @col1_changed = COUNT(*) 
    FROM Inserted i 
     INNER JOIN Deleted d ON i.Table_ID = d.Table_ID 
    WHERE ISNULL(i.Col1, '<unique null value>') 
      != ISNULL(i.Col1, '<unique null value>') 

Mã này cần phải được lặp lại cho mỗi cột bạn quan tâm đến kiểm tra. Sau đó, bạn có thể kiểm tra giá trị 'đã thay đổi' để xác định có thực hiện các hoạt động đắt tiền hay không. Tất nhiên, mã này là chính nó có vấn đề, vì nó chỉ cho bạn biết rằng ít nhất một giá trị trong cột đã thay đổi trên tất cả các hàng đã được sửa đổi.

Bạn có thể kiểm tra báo cáo UPDATE cá nhân với một cái gì đó như thế này:

UPDATE Table SET Col1 = CASE WHEN i.Col1 = d.Col1 
      THEN Col1 
      ELSE dbo.fnTransform(Col1) END 
FROM Inserted i 
    INNER JOIN Deleted d ON i.Table_ID = d.Table_ID 

... nhưng điều này không làm việc tốt khi bạn đang cần để gọi một thủ tục lưu trữ. Trong những trường hợp đó, bạn phải quay trở lại các cách tiếp cận khác theo như tôi có thể nói.

Câu hỏi của tôi là liệu có ai có cái nhìn sâu sắc (hay, tốt hơn, dữ liệu cứng) về cách tiếp cận tốt nhất/rẻ nhất là vấn đề dự đoán hoạt động của cơ sở dữ liệu trong trình kích hoạt hay không. đã thực sự thay đổi hay không. Cả hai phương pháp trên đều không lý tưởng và tôi đã tự hỏi liệu có phương pháp nào tốt hơn hay không.

+1

Tôi đã thêm một câu trả lời mới cho câu hỏi này cũ mà là liên quan: http://stackoverflow.com/questions/1254787/sql-server-update-trigger -get-only-modified-fields/8020461 # 8020461 –

+0

Điều đó rất thú vị, cảm ơn những người đứng đầu! – mwigdahl

Trả lời

7

Mặc dù HLGEM đã đưa ra một số lời khuyên tốt ở trên, nhưng đó không phải là những gì tôi cần. Tôi đã thực hiện khá nhiều thử nghiệm trong vài ngày qua, và tôi đã tìm ra ít nhất là chia sẻ kết quả ở đây cho rằng có vẻ như không có thêm thông tin nào sẽ được sắp tới.

Tôi thiết lập một bảng có hiệu quả là một tập hợp con nhỏ hơn (9 cột) của một trong các bảng chính của hệ thống của chúng tôi và điền nó với dữ liệu sản xuất sao cho nó sâu như phiên bản sản xuất của bảng.

Sau đó, tôi nhân bản bảng đó và lần đầu tiên viết một trình kích hoạt cố gắng phát hiện từng thay đổi cột riêng lẻ và sau đó xác định từng cột cập nhật về dữ liệu trong cột đó có thực sự thay đổi hay không.

Đối với bảng thứ hai, tôi đã viết trình kích hoạt sử dụng logic CASE có điều kiện mở rộng để thực hiện tất cả cập nhật cho tất cả các cột trong một câu lệnh.

sau đó tôi chạy 4 kiểm tra:

  1. Một bản cập nhật đơn cột để một hàng duy nhất
  2. Một đơn cột cập nhật cho 10000 hàng
  3. Một cập nhật chín cột để một hàng duy nhất
  4. Bản cập nhật chín cột cho 10000 hàng

Tôi lặp lại thử nghiệm này cho cả hai phiên bản được lập chỉ mục và không được lập chỉ mục của bảng và sau đó lặp lại e toàn bộ điều trên SQL 2000 và SQL 2008 máy chủ.

Các kết quả tôi nhận được là khá thú vị:

Phương pháp thứ hai (một báo cáo cập nhật đơn với logic TRƯỜNG HỢP lông trong mệnh đề SET) đã thống nhất có hiệu suất tốt hơn so với việc phát hiện sự thay đổi cá nhân (đến một mức độ nhiều hay ít tùy thuộc vào thử nghiệm) với ngoại lệ duy nhất của một thay đổi cột đơn ảnh hưởng đến nhiều hàng nơi cột được lập chỉ mục, chạy trên SQL 2000. Trong trường hợp cụ thể của chúng tôi, chúng tôi không làm nhiều, cập nhật sâu như thế này, vì vậy cho mục đích của tôi cách tiếp cận đơn tuyên bố chắc chắn là con đường để đi.


Tôi muốn nghe kết quả của người khác về các loại thử nghiệm tương tự, xem kết luận của tôi có phổ biến như tôi nghi ngờ hay không.

Để giúp bạn bắt đầu, đây là kịch bản thử nghiệm tôi đã sử dụng - bạn sẽ rõ ràng là cần phải đưa ra các dữ liệu khác để cư nó với:

create table test1 
( 
    t_id int NOT NULL PRIMARY KEY, 
    i1 int NULL, 
    i2 int NULL, 
    i3 int NULL, 
    v1 varchar(500) NULL, 
    v2 varchar(500) NULL, 
    v3 varchar(500) NULL, 
    d1 datetime NULL, 
    d2 datetime NULL, 
    d3 datetime NULL 
) 

create table test2 
( 
    t_id int NOT NULL PRIMARY KEY, 
    i1 int NULL, 
    i2 int NULL, 
    i3 int NULL, 
    v1 varchar(500) NULL, 
    v2 varchar(500) NULL, 
    v3 varchar(500) NULL, 
    d1 datetime NULL, 
    d2 datetime NULL, 
    d3 datetime NULL 
) 

-- optional indexing here, test with it on and off... 
CREATE INDEX [IX_test1_i1] ON [dbo].[test1] ([i1]) 
CREATE INDEX [IX_test1_i2] ON [dbo].[test1] ([i2]) 
CREATE INDEX [IX_test1_i3] ON [dbo].[test1] ([i3]) 
CREATE INDEX [IX_test1_v1] ON [dbo].[test1] ([v1]) 
CREATE INDEX [IX_test1_v2] ON [dbo].[test1] ([v2]) 
CREATE INDEX [IX_test1_v3] ON [dbo].[test1] ([v3]) 
CREATE INDEX [IX_test1_d1] ON [dbo].[test1] ([d1]) 
CREATE INDEX [IX_test1_d2] ON [dbo].[test1] ([d2]) 
CREATE INDEX [IX_test1_d3] ON [dbo].[test1] ([d3]) 

CREATE INDEX [IX_test2_i1] ON [dbo].[test2] ([i1]) 
CREATE INDEX [IX_test2_i2] ON [dbo].[test2] ([i2]) 
CREATE INDEX [IX_test2_i3] ON [dbo].[test2] ([i3]) 
CREATE INDEX [IX_test2_v1] ON [dbo].[test2] ([v1]) 
CREATE INDEX [IX_test2_v2] ON [dbo].[test2] ([v2]) 
CREATE INDEX [IX_test2_v3] ON [dbo].[test2] ([v3]) 
CREATE INDEX [IX_test2_d1] ON [dbo].[test2] ([d1]) 
CREATE INDEX [IX_test2_d2] ON [dbo].[test2] ([d2]) 
CREATE INDEX [IX_test2_d3] ON [dbo].[test2] ([d3]) 

insert into test1 (t_id, i1, i2, i3, v1, v2, v3, d1, d2, d3) 
-- add data population here... 

insert into test2 (t_id, i1, i2, i3, v1, v2, v3, d1, d2, d3) 
select t_id, i1, i2, i3, v1, v2, v3, d1, d2, d3 from test1 

go 

create trigger test1_update on test1 for update 
as 
begin 

declare @i1_changed int, 
    @i2_changed int, 
    @i3_changed int, 
    @v1_changed int, 
    @v2_changed int, 
    @v3_changed int, 
    @d1_changed int, 
    @d2_changed int, 
    @d3_changed int 

IF UPDATE(i1) 
    SELECT @i1_changed = COUNT(*) FROM Inserted i INNER JOIN Deleted d 
     ON i.t_id = d.t_id WHERE ISNULL(i.i1,0) != ISNULL(d.i1,0) 
IF UPDATE(i2) 
    SELECT @i2_changed = COUNT(*) FROM Inserted i INNER JOIN Deleted d 
     ON i.t_id = d.t_id WHERE ISNULL(i.i2,0) != ISNULL(d.i2,0) 
IF UPDATE(i3) 
    SELECT @i3_changed = COUNT(*) FROM Inserted i INNER JOIN Deleted d 
     ON i.t_id = d.t_id WHERE ISNULL(i.i3,0) != ISNULL(d.i3,0) 
IF UPDATE(v1) 
    SELECT @v1_changed = COUNT(*) FROM Inserted i INNER JOIN Deleted d 
     ON i.t_id = d.t_id WHERE ISNULL(i.v1,'') != ISNULL(d.v1,'') 
IF UPDATE(v2) 
    SELECT @v2_changed = COUNT(*) FROM Inserted i INNER JOIN Deleted d 
     ON i.t_id = d.t_id WHERE ISNULL(i.v2,'') != ISNULL(d.v2,'') 
IF UPDATE(v3) 
    SELECT @v3_changed = COUNT(*) FROM Inserted i INNER JOIN Deleted d 
     ON i.t_id = d.t_id WHERE ISNULL(i.v3,'') != ISNULL(d.v3,'') 
IF UPDATE(d1) 
    SELECT @d1_changed = COUNT(*) FROM Inserted i INNER JOIN Deleted d 
     ON i.t_id = d.t_id WHERE ISNULL(i.d1,'1/1/1980') != ISNULL(d.d1,'1/1/1980') 
IF UPDATE(d2) 
    SELECT @d2_changed = COUNT(*) FROM Inserted i INNER JOIN Deleted d 
     ON i.t_id = d.t_id WHERE ISNULL(i.d2,'1/1/1980') != ISNULL(d.d2,'1/1/1980') 
IF UPDATE(d3) 
    SELECT @d3_changed = COUNT(*) FROM Inserted i INNER JOIN Deleted d 
     ON i.t_id = d.t_id WHERE ISNULL(i.d3,'1/1/1980') != ISNULL(d.d3,'1/1/1980') 

if (@i1_changed > 0) 
begin 
    UPDATE test1 SET i1 = CASE WHEN i.i1 > d.i1 THEN i.i1 ELSE d.i1 END 
    FROM test1 
     INNER JOIN inserted i ON test1.t_id = i.t_id 
     INNER JOIN deleted d ON i.t_id = d.t_id 
    WHERE i.i1 != d.i1 
end 

if (@i2_changed > 0) 
begin 
    UPDATE test1 SET i2 = CASE WHEN i.i2 > d.i2 THEN POWER(i.i2, 1.1) ELSE POWER(d.i2, 1.1) END 
    FROM test1 
     INNER JOIN inserted i ON test1.t_id = i.t_id 
     INNER JOIN deleted d ON i.t_id = d.t_id 
    WHERE i.i2 != d.i2 
end 

if (@i3_changed > 0) 
begin 
    UPDATE test1 SET i3 = i.i3^d.i3 
    FROM test1 
     INNER JOIN inserted i ON test1.t_id = i.t_id 
     INNER JOIN deleted d ON i.t_id = d.t_id 
    WHERE i.i3 != d.i3 
end 

if (@v1_changed > 0) 
begin 
    UPDATE test1 SET v1 = i.v1 + 'a' 
    FROM test1 
     INNER JOIN inserted i ON test1.t_id = i.t_id 
     INNER JOIN deleted d ON i.t_id = d.t_id 
    WHERE i.v1 != d.v1 
end 

UPDATE test1 SET v2 = LEFT(i.v2, 5) + '|' + RIGHT(d.v2, 5) 
FROM test1 
    INNER JOIN inserted i ON test1.t_id = i.t_id 
    INNER JOIN deleted d ON i.t_id = d.t_id 

if (@v3_changed > 0) 
begin 
    UPDATE test1 SET v3 = LEFT(i.v3, 5) + '|' + LEFT(i.v2, 5) + '|' + LEFT(i.v1, 5) 
    FROM test1 
     INNER JOIN inserted i ON test1.t_id = i.t_id 
     INNER JOIN deleted d ON i.t_id = d.t_id 
    WHERE i.v3 != d.v3 
end 

if (@d1_changed > 0) 
begin 
    UPDATE test1 SET d1 = DATEADD(dd, 1, i.d1) 
    FROM test1 
     INNER JOIN inserted i ON test1.t_id = i.t_id 
     INNER JOIN deleted d ON i.t_id = d.t_id 
    WHERE i.d1 != d.d1 
end 

if (@d2_changed > 0) 
begin 
    UPDATE test1 SET d2 = DATEADD(dd, DATEDIFF(dd, i.d2, d.d2), d.d2) 
    FROM test1 
     INNER JOIN inserted i ON test1.t_id = i.t_id 
     INNER JOIN deleted d ON i.t_id = d.t_id 
    WHERE i.d2 != d.d2 
end 

UPDATE test1 SET d3 = DATEADD(dd, 15, i.d3) 
FROM test1 
    INNER JOIN inserted i ON test1.t_id = i.t_id 
    INNER JOIN deleted d ON i.t_id = d.t_id 

end 

go 

create trigger test2_update on test2 for update 
as 
begin 

    UPDATE test2 SET 
     i1 = 
      CASE 
      WHEN ISNULL(i.i1, 0) != ISNULL(d.i1, 0) 
      THEN CASE WHEN i.i1 > d.i1 THEN i.i1 ELSE d.i1 END 
      ELSE test2.i1 END, 
     i2 = 
      CASE 
      WHEN ISNULL(i.i2, 0) != ISNULL(d.i2, 0) 
      THEN CASE WHEN i.i2 > d.i2 THEN POWER(i.i2, 1.1) ELSE POWER(d.i2, 1.1) END 
      ELSE test2.i2 END, 
     i3 = 
      CASE 
      WHEN ISNULL(i.i3, 0) != ISNULL(d.i3, 0) 
      THEN i.i3^d.i3 
      ELSE test2.i3 END, 
     v1 = 
      CASE 
      WHEN ISNULL(i.v1, '') != ISNULL(d.v1, '') 
      THEN i.v1 + 'a' 
      ELSE test2.v1 END, 
     v2 = LEFT(i.v2, 5) + '|' + RIGHT(d.v2, 5), 
     v3 = 
      CASE 
      WHEN ISNULL(i.v3, '') != ISNULL(d.v3, '') 
      THEN LEFT(i.v3, 5) + '|' + LEFT(i.v2, 5) + '|' + LEFT(i.v1, 5) 
      ELSE test2.v3 END, 
     d1 = 
      CASE 
      WHEN ISNULL(i.d1, '1/1/1980') != ISNULL(d.d1, '1/1/1980') 
      THEN DATEADD(dd, 1, i.d1) 
      ELSE test2.d1 END, 
     d2 = 
      CASE 
      WHEN ISNULL(i.d2, '1/1/1980') != ISNULL(d.d2, '1/1/1980') 
      THEN DATEADD(dd, DATEDIFF(dd, i.d2, d.d2), d.d2) 
      ELSE test2.d2 END, 
     d3 = DATEADD(dd, 15, i.d3) 
    FROM test2 
     INNER JOIN inserted i ON test2.t_id = i.t_id 
     INNER JOIN deleted d ON test2.t_id = d.t_id 

end 

go 

----- 
-- the below code can be used to confirm that the triggers operated identically over both tables after a test 
select top 10 test1.i1, test2.i1, test1.i2, test2.i2, test1.i3, test2.i3, test1.v1, test2.v1, test1.v2, test2.v2, test1.v3, test2.v3, test1.d1, test1.d1, test1.d2, test2.d2, test1.d3, test2.d3 
from test1 inner join test2 on test1.t_id = test2.t_id 
where 
    test1.i1 != test2.i1 or 
    test1.i2 != test2.i2 or 
    test1.i3 != test2.i3 or 
    test1.v1 != test2.v1 or 
    test1.v2 != test2.v2 or 
    test1.v3 != test2.v3 or 
    test1.d1 != test2.d1 or 
    test1.d2 != test2.d2 or 
    test1.d3 != test2.d3 

-- test 1 -- one column, one row 
update test1 set i3 = 64 where t_id = 1000 
go 
update test2 set i3 = 64 where t_id = 1000 
go 

update test1 set i3 = 64 where t_id = 1001 
go 
update test2 set i3 = 64 where t_id = 1001 
go 

-- test 2 -- one column, 10000 rows 
update test1 set v3 = LEFT(v3, 50) where t_id between 10000 and 20000 
go 
update test2 set v3 = LEFT(v3, 50) where t_id between 10000 and 20000 
go 

-- test 3 -- all columns, 1 row, non-self-referential 
update test1 set i1 = 1000, i2 = 2000, i3 = 3000, v1 = 'R12345123', v2 = 'Happy!', v3 = 'I am v3!!!', d1 = '1/1/1985', d2 = '1/1/1988', d3 = NULL 
where t_id = 3000 
go 
update test2 set i1 = 1000, i2 = 2000, i3 = 3000, v1 = 'R12345123', v2 = 'Happy!', v3 = 'I am v3!!!', d1 = '1/1/1985', d2 = '1/1/1988', d3 = NULL 
where t_id = 3000 
go 

-- test 4 -- all columns, 10000 rows, non-self-referential 
update test1 set i1 = 1000, i2 = 2000, i3 = 3000, v1 = 'R12345123', v2 = 'Happy!', v3 = 'I am v3!!!', d1 = '1/1/1985', d2 = '1/1/1988', d3 = NULL 
where t_id between 30000 and 40000 
go 
update test2 set i1 = 1000, i2 = 2000, i3 = 3000, v1 = 'R12345123', v2 = 'Happy!', v3 = 'I am v3!!!', d1 = '1/1/1985', d2 = '1/1/1988', d3 = NULL 
where t_id between 30000 and 40000 
go 

----- 

drop table test1 
drop table test2 
16

Hãy bắt đầu với tôi sẽ không bao giờ và tôi có nghĩa là không bao giờ gọi một proc được lưu trữ trong một kích hoạt. Để tính toán chèn nhiều hàng, bạn sẽ phải trỏ qua proc. Điều này có nghĩa là 200.000 hàng bạn vừa tải mặc dù truy vấn dựa trên tập hợp (nói lên tất cả giá lên 10%) cũng có thể khóa bảng trong nhiều giờ vì trình kích hoạt cố gắng xử lý tải. Thêm vào đó nếu có thay đổi gì đó trong proc, bạn có thể phá vỡ bất kỳ chèn nào vào bàn hoặc thậm chí hoàn toàn treo bảng. Tôi là một beliver công ty mà kích hoạt mã nên gọi không có gì khác ngoài kích hoạt.

Cá nhân tôi chỉ đơn giản là làm nhiệm vụ của mình. Nếu tôi đã viết các hành động tôi muốn làm đúng trong trình kích hoạt, nó sẽ chỉ cập nhật, xóa hoặc chèn nơi các cột đã thay đổi.

Ví dụ: giả sử bạn muốn cập nhật trường last_name mà bạn đang lưu trữ ở hai vị trí do không chuẩn hóa được đặt ở đó vì lý do hiệu suất.

update t 
set lname = i.lname 
from table2 t 
join inserted i on t.fkfield = i.pkfield 
where t.lname <>i.lname 

Như bạn có thể thấy, nó sẽ chỉ cập nhật các tên khác với những gì tôi đang cập nhật trong bảng.

Nếu bạn muốn làm kiểm toán và ghi lại chỉ có những hàng mà thay đổi sau đó làm việc so sánh sử dụng tất cả các lĩnh vực như nơi i.field1 <> d.field1 hoặc i.field2 <> d.field3 (vv qua tất cả các lĩnh vực)

+0

Trong trường hợp bạn trình bày, bạn sẽ kết thúc khóa bảng2 để cập nhật cho mọi sửa đổi bạn thực hiện cho bảng gốc, ngay cả khi bạn không bao giờ sửa đổi lname cả. Đó là một phần của những gì tôi đang cố gắng tránh. Cảm ơn lời khuyên, mặc dù! – mwigdahl

+1

Tôi đã bình chọn điều này vì tôi phát hiện ra một cách khó khăn về việc không gọi SP từ trình kích hoạt .... Không bao giờ nữa! – RolandTumble

9

tôi nghĩ bạn có thể muốn điều tra sử dụng ngoại trừ điều hành. Nó là một toán tử dựa trên thiết lập có thể loại bỏ các hàng không thay đổi. Những điều tốt đẹp là xem xét giá trị null như bình đẳng vì nó trông cho các hàng trong tập đầu tiên được liệt kê trước TRỪ điều hành chứ không phải trong lần thứ hai niêm yết Sau khi trừ

WITH ChangedData AS (
SELECT d.Table_ID , d.Col1 FROM deleted d 
EXCEPT 
SELECT i.Table_ID , i.Col1 FROM inserted i 
) 
/*Do Something with the ChangedData */ 

này xử lý các vấn đề của các cột cho phép Nulls không việc sử dụng ISNULL() trong trình kích hoạt và chỉ trả về các id của các hàng có thay đổi đối với col1 cho phương pháp tiếp cận dựa trên tập hợp tốt đẹp để phát hiện các thay đổi. Tôi đã không thử nghiệm cách tiếp cận nhưng nó cũng có thể được giá trị thời gian của bạn. Tôi nghĩ rằng EXCEPT đã được giới thiệu với SQL Server 2005.

+0

Tôi sẽ điều tra điều này, cảm ơn bạn! – mwigdahl

+0

Tôi đang sử dụng phương pháp này khá nhiều trong cơ sở dữ liệu của tôi và mặc dù tôi đã không đo hiệu suất có vẻ khá nhanh. Tôi đã không đo lường vì tôi không thấy một hit hiệu suất. BTW, logic của tôi đặt INSERT trước EXCEPT và xử lý việc phát hiện các thay đổi INSERT và UPDATE. BTW, tôi đã không sử dụng tuyên bố "WITH" nhưng điều đó có vẻ thú vị. Xem câu trả lời hơi khác của tôi bên dưới. –

5

Tôi khuyên bạn nên sử dụng toán tử EXCEPT được đề cập bởi Todd/arghtype ở trên.

Tôi đã thêm câu trả lời này bởi vì tôi đặt "được chèn" trước khi "xóa" để INSERTs sẽ được phát hiện cũng như UPDATE. Vì vậy, tôi thường có thể có một kích hoạt để bao gồm cả chèn và cập nhật. Cũng có thể phát hiện xóa bằng cách thêm OR (NOT EXISTS (SELECT * FROM chèn) VÀ EXISTS (SELECT * FROM deleted))

Xác định xem giá trị có thay đổi chỉ trong các cột được chỉ định hay không. Tôi đã không điều tra hiệu suất của nó so với các giải pháp khác nhưng nó hoạt động tốt trong cơ sở dữ liệu của tôi.

Sử dụng toán tử bộ EXCEPT để trả về bất kỳ hàng nào từ truy vấn bên trái cũng không được tìm thấy trên truy vấn phù hợp. Mã này có thể được sử dụng trong các trình kích hoạt INSERT, UPDATE và DELETE.

Cột "PKID" là khóa chính. Nó là cần thiết để cho phép kết hợp giữa hai bộ. Nếu bạn có nhiều cột cho khóa chính thì bạn sẽ cần phải bao gồm tất cả các cột để thực hiện khớp chính xác giữa các tập hợp được chèn và đã xóa.

-- Only do trigger logic if specific field values change. 
IF EXISTS(SELECT PKID 
       ,Column1 
       ,Column7 
       ,Column10 
      FROM inserted 
      EXCEPT 
      SELECT PKID 
       ,Column1 
       ,Column7 
       ,Column10 
      FROM deleted) -- Tests for modifications to fields that we are interested in 
OR (NOT EXISTS(SELECT * FROM inserted) AND EXISTS(SELECT * FROM deleted)) -- Have a deletion 
BEGIN 
      -- Put code here that does the work in the trigger 

END 

Nếu bạn muốn sử dụng các hàng đã thay đổi trong logic kích hoạt tiếp theo, tôi thường đưa kết quả của truy vấn EXCEPT vào biến bảng có thể được tham chiếu sau này.

Tôi hy vọng điều này là quan tâm :-)

Các vấn đề liên quan