Skip to content

Commit 6718d45

Browse files
authored
Merge pull request #33338 from rwestMSFT/rw-0228-fix-375105
Refresh COLUMNS UPDATED (UUF 375105)
2 parents 5b434c3 + e157d11 commit 6718d45

File tree

1 file changed

+185
-166
lines changed

1 file changed

+185
-166
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
---
2-
title: COLUMNS_UPDATED (Transact-SQL)
3-
description: "COLUMNS_UPDATED (Transact-SQL)"
2+
title: "COLUMNS_UPDATED (Transact-SQL)"
3+
description: The COLUMNS_UPDATED Transact-SQL function returns a bit pattern indicating the inserted or updated columns of a table or view.
44
author: markingmyname
55
ms.author: maghan
6-
ms.date: "07/25/2017"
6+
ms.reviewer: randolphwest
7+
ms.date: 02/28/2025
78
ms.service: sql
89
ms.subservice: t-sql
910
ms.topic: reference
@@ -23,185 +24,203 @@ dev_langs:
2324

2425
[!INCLUDE [SQL Server Azure SQL Database Azure SQL Managed Instance](../../includes/applies-to-version/sql-asdb-asdbmi.md)]
2526

26-
This function returns a **varbinary** bit pattern indicating the inserted or updated columns of a table or view. Use `COLUMNS_UPDATED` anywhere inside the body of a [!INCLUDE[tsql](../../includes/tsql-md.md)] INSERT or UPDATE trigger to test whether the trigger should execute certain actions.
27-
27+
This function returns a **varbinary** bit pattern indicating the inserted or updated columns of a table or view. Use `COLUMNS_UPDATED` anywhere inside the body of a [!INCLUDE [tsql](../../includes/tsql-md.md)] `INSERT` or `UPDATE` trigger to test whether the trigger should execute certain actions.
28+
2829
:::image type="icon" source="../../includes/media/topic-link-icon.svg" border="false"::: [Transact-SQL syntax conventions](../../t-sql/language-elements/transact-sql-syntax-conventions-transact-sql.md)
29-
30-
## Syntax
31-
30+
31+
## Syntax
32+
3233
```syntaxsql
33-
COLUMNS_UPDATED ( )
34-
```
34+
COLUMNS_UPDATED ( )
35+
```
3536

3637
## Return types
38+
3739
**varbinary**
38-
39-
## Remarks
40-
`COLUMNS_UPDATED` tests for UPDATE or INSERT actions performed on multiple columns. To test for UPDATE or INSERT attempts on one column, use [UPDATE()](../../t-sql/functions/update-trigger-functions-transact-sql.md).
41-
42-
`COLUMNS_UPDATED` returns one or more bytes that are ordered from left to right. The rightmost bit of each byte is the least significant bit. The rightmost bit of the leftmost byte represents the first table column in the table, the next bit to the left represents the second column, and so on. `COLUMNS_UPDATED` returns multiple bytes if the table on which the trigger is created contains more than eight columns, with the least significant byte being the leftmost. `COLUMNS_UPDATED` returns TRUE for all columns in INSERT actions because the columns have either explicit values or implicit (NULL) values inserted.
43-
44-
To test for updates or inserts to specific columns, follow the syntax with a bitwise operator and an integer bitmask of the tested columns. For example, say that table **t1** contains columns **C1**, **C2**, **C3**, **C4**, and **C5**. To verify that columns **C2**, **C3**, and **C4** all successfully updated (with table **t1** having an UPDATE trigger), follow the syntax with **& 14**. To test whether only column **C2** is updated, specify **& 2**. See [Example A](#a-using-columns_updated-to-test-the-first-eight-columns-of-a-table) and [Example B](#b-using-columns_updated-to-test-more-than-eight-columns) for actual examples.
45-
46-
Use `COLUMNS_UPDATED` anywhere inside a [!INCLUDE[tsql](../../includes/tsql-md.md)] INSERT or UPDATE trigger.
47-
48-
The ORDINAL_POSITION column of the INFORMATION_SCHEMA.COLUMNS view is not compatible with the bit pattern of columns returned by `COLUMNS_UPDATED`. To obtain a bit pattern compatible with `COLUMNS_UPDATED`, reference the `ColumnID` property of the `COLUMNPROPERTY` system function when querying the `INFORMATION_SCHEMA.COLUMNS` view, as shown in the following example.
49-
40+
41+
## Remarks
42+
43+
`COLUMNS_UPDATED` tests for `UPDATE` or `INSERT` actions performed on multiple columns. To test for `UPDATE` or `INSERT` attempts on one column, use [UPDATE()](../../t-sql/functions/update-trigger-functions-transact-sql.md).
44+
45+
`COLUMNS_UPDATED` returns one or more bytes that are ordered from left to right. The rightmost bit of each byte is the least significant bit. The rightmost bit of the leftmost byte represents the first table column in the table, the next bit to the left represents the second column, and so on. `COLUMNS_UPDATED` returns multiple bytes if the table on which the trigger is created contains more than eight columns, with the least significant byte being the leftmost. `COLUMNS_UPDATED` returns `TRUE` for all columns in `INSERT` actions because the columns have either explicit values or implicit (NULL) values inserted.
46+
47+
To test for updates or inserts to specific columns, follow the syntax with a bitwise operator and an integer bitmask of the tested columns. For example, say that table `t1` contains columns `C1`, `C2`, `C3`, `C4`, and `C5`. To verify that columns `C2`, `C3`, and `C4` all successfully updated (with table `t1` having an `UPDATE` trigger), follow the syntax with `& 14`. To test whether only column `C2` is updated, specify `& 2`. See [Example A](#a-use-columns_updated-to-test-the-first-eight-columns-of-a-table) and [Example B](#b-use-columns_updated-to-test-more-than-eight-columns) for actual examples.
48+
49+
Use `COLUMNS_UPDATED` anywhere inside a [!INCLUDE [tsql](../../includes/tsql-md.md)] `INSERT` or `UPDATE` trigger.
50+
51+
The `ORDINAL_POSITION` column of the `INFORMATION_SCHEMA.COLUMNS` view isn't compatible with the bit pattern of columns returned by `COLUMNS_UPDATED`. To obtain a bit pattern compatible with `COLUMNS_UPDATED`, reference the `ColumnID` property of the `COLUMNPROPERTY` system function when querying the `INFORMATION_SCHEMA.COLUMNS` view, as shown in the following example.
52+
5053
```sql
51-
SELECT TABLE_NAME, COLUMN_NAME,
52-
COLUMNPROPERTY(OBJECT_ID(TABLE_SCHEMA + '.' + TABLE_NAME),
53-
COLUMN_NAME, 'ColumnID') AS COLUMN_ID
54-
FROM AdventureWorks2022.INFORMATION_SCHEMA.COLUMNS
55-
WHERE TABLE_NAME = 'Person';
56-
```
57-
58-
If a trigger applies to a column, the `COLUMNS_UPDATED` returns as `true` or `1`, even if the column value remains unchanged. This is by-design, and the trigger should implement business logic that determines if the insert/update/delete operation is permissible or not.
59-
54+
SELECT TABLE_NAME, COLUMN_NAME,
55+
COLUMNPROPERTY(OBJECT_ID(TABLE_SCHEMA + '.' + TABLE_NAME),
56+
COLUMN_NAME, 'ColumnID') AS COLUMN_ID
57+
FROM AdventureWorks2022.INFORMATION_SCHEMA.COLUMNS
58+
WHERE TABLE_NAME = 'Person';
59+
```
60+
61+
If a trigger applies to a column, the `COLUMNS_UPDATED` returns as `true` or `1`, even if the column value remains unchanged. This is by-design, and the trigger should implement business logic that determines if the insert/update/delete operation is permissible or not.
62+
6063
## Column sets
64+
6165
When a column set is defined on a table, the `COLUMNS_UPDATED` function behaves in the following ways:
62-
- When explicitly updating a member column of the column set, the corresponding bit for that column is set to 1, and the column set bit is set to 1.
63-
- When a explicitly updating a column set, the column set bit is set to 1, and the bits for all of the sparse columns in that table are set to 1.
64-
- For insert operations, all bits are set to 1.
65-
66-
Because changes to a column set cause the bits of all columns in the column set to reset to 1, unchanged columns in a column set will appear modified. See [Use Column Sets](../../relational-databases/tables/use-column-sets.md) for more information about column sets.
67-
68-
## Examples
69-
70-
### A. Using COLUMNS_UPDATED to test the first eight columns of a table
66+
67+
- When explicitly updating a member column of the column set, the corresponding bit for that column is set to `1`, and the column set bit is set to `1`.
68+
69+
- When explicitly updating a column set, the column set bit is set to `1`, and the bits for all of the sparse columns in that table are set to `1`.
70+
71+
- For insert operations, all bits are set to `1`.
72+
73+
Because changes to a column set cause the bits of all columns in the column set to reset to `1`, unchanged columns in a column set will appear modified. See [Use Column Sets](../../relational-databases/tables/use-column-sets.md) for more information about column sets.
74+
75+
## Examples
76+
77+
### A. Use COLUMNS_UPDATED to test the first eight columns of a table
78+
7179
This example creates two tables: `employeeData` and `auditEmployeeData`. The `employeeData` table holds sensitive employee payroll information and human resources department members can modify it. If the social security number (SSN), yearly salary, or bank account number for an employee changes, an audit record is generated and inserted into the `auditEmployeeData` audit table.
72-
80+
7381
With the `COLUMNS_UPDATED()` function, we can quickly test for any changes made to columns containing sensitive employee information. Using `COLUMNS_UPDATED()` this way works only when trying to detect changes to the first eight columns in the table.
74-
82+
7583
```sql
76-
USE AdventureWorks2022;
77-
GO
78-
IF EXISTS(SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
79-
WHERE TABLE_NAME = 'employeeData')
80-
DROP TABLE employeeData;
81-
IF EXISTS(SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
82-
WHERE TABLE_NAME = 'auditEmployeeData')
83-
DROP TABLE auditEmployeeData;
84-
GO
85-
CREATE TABLE dbo.employeeData (
86-
emp_id INT NOT NULL PRIMARY KEY,
87-
emp_bankAccountNumber CHAR (10) NOT NULL,
88-
emp_salary INT NOT NULL,
89-
emp_SSN CHAR (11) NOT NULL,
90-
emp_lname NCHAR (32) NOT NULL,
91-
emp_fname NCHAR (32) NOT NULL,
92-
emp_manager INT NOT NULL
93-
);
94-
GO
95-
CREATE TABLE dbo.auditEmployeeData (
96-
audit_log_id uniqueidentifier DEFAULT NEWID() PRIMARY KEY,
97-
audit_log_type CHAR (3) NOT NULL,
98-
audit_emp_id INT NOT NULL,
99-
audit_emp_bankAccountNumber CHAR (10) NULL,
100-
audit_emp_salary INT NULL,
101-
audit_emp_SSN CHAR (11) NULL,
102-
audit_user sysname DEFAULT SUSER_SNAME(),
103-
audit_changed DATETIME DEFAULT GETDATE()
104-
);
105-
GO
106-
CREATE TRIGGER dbo.updEmployeeData
107-
ON dbo.employeeData
108-
AFTER UPDATE AS
109-
/* Check whether columns 2, 3 or 4 have been updated. If any or all
84+
USE AdventureWorks2022;
85+
GO
86+
87+
IF EXISTS (SELECT TABLE_NAME
88+
FROM INFORMATION_SCHEMA.TABLES
89+
WHERE TABLE_NAME = 'employeeData')
90+
DROP TABLE employeeData;
91+
92+
IF EXISTS (SELECT TABLE_NAME
93+
FROM INFORMATION_SCHEMA.TABLES
94+
WHERE TABLE_NAME = 'auditEmployeeData')
95+
DROP TABLE auditEmployeeData;
96+
GO
97+
98+
CREATE TABLE dbo.employeeData
99+
(
100+
emp_id INT NOT NULL PRIMARY KEY,
101+
emp_bankAccountNumber CHAR (10) NOT NULL,
102+
emp_salary INT NOT NULL,
103+
emp_SSN CHAR (11) NOT NULL,
104+
emp_lname NCHAR (32) NOT NULL,
105+
emp_fname NCHAR (32) NOT NULL,
106+
emp_manager INT NOT NULL
107+
);
108+
GO
109+
110+
CREATE TABLE dbo.auditEmployeeData
111+
(
112+
audit_log_id UNIQUEIDENTIFIER DEFAULT NEWID() PRIMARY KEY,
113+
audit_log_type CHAR (3) NOT NULL,
114+
audit_emp_id INT NOT NULL,
115+
audit_emp_bankAccountNumber CHAR (10) NULL,
116+
audit_emp_salary INT NULL,
117+
audit_emp_SSN CHAR (11) NULL,
118+
audit_user sysname DEFAULT SUSER_SNAME(),
119+
audit_changed DATETIME DEFAULT GETDATE()
120+
);
121+
GO
122+
123+
CREATE TRIGGER dbo.updEmployeeData
124+
ON dbo.employeeData
125+
AFTER UPDATE AS
126+
/* Check whether columns 2, 3 or 4 have been updated. If any or all
110127
columns 2, 3 or 4 have been changed, create an audit record.
111128
The bitmask is: power(2, (2-1)) + power(2, (3-1)) + power(2, (4-1)) = 14.
112129
This bitmask translates into base_10 as: 2 + 4 + 8 = 14.
113-
To test whether all columns 2, 3, and 4 are updated, use = 14 instead of > 0
130+
To test whether all columns 2, 3, and 4 are updated, use = 14 instead of > 0
114131
(below). */
115-
116-
IF (COLUMNS_UPDATED() & 14) > 0
117-
/* Use IF (COLUMNS_UPDATED() & 14) = 14 to see whether all columns 2, 3,
118-
and 4 are updated. */
119-
BEGIN
120-
-- Audit OLD record.
121-
INSERT INTO dbo.auditEmployeeData
122-
(audit_log_type,
123-
audit_emp_id,
124-
audit_emp_bankAccountNumber,
125-
audit_emp_salary,
126-
audit_emp_SSN)
127-
SELECT 'OLD',
128-
del.emp_id,
129-
del.emp_bankAccountNumber,
130-
del.emp_salary,
131-
del.emp_SSN
132-
FROM deleted del;
133-
134-
-- Audit NEW record.
135-
INSERT INTO dbo.auditEmployeeData
136-
(audit_log_type,
137-
audit_emp_id,
138-
audit_emp_bankAccountNumber,
139-
audit_emp_salary,
140-
audit_emp_SSN)
141-
SELECT 'NEW',
142-
ins.emp_id,
143-
ins.emp_bankAccountNumber,
144-
ins.emp_salary,
145-
ins.emp_SSN
146-
FROM inserted ins;
147-
END;
148-
GO
149-
150-
/* Inserting a new employee does not cause the UPDATE trigger to fire. */
151-
INSERT INTO employeeData
152-
VALUES ( 101, 'USA-987-01', 23000, 'R-M53550M', N'Mendel', N'Roland', 32);
153-
GO
154-
132+
133+
IF (COLUMNS_UPDATED() & 14) > 0
134+
/* Use IF (COLUMNS_UPDATED() & 14) = 14 to see whether all columns 2, 3,
135+
and 4 are updated. */
136+
BEGIN
137+
-- Audit OLD record.
138+
INSERT INTO dbo.auditEmployeeData (
139+
audit_log_type,
140+
audit_emp_id,
141+
audit_emp_bankAccountNumber,
142+
audit_emp_salary,
143+
audit_emp_SSN)
144+
SELECT 'OLD',
145+
del.emp_id,
146+
del.emp_bankAccountNumber,
147+
del.emp_salary,
148+
del.emp_SSN
149+
FROM deleted AS del;
150+
-- Audit NEW record.
151+
INSERT INTO dbo.auditEmployeeData (
152+
audit_log_type,
153+
audit_emp_id,
154+
audit_emp_bankAccountNumber,
155+
audit_emp_salary,
156+
audit_emp_SSN)
157+
SELECT 'NEW',
158+
ins.emp_id,
159+
ins.emp_bankAccountNumber,
160+
ins.emp_salary,
161+
ins.emp_SSN
162+
FROM inserted AS ins;
163+
END
164+
GO
165+
166+
/* Inserting a new employee does not cause the UPDATE trigger to fire. */
167+
INSERT INTO employeeData
168+
VALUES (101, 'USA-987-01', 23000, 'R-M53550M', N'Mendel', N'Roland', 32);
169+
GO
170+
155171
/* Updating the employee record for employee number 101 to change the
156172
salary to 51000 causes the UPDATE trigger to fire and an audit trail to
157-
be produced. */
158-
159-
UPDATE dbo.employeeData
160-
SET emp_salary = 51000
161-
WHERE emp_id = 101;
162-
GO
163-
SELECT * FROM auditEmployeeData;
164-
GO
165-
166-
/* Updating the employee record for employee number 101 to change both
167-
the bank account number and social security number (SSN) causes the
168-
UPDATE trigger to fire and an audit trail to be produced. */
169-
170-
UPDATE dbo.employeeData
171-
SET emp_bankAccountNumber = '133146A0', emp_SSN = 'R-M53550M'
172-
WHERE emp_id = 101;
173-
GO
174-
SELECT * FROM dbo.auditEmployeeData;
175-
176-
GO
177-
```
178-
179-
### B. Using COLUMNS_UPDATED to test more than eight columns
173+
be produced. */
174+
UPDATE dbo.employeeData
175+
SET emp_salary = 51000
176+
WHERE emp_id = 101;
177+
GO
178+
179+
SELECT * FROM auditEmployeeData;
180+
GO
181+
182+
/* Updating the employee record for employee number 101 to change both
183+
the bank account number and social security number (SSN) causes the
184+
UPDATE trigger to fire and an audit trail to be produced. */
185+
UPDATE dbo.employeeData
186+
SET emp_bankAccountNumber = '133146A0',
187+
emp_SSN = 'R-M53550M'
188+
WHERE emp_id = 101;
189+
GO
190+
191+
SELECT * FROM dbo.auditEmployeeData;
192+
GO
193+
```
194+
195+
### B. Use COLUMNS_UPDATED to test more than eight columns
196+
180197
To test for updates that affect columns other than the first eight table columns, use the `SUBSTRING` function to test the correct bit returned by `COLUMNS_UPDATED`. This example tests for updates affecting columns `3`, `5`, and `9` in the `AdventureWorks2022.Person.Person` table.
181-
198+
182199
```sql
183-
USE AdventureWorks2022;
184-
GO
185-
IF OBJECT_ID (N'Person.uContact2', N'TR') IS NOT NULL
186-
DROP TRIGGER Person.uContact2;
187-
GO
188-
CREATE TRIGGER Person.uContact2 ON Person.Person
189-
AFTER UPDATE AS
190-
IF ( (SUBSTRING(COLUMNS_UPDATED(), 1, 1) & 20 = 20)
191-
AND (SUBSTRING(COLUMNS_UPDATED(), 2, 1) & 1 = 1) )
192-
PRINT 'Columns 3, 5 and 9 updated';
193-
GO
194-
195-
UPDATE Person.Person
196-
SET NameStyle = NameStyle,
197-
FirstName=FirstName,
198-
EmailPromotion=EmailPromotion;
199-
GO
200-
```
201-
202-
## See also
203-
[Bitwise Operators (Transact-SQL)](../../t-sql/language-elements/bitwise-operators-transact-sql.md)
204-
[CREATE TRIGGER (Transact-SQL)](../../t-sql/statements/create-trigger-transact-sql.md)
205-
[UPDATE() (Transact-SQL)](../../t-sql/functions/update-trigger-functions-transact-sql.md)
206-
207-
200+
USE AdventureWorks2022;
201+
GO
202+
203+
IF OBJECT_ID(N'Person.uContact2', N'TR') IS NOT NULL
204+
DROP TRIGGER Person.uContact2;
205+
GO
206+
207+
CREATE TRIGGER Person.uContact2
208+
ON Person.Person
209+
AFTER UPDATE AS
210+
IF ((SUBSTRING(COLUMNS_UPDATED(), 1, 1) & 20 = 20)
211+
AND (SUBSTRING(COLUMNS_UPDATED(), 2, 1) & 1 = 1))
212+
PRINT 'Columns 3, 5 and 9 updated';
213+
GO
214+
215+
UPDATE Person.Person
216+
SET NameStyle = NameStyle,
217+
FirstName = FirstName,
218+
EmailPromotion = EmailPromotion;
219+
GO
220+
```
221+
222+
## Related content
223+
224+
- [Bitwise operators (Transact-SQL)](../language-elements/bitwise-operators-transact-sql.md)
225+
- [CREATE TRIGGER (Transact-SQL)](../statements/create-trigger-transact-sql.md)
226+
- [UPDATE - Trigger Functions (Transact-SQL)](update-trigger-functions-transact-sql.md)

0 commit comments

Comments
 (0)