Examples for SQL Server 2025. Each block is a separate example. Replace the table and column names as needed; Id is assumed to be unique.
- Basics
- Values and expressions
- Large strings and binary data
- Values from another source
- Conditions and changed values
- CTEs and batches
- OUTPUT and variables
- SQL Server 2025
- JSON and XML
- Other update targets
- Transactions and concurrency
- Other forms
Basics
Update one row by its Id:
UPDATE [dbo].[SampleTable] SET [Col1] = N'New Value' WHERE [Id] = 1;
Update multiple columns in one statement:
UPDATE [dbo].[SampleTable] SET [Col1] = N'New Value', [Col2] = NULL WHERE [Id] = 1;
Update all rows:
UPDATE [dbo].[SampleTable] SET [Status] = N'Archived';
Update using a variable:
DECLARE @Id INT = 1; DECLARE @NewValue NVARCHAR(100) = N'New Value'; UPDATE [dbo].[SampleTable] SET [Col1] = @NewValue WHERE [Id] = @Id;
Set a column to NULL or its default value:
-- NULL UPDATE [dbo].[SampleTable] SET [Col1] = NULL WHERE [Id] = 1; -- column default UPDATE [dbo].[SampleTable] SET [Status] = DEFAULT WHERE [Id] = 1;
Values and expressions
Set a value using CASE:
UPDATE [dbo].[SampleTable] SET [Status] = CASE WHEN [Quantity] <= 0 THEN N'Empty' WHEN [Quantity] < 10 THEN N'Low' ELSE N'Available' END;
Increase or decrease a numeric value:
-- add UPDATE [dbo].[SampleTable] SET [Value] += 10 WHERE [Id] = 1; -- subtract UPDATE [dbo].[SampleTable] SET [Value] -= 5 WHERE [Id] = 1; -- multiply UPDATE [dbo].[SampleTable] SET [Value] *= 1.10 WHERE [Id] = 1; -- divide UPDATE [dbo].[SampleTable] SET [Value] /= 2 WHERE [Id] = 1; -- remainder (integer column) UPDATE [dbo].[SampleTable] SET [Quantity] %= 10 WHERE [Id] = 1;
Set, clear or toggle a bit flag:
-- set flag 4 UPDATE [dbo].[SampleTable] SET [Flags] |= 4 WHERE [Id] = 1; -- clear flag 4 UPDATE [dbo].[SampleTable] SET [Flags] &= ~4 WHERE [Id] = 1; -- toggle flag 4 UPDATE [dbo].[SampleTable] SET [Flags] ^= 4 WHERE [Id] = 1;
Replace NULL values with a fallback:
UPDATE [dbo].[SampleTable] SET [Value] = COALESCE([Value], 0) WHERE [Value] IS NULL;
Replace text or append a suffix:
-- replace text UPDATE [dbo].[SampleTable] SET [TextValue] = REPLACE([TextValue], N'Old', N'New') WHERE [Id] = 1; -- append text, treating NULL as empty text UPDATE [dbo].[SampleTable] SET [TextValue] = CONCAT([TextValue], N' suffix') WHERE [Id] = 1;
Add one day or store the current UTC time:
-- add one day UPDATE [dbo].[SampleTable] SET [DueDate] = DATEADD(DAY, 1, [DueDate]) WHERE [Id] = 1; -- UTC timestamp UPDATE [dbo].[SampleTable] SET [ModifiedAt] = SYSUTCDATETIME() WHERE [Id] = 1;
Swap values between two columns:
UPDATE [dbo].[SampleTable] SET [Col1] = [Col2], [Col2] = [Col1] WHERE [Id] = 1;
Convert text to a number:
UPDATE [dbo].[SampleTable] SET [Quantity] = TRY_CONVERT(INT, [TextValue]) WHERE [Id] = 1;
Invalid numeric text returns NULL. Quantity must allow NULL.
Replace part of a normal string using STUFF:
UPDATE [dbo].[SampleTable] SET [Col1] = STUFF([Col1], 1, 3, N'New') WHERE [Id] = 1;
STUFF starts at 1. This replaces the first three characters and also works with non-MAX string columns.
Update with the expression:
UPDATE [dbo].[SampleTable] SET [Col1] = @a + @b + @c
Large strings and binary data
.WRITE() works with VARCHAR(MAX), NVARCHAR(MAX) and VARBINARY(MAX). The value must not be NULL. Offsets start at 0: bytes for VARCHAR/VARBINARY and UTF-16 units for NVARCHAR. TextValue in these examples is NVARCHAR(MAX).
Update using the .WRITE() clause:
UPDATE [dbo].[SampleTable] SET [Col1].WRITE('New Value', 0, NULL)
UPDATE [dbo].[SampleTable] SET [Col1].WRITE(@a + @b + @c, 0, NULL)
UPDATE [dbo].[SampleTable] SET [Col1].WRITE(@x, 500, 1000)
Append text using .WRITE():
UPDATE [dbo].[SampleTable] SET [TextValue].WRITE(N' appended text', NULL, 0) WHERE [Id] = 1;
Insert text at the beginning using .WRITE():
UPDATE [dbo].[SampleTable] SET [TextValue].WRITE(N'Prefix: ', 0, 0) WHERE [Id] = 1;
Replace or remove part of a value using .WRITE():
-- replace the first 3 UTF-16 units UPDATE [dbo].[SampleTable] SET [TextValue].WRITE(N'New', 0, 3) WHERE [Id] = 1; -- remove the first 3 UTF-16 units UPDATE [dbo].[SampleTable] SET [TextValue].WRITE(N'', 0, 3) WHERE [Id] = 1;
Truncate a value or replace everything after an offset:
-- keep the first 5 UTF-16 units UPDATE [dbo].[SampleTable] SET [TextValue].WRITE(NULL, 5, 0) WHERE [Id] = 1; -- replace everything after the first 5 UTF-16 units UPDATE [dbo].[SampleTable] SET [TextValue].WRITE(N'New ending', 5, NULL) WHERE [Id] = 1;
The offset must not exceed the current value length.
Initialize a NULL value before using .WRITE():
UPDATE [dbo].[SampleTable] SET [TextValue] = N'' WHERE [Id] = 1 AND [TextValue] IS NULL; UPDATE [dbo].[SampleTable] SET [TextValue].WRITE(N'New Value', NULL, 0) WHERE [Id] = 1;
Replace or append binary data:
-- BinaryData is VARBINARY(MAX) UPDATE [dbo].[SampleTable] SET [BinaryData] = 0x01020304 WHERE [Id] = 1; UPDATE [dbo].[SampleTable] SET [BinaryData].WRITE(0x0506, NULL, 0) WHERE [Id] = 1;
Read a file into a binary column:
UPDATE [dbo].[SampleTable] SET [BinaryData] = ( SELECT [BulkColumn] FROM OPENROWSET( BULK N'C:\Data\Sample.bin', SINGLE_BLOB ) AS [f] ) WHERE [Id] = 1;
The file path is on the SQL Server host. Requires bulk-read permission and file access.
Replace or clear FILESTREAM data:
-- FileData is a VARBINARY(MAX) FILESTREAM column UPDATE [dbo].[SampleTable] SET [FileData] = 0x01020304 WHERE [Id] = 1; UPDATE [dbo].[SampleTable] SET [FileData] = NULL WHERE [Id] = 1;
FILESTREAM does not support partial updates with .WRITE(). FileData must allow NULL for the second example.
Values from another source
Update using values from another table:
UPDATE [t] SET [Col1] = [s].[Col1] FROM [dbo].[SampleTable] AS [t] INNER JOIN [dbo].[SourceTable] AS [s] ON [s].[Id] = [t].[Id];
The source must provide at most one matching row for each target row; otherwise the chosen source value is undefined.
Keep the current value when a LEFT JOIN has no match:
UPDATE [t] SET [Col1] = CASE WHEN [s].[Id] IS NULL THEN [t].[Col1] ELSE [s].[Col1] END FROM [dbo].[SampleTable] AS [t] LEFT JOIN [dbo].[SourceTable] AS [s] ON [s].[Id] = [t].[Id];
Source Id must be unique. A matched source row containing NULL still sets Col1 to NULL.
Update from another row in the same table:
UPDATE [child] SET [ParentName] = [parent].[Name] FROM [dbo].[SampleTable] AS [child] INNER JOIN [dbo].[SampleTable] AS [parent] ON [parent].[Id] = [child].[ParentId];
Update using a correlated subquery:
UPDATE [t] SET [Col1] = ( SELECT [s].[Col1] FROM [dbo].[SourceTable] AS [s] WHERE [s].[Id] = [t].[Id] ) FROM [dbo].[SampleTable] AS [t];
No matching row returns NULL. More than one matching row raises an error.
Update using aggregated source values:
UPDATE [t] SET [TotalAmount] = [s].[TotalAmount] FROM [dbo].[SampleTable] AS [t] INNER JOIN ( SELECT [SampleId], SUM([Amount]) AS [TotalAmount] FROM [dbo].[SourceTable] GROUP BY [SampleId] ) AS [s] ON [s].[SampleId] = [t].[Id];
Update multiple rows with different values using VALUES:
UPDATE [t] SET [Col1] = [s].[Col1] FROM [dbo].[SampleTable] AS [t] INNER JOIN (VALUES (1, N'First value'), (2, N'Second value'), (3, N'Third value') ) AS [s] ([Id], [Col1]) ON [s].[Id] = [t].[Id];
Use each Id once in the VALUES list.
Update using the latest matching source row:
UPDATE [t] SET [Col1] = [s].[Col1] FROM [dbo].[SampleTable] AS [t] CROSS APPLY ( SELECT TOP (1) [src].[Col1] FROM [dbo].[SourceTable] AS [src] WHERE [src].[SampleId] = [t].[Id] ORDER BY [src].[ModifiedAt] DESC, [src].[Id] DESC ) AS [s];
Source Id must be unique to break timestamp ties. CROSS APPLY skips targets with no match.
Update using a JSON array of changes:
DECLARE @Changes NVARCHAR(MAX) =
N'[{"Id":1,"Col1":"First value"},{"Id":2,"Col1":"Second value"}]';
UPDATE [t]
SET [Col1] = [s].[Col1]
FROM [dbo].[SampleTable] AS [t]
INNER JOIN OPENJSON(@Changes)
WITH
(
[Id] INT '$.Id',
[Col1] NVARCHAR(100) '$.Col1'
) AS [s]
ON [s].[Id] = [t].[Id];
Use one JSON item per Id. OPENJSON requires compatibility level 130 or higher.
Update from a table-valued parameter:
-- create once CREATE TYPE [dbo].[utt_UpdateValues] AS TABLE ( [Id] INT NOT NULL PRIMARY KEY, [Col1] NVARCHAR(100) NULL ); GO CREATE OR ALTER PROCEDURE [dbo].[p_UpdateValues] @Changes [dbo].[utt_UpdateValues] READONLY AS BEGIN SET NOCOUNT ON; UPDATE [t] SET [Col1] = [s].[Col1] FROM [dbo].[SampleTable] AS [t] INNER JOIN @Changes AS [s] ON [s].[Id] = [t].[Id]; END; GO DECLARE @Changes [dbo].[utt_UpdateValues]; INSERT INTO @Changes ([Id], [Col1]) VALUES (1, N'First value'), (2, N'Second value'); EXEC [dbo].[p_UpdateValues] @Changes = @Changes;
Conditions and changed values
Update only changed values, including NULL (SQL Server 2022+):
DECLARE @NewValue NVARCHAR(100) = N'New Value'; UPDATE [dbo].[SampleTable] SET [Col1] = @NewValue WHERE [Id] = 1 AND [Col1] IS DISTINCT FROM @NewValue;
IS DISTINCT FROM also detects changes to or from NULL.
Update a list of Ids:
UPDATE [dbo].[SampleTable] SET [Status] = N'Archived' WHERE [Id] IN (1, 2, 3);
Update rows from a date range:
DECLARE @FromDate DATE = '20250101'; DECLARE @ToDate DATE = '20250201'; UPDATE [dbo].[SampleTable] SET [Status] = N'Archived' WHERE [CreatedAt] >= @FromDate AND [CreatedAt] < @ToDate;
Includes January 2025. The upper boundary is excluded.
Update rows containing NULL or matching a text pattern:
-- NULL values UPDATE [dbo].[SampleTable] SET [Col1] = N'Unknown' WHERE [Col1] IS NULL; -- text pattern UPDATE [dbo].[SampleTable] SET [Status] = N'Archived' WHERE [Name] LIKE N'Old%';
Update rows with or without a matching source row:
-- matching source row UPDATE [t] SET [Status] = N'Matched' FROM [dbo].[SampleTable] AS [t] WHERE EXISTS ( SELECT 1 FROM [dbo].[SourceTable] AS [s] WHERE [s].[Id] = [t].[Id] ); -- no matching source row UPDATE [t] SET [Status] = N'Missing' FROM [dbo].[SampleTable] AS [t] WHERE NOT EXISTS ( SELECT 1 FROM [dbo].[SourceTable] AS [s] WHERE [s].[Id] = [t].[Id] );
Update only when at least one column changed (SQL Server 2022+):
UPDATE [t] SET [Col1] = [s].[Col1], [Col2] = [s].[Col2] FROM [dbo].[SampleTable] AS [t] INNER JOIN [dbo].[SourceTable] AS [s] ON [s].[Id] = [t].[Id] WHERE [t].[Col1] IS DISTINCT FROM [s].[Col1] OR [t].[Col2] IS DISTINCT FROM [s].[Col2];
Source Id must be unique. NULL values are compared too.
CTEs and batches
Update the next 100 pending rows selected by Id:
;WITH [Batch] AS ( SELECT TOP (100) [Id], [Status] FROM [dbo].[SampleTable] WHERE [Status] = N'Pending' ORDER BY [Id] ) UPDATE [Batch] SET [Status] = N'Processed';
Assumes Id is unique. ORDER BY selects the batch; it does not guarantee modification order. UPDATE TOP (100) alone does not define which qualifying rows are selected.
Update a filtered CTE:
;WITH [RowsToUpdate] AS ( SELECT [Id], [Status] FROM [dbo].[SampleTable] WHERE [Status] = N'Pending' ) UPDATE [RowsToUpdate] SET [Status] = N'Ready';
Store row numbers in a column:
;WITH [NumberedRows] AS ( SELECT [Id], ROW_NUMBER() OVER (ORDER BY [Id]) AS [SequenceNo] FROM [dbo].[SampleTable] ) UPDATE [t] SET [SequenceNo] = [s].[SequenceNo] FROM [dbo].[SampleTable] AS [t] INNER JOIN [NumberedRows] AS [s] ON [s].[Id] = [t].[Id];
Id must be unique. ROW_NUMBER returns BIGINT.
Mark the latest row in each group:
;WITH [RankedRows] AS ( SELECT [Id], ROW_NUMBER() OVER ( PARTITION BY [GroupId] ORDER BY [CreatedAt] DESC, [Id] DESC ) AS [RowNo] FROM [dbo].[SampleTable] ) UPDATE [t] SET [IsLatest] = CASE WHEN [s].[RowNo] = 1 THEN 1 ELSE 0 END FROM [dbo].[SampleTable] AS [t] INNER JOIN [RankedRows] AS [s] ON [s].[Id] = [t].[Id];
Id must be unique to break timestamp ties.
Update a parent row and all its descendants:
DECLARE @RootId INT = 1; ;WITH [Descendants] AS ( SELECT [Id] FROM [dbo].[SampleTable] WHERE [Id] = @RootId UNION ALL SELECT [child].[Id] FROM [dbo].[SampleTable] AS [child] INNER JOIN [Descendants] AS [parent] ON [child].[ParentId] = [parent].[Id] ) UPDATE [t] SET [Status] = N'Archived' FROM [dbo].[SampleTable] AS [t] INNER JOIN [Descendants] AS [d] ON [d].[Id] = [t].[Id] OPTION (MAXRECURSION 100);
Assumes a tree without cycles. Increase MAXRECURSION if deeper branches are expected.
Limit the number or percentage of updated rows:
-- fixed count UPDATE TOP (100) [dbo].[SampleTable] SET [Status] = N'Processed' WHERE [Status] = N'Pending'; -- variable count DECLARE @BatchSize INT = 100; UPDATE TOP (@BatchSize) [dbo].[SampleTable] SET [Status] = N'Processed' WHERE [Status] = N'Pending'; -- percentage UPDATE TOP (10) PERCENT [dbo].[SampleTable] SET [Status] = N'Processed' WHERE [Status] = N'Pending';
These forms do not define row order.
Update in batches until no pending rows remain:
DECLARE @BatchSize INT = 1000; DECLARE @RowsUpdated INT = 1; WHILE @RowsUpdated > 0 BEGIN ;WITH [Batch] AS ( SELECT TOP (@BatchSize) [Id], [Status] FROM [dbo].[SampleTable] WHERE [Status] = N'Pending' ORDER BY [Id] ) UPDATE [Batch] SET [Status] = N'Processed'; SET @RowsUpdated = @@ROWCOUNT; END;
Id must be unique. Run without an outer transaction for separate batch commits. Processed rows leave the filter.
OUTPUT and variables
Capture old and new values using OUTPUT:
DECLARE @Changes TABLE ( [Id] INT, [OldValue] NVARCHAR(MAX), [NewValue] NVARCHAR(MAX) ); UPDATE [dbo].[SampleTable] SET [Col1] = N'New Value' OUTPUT [Inserted].[Id], [Deleted].[Col1], [Inserted].[Col1] INTO @Changes ([Id], [OldValue], [NewValue]) WHERE [Id] = 1; SELECT [Id], [OldValue], [NewValue] FROM @Changes;
OUTPUT captures values before subsequent AFTER trigger changes. Returned values alone do not confirm that the transaction committed.
Return old and new values directly:
UPDATE [dbo].[SampleTable] SET [Col1] = N'New Value' OUTPUT [Inserted].[Id], [Deleted].[Col1] AS [OldValue], [Inserted].[Col1] AS [NewValue] WHERE [Id] = 1;
Use OUTPUT INTO instead if the target has an enabled UPDATE trigger.
Return a value from the joined source:
UPDATE [t] SET [Col1] = [s].[Col1] OUTPUT [Inserted].[Id], [Deleted].[Col1] AS [OldValue], [Inserted].[Col1] AS [NewValue], [s].[SourceCode] FROM [dbo].[SampleTable] AS [t] INNER JOIN [dbo].[SourceTable] AS [s] ON [s].[Id] = [t].[Id];
Source Id must be unique. Direct OUTPUT requires a target without enabled UPDATE triggers.
Save changed rows into a temporary table:
CREATE TABLE #Changes ( [Id] INT, [OldValue] NVARCHAR(MAX), [NewValue] NVARCHAR(MAX) ); UPDATE [dbo].[SampleTable] SET [Col1] = N'New Value' OUTPUT [Inserted].[Id], [Deleted].[Col1], [Inserted].[Col1] INTO #Changes ([Id], [OldValue], [NewValue]) WHERE [Id] IN (1, 2, 3); SELECT [Id], [OldValue], [NewValue] FROM #Changes; DROP TABLE #Changes;
A permanent capture table can be used too. The OUTPUT INTO table cannot have enabled triggers, CHECK constraints, or foreign-key participation.
Get the number of updated rows:
DECLARE @RowsUpdated INT; UPDATE [dbo].[SampleTable] SET [Status] = N'Archived' WHERE [Id] IN (1, 2, 3); SET @RowsUpdated = @@ROWCOUNT; SELECT @RowsUpdated AS [RowsUpdated];
Read @@ROWCOUNT immediately after UPDATE. It counts affected rows, including unchanged values.
Capture a single row’s new or previous value into a variable:
-- new value DECLARE @NewValue NVARCHAR(100); UPDATE [dbo].[SampleTable] SET @NewValue = [Col1] = N'New Value' WHERE [Id] = 1; SELECT @NewValue AS [NewValue]; -- previous value DECLARE @OldValue NVARCHAR(100); UPDATE [dbo].[SampleTable] SET @OldValue = [Col1], [Col1] = N'Another Value' WHERE [Id] = 1; SELECT @OldValue AS [OldValue]; -- new value after a compound assignment DECLARE @NewQuantity INT; UPDATE [dbo].[SampleTable] SET @NewQuantity = [Quantity] += 1 WHERE [Id] = 1; SELECT @NewQuantity AS [NewQuantity];
Id must identify one row. Use OUTPUT for multiple rows.
Assign a variable from the updated row:
DECLARE @Id INT = 1; DECLARE @PreviousQuantity INT; UPDATE [dbo].[SampleTable] SET @PreviousQuantity = [Quantity], [Status] = N'Processed' WHERE [Id] = @Id; SELECT @PreviousQuantity AS [PreviousQuantity];
Id must identify one row. If no row matches, the variable keeps its previous value.
SQL Server 2025
Collapse repeated spaces using a regular expression (SQL Server 2025):
UPDATE [dbo].[SampleTable]
SET [TextValue] =
REGEXP_REPLACE([TextValue], N' {2,}', N' ')
WHERE [Id] = 1;
Replaces runs of two or more ordinary spaces with one space.
REGEXP_REPLACE reference on Microsoft Learn.
Remove non-digit characters (SQL Server 2025):
UPDATE [dbo].[SampleTable] SET [Phone] = REGEXP_REPLACE([Phone], N'[^0-9]', N'') WHERE [Id] = 1;
Replace a native JSON document or modify one property (SQL Server 2025):
-- JsonData has the native JSON data type
UPDATE [dbo].[SampleTable]
SET [JsonData] = CAST(N'{"status":"Pending","count":1}' AS JSON)
WHERE [Id] = 1;
UPDATE [dbo].[SampleTable]
SET [JsonData].modify('$.status', N'Processed')
WHERE [Id] = 1;
Native JSON columns use the .modify() method. JSON data type reference.
Replace a vector value (SQL Server 2025):
-- Embedding is VECTOR(3) DECLARE @Embedding VECTOR(3) = '[0.15, 0.25, 0.35]'; UPDATE [dbo].[SampleTable] SET [Embedding] = @Embedding WHERE [Id] = 1;
Match the column dimension. Vector-index update support depends on the index version. Current vector-index limitations.
JSON and XML
Change or add a property in JSON text:
-- JsonText is NVARCHAR(MAX) containing a JSON object UPDATE [dbo].[SampleTable] SET [JsonText] = JSON_MODIFY([JsonText], '$.status', N'Processed') WHERE [Id] = 1;
Adds status when missing from the root object. JsonText must contain valid JSON.
Delete a JSON property:
UPDATE [dbo].[SampleTable] SET [JsonText] = JSON_MODIFY([JsonText], '$.status', NULL) WHERE [Id] = 1;
In the default lax mode, SQL NULL removes the property.
Set an existing JSON property to JSON null:
UPDATE [dbo].[SampleTable] SET [JsonText] = JSON_MODIFY([JsonText], 'strict $.status', NULL) WHERE [Id] = 1;
Strict mode keeps the property with a JSON null value. The property must exist.
Append a value to a JSON array:
UPDATE [dbo].[SampleTable] SET [JsonText] = JSON_MODIFY([JsonText], 'append $.tags', N'SQL Server') WHERE [Id] = 1;
Assumes tags is an existing array.
Store an object instead of an escaped JSON string:
UPDATE [dbo].[SampleTable]
SET [JsonText] = JSON_MODIFY(
[JsonText],
'$.address',
JSON_QUERY(N'{"city":"Prague","country":"CZ"}')
)
WHERE [Id] = 1;
Change multiple JSON properties:
UPDATE [dbo].[SampleTable] SET [JsonText] = JSON_MODIFY( JSON_MODIFY([JsonText], '$.status', N'Processed'), '$.count', 2 ) WHERE [Id] = 1;
The number stays numeric. JSON_MODIFY reference.
Replace XML text or an attribute value:
-- XmlData is XML, for example:
-- <Root Status="Pending"><Value>Old</Value></Root>
UPDATE [dbo].[SampleTable]
SET [XmlData].modify('
replace value of (/Root/Value/text())[1]
with "New"
')
WHERE [Id] = 1
AND [XmlData] IS NOT NULL;
UPDATE [dbo].[SampleTable]
SET [XmlData].modify('
replace value of (/Root/@Status)[1]
with "Processed"
')
WHERE [Id] = 1
AND [XmlData] IS NOT NULL;
The [1] selects a single node.
Insert or delete an XML element:
-- insert under the existing Root element
UPDATE [dbo].[SampleTable]
SET [XmlData].modify('
insert <Note>New note</Note>
as last into (/Root)[1]
')
WHERE [Id] = 1
AND [XmlData] IS NOT NULL;
-- delete Note elements
UPDATE [dbo].[SampleTable]
SET [XmlData].modify('delete /Root/Note')
WHERE [Id] = 1
AND [XmlData] IS NOT NULL;
Examples use untyped XML without namespaces. XML modify() reference.
Other update targets
Update a temporary table or a table variable:
-- temporary table CREATE TABLE #SampleTable ( [Id] INT NOT NULL PRIMARY KEY, [Col1] NVARCHAR(100) NULL ); INSERT INTO #SampleTable ([Id], [Col1]) VALUES (1, N'Old Value'); UPDATE #SampleTable SET [Col1] = N'New Value' WHERE [Id] = 1; SELECT * FROM #SampleTable; DROP TABLE #SampleTable; -- table variable DECLARE @SampleTable TABLE ( [Id] INT NOT NULL PRIMARY KEY, [Col1] NVARCHAR(100) NULL ); INSERT INTO @SampleTable ([Id], [Col1]) VALUES (1, N'Old Value'); UPDATE @SampleTable SET [Col1] = N'New Value' WHERE [Id] = 1; SELECT * FROM @SampleTable;
Update through a view:
CREATE OR ALTER VIEW [dbo].[v_SampleTable] AS SELECT [Id], [Col1] FROM [dbo].[SampleTable]; GO UPDATE [dbo].[v_SampleTable] SET [Col1] = N'New Value' WHERE [Id] = 1;
The change is stored in SampleTable. The updated columns must belong to one base table and meet the view-update rules.
Update through a synonym:
-- create once CREATE SYNONYM [dbo].[SampleAlias] FOR [dbo].[SampleTable]; GO UPDATE [dbo].[SampleAlias] SET [Col1] = N'New Value' WHERE [Id] = 1;
Update a table in another database or on a linked server:
-- another database on the same instance UPDATE [SampleDb].[dbo].[SampleTable] SET [Col1] = N'New Value' WHERE [Id] = 1; -- configured linked server UPDATE [RemoteServer].[SampleDb].[dbo].[SampleTable] SET [Col1] = N'New Value' WHERE [Id] = 1;
Remote updates require a writable provider and the appropriate login mapping. OUTPUT is not supported for remote targets.
Update through OPENQUERY:
UPDATE OPENQUERY( [RemoteServer], 'SELECT [Id], [Col1] FROM [SampleDb].[dbo].[SampleTable] WHERE [Id] = 1' ) SET [Col1] = N'New Value';
Uses a configured linked server. The provider must return an updatable rowset.
Update through OPENROWSET:
UPDATE OPENROWSET( 'MSOLEDBSQL19', 'Data Source=RemoteServer;Initial Catalog=SampleDb;Integrated Security=SSPI;Use Encryption for Data=Mandatory;', 'SELECT [Id], [Col1] FROM [dbo].[SampleTable] WHERE [Id] = 1' ) SET [Col1] = N'New Value';
Requires OLE DB Driver 19, allowed ad hoc access, a trusted server certificate and remote access for the Windows login. OPENROWSET reference.
Update through OPENDATASOURCE:
UPDATE OPENDATASOURCE( 'MSOLEDBSQL19', 'Data Source=RemoteServer;Integrated Security=SSPI;Use Encryption for Data=Mandatory;' ).[SampleDb].[dbo].[SampleTable] SET [Col1] = N'New Value' WHERE [Id] = 1;
Uses the same driver, ad hoc access and authentication requirements as the OPENROWSET example. OPENDATASOURCE reference.
Update a system-versioned temporal table:
-- SampleTemporalTable already has SYSTEM_VERSIONING = ON UPDATE [dbo].[SampleTemporalTable] SET [Col1] = N'New Value' WHERE [Id] = 1;
Update the current table. SQL Server stores the previous version in the history table. Period columns and the history table cannot be updated directly while system versioning is on. Temporal update reference.
Restore a current row from temporal history:
DECLARE @Id INT = 1; DECLARE @AsOf DATETIME2 = '2025-01-15T12:00:00'; UPDATE [t] SET [Col1] = [h].[Col1] FROM [dbo].[SampleTemporalTable] AS [t] INNER JOIN [dbo].[SampleTemporalTable] FOR SYSTEM_TIME AS OF @AsOf AS [h] ON [h].[Id] = [t].[Id] WHERE [t].[Id] = @Id;
Updates the current row and creates another history version. It does not insert a row that has already been deleted.
Transactions and concurrency
Inspect an update and roll it back:
BEGIN TRANSACTION; UPDATE [dbo].[SampleTable] SET [Col1] = N'New Value' WHERE [Id] = 1; SELECT [Id], [Col1] FROM [dbo].[SampleTable] WHERE [Id] = 1; ROLLBACK TRANSACTION; -- use COMMIT TRANSACTION instead to save the change
Run as a separate transaction.
Update two tables in one transaction:
SET XACT_ABORT ON; BEGIN TRY BEGIN TRANSACTION; UPDATE [dbo].[SampleTable] SET [Status] = N'Processed' WHERE [Id] = 1; UPDATE [dbo].[SourceTable] SET [Status] = N'Processed' WHERE [Id] = 1; COMMIT TRANSACTION; END TRY BEGIN CATCH IF XACT_STATE() <> 0 ROLLBACK TRANSACTION; THROW; END CATCH;
Assumes no transaction is already open. An error rolls back both changes.
Decrease stock only when enough stock is available:
DECLARE @Id INT = 1; DECLARE @Requested INT = 3; IF @Requested <= 0 THROW 50000, 'Requested quantity must be positive.', 1; UPDATE [dbo].[SampleTable] SET [Quantity] -= @Requested WHERE [Id] = @Id AND [Quantity] >= @Requested; IF @@ROWCOUNT = 0 THROW 50001, 'Row not found or insufficient stock.', 1;
Id must be unique. The check and decrease happen in one statement.
Update only if the rowversion still matches:
DECLARE @Id INT = 1; -- rowversion returned by the previous read DECLARE @ExpectedVersion BINARY(8) = 0x0000000000000123; UPDATE [dbo].[SampleTable] SET [Col1] = N'New Value' WHERE [Id] = @Id AND [RowVersion] = @ExpectedVersion; IF @@ROWCOUNT = 0 THROW 50002, 'Row was changed or deleted.', 1;
RowVersion must be a ROWVERSION column. SQL Server generates its value; it is not a date or time. ROWVERSION reference.
Update an existing row or insert a missing row:
DECLARE @Id INT = 1; DECLARE @NewValue NVARCHAR(100) = N'New Value'; SET XACT_ABORT ON; BEGIN TRY BEGIN TRANSACTION; UPDATE [dbo].[SampleTable] WITH (UPDLOCK, SERIALIZABLE) SET [Col1] = @NewValue WHERE [Id] = @Id; IF @@ROWCOUNT = 0 BEGIN INSERT INTO [dbo].[SampleTable] ([Id], [Col1]) VALUES (@Id, @NewValue); END; COMMIT TRANSACTION; END TRY BEGIN CATCH IF XACT_STATE() <> 0 ROLLBACK TRANSACTION; THROW; END CATCH;
Id needs a unique key and must allow the explicit inserted value. Assumes no transaction is already open. The lock protects the missing-key check until commit.
Claim the next pending row while skipping locked rows:
-- READ COMMITTED isolation, READ_COMMITTED_SNAPSHOT OFF DECLARE @Claimed TABLE ( [Id] INT, [Col1] NVARCHAR(100) ); SET XACT_ABORT ON; BEGIN TRY BEGIN TRANSACTION; ;WITH [NextRow] AS ( SELECT TOP (1) [Id], [Col1], [Status] FROM [dbo].[SampleTable] WITH (UPDLOCK, READPAST, ROWLOCK) WHERE [Status] = N'Pending' ORDER BY [Id] ) UPDATE [NextRow] SET [Status] = N'Processing' OUTPUT [Inserted].[Id], [Inserted].[Col1] INTO @Claimed ([Id], [Col1]); COMMIT TRANSACTION; SELECT [Id], [Col1] FROM @Claimed; END TRY BEGIN CATCH IF XACT_STATE() <> 0 ROLLBACK TRANSACTION; THROW; END CATCH;
Use a rowstore table with unique Id and no outer transaction. READPAST skips row locks, not page locks. RCSI needs a different hint combination. Table-hint reference.
Other forms
Update the current cursor row:
DECLARE @Id INT; DECLARE @Col1 NVARCHAR(100); DECLARE [UpdateCursor] CURSOR LOCAL KEYSET FOR SELECT [Id], [Col1] FROM [dbo].[SampleTable] WHERE [Status] = N'Pending' FOR UPDATE OF [Col1]; OPEN [UpdateCursor]; FETCH NEXT FROM [UpdateCursor] INTO @Id, @Col1; WHILE @@FETCH_STATUS = 0 BEGIN UPDATE [dbo].[SampleTable] SET [Col1] = N'New Value' WHERE CURRENT OF [UpdateCursor]; FETCH NEXT FROM [UpdateCursor] INTO @Id, @Col1; END; CLOSE [UpdateCursor]; DEALLOCATE [UpdateCursor];
The table needs a unique index for this KEYSET cursor. FAST_FORWARD cursors are read-only.
Update a CLR user-defined type property or call its mutator method:
-- Location is a custom CLR type with a writable X property UPDATE [dbo].[SampleTable] SET [Location].[X] = 10 WHERE [Id] = 1; -- the CLR type exposes a SetXY mutator method UPDATE [dbo].[SampleTable] SET [Location].SetXY(10, 20) WHERE [Id] = 1;
Requires an installed CLR type defining these members and a non-NULL Location. SetXY must be marked as a mutator. CLR type update reference.
Use a table hint or query hint:
-- request row locks UPDATE [dbo].[SampleTable] WITH (ROWLOCK) SET [Col1] = N'New Value' WHERE [Id] = 1; -- recompile this statement and limit parallelism UPDATE [dbo].[SampleTable] SET [Status] = N'Archived' WHERE [GroupId] = 1 OPTION (RECOMPILE, MAXDOP 2);
ROWLOCK does not prevent lock escalation and cannot be used on a table with a clustered columnstore index.
Update a table selected by name using dynamic SQL:
DECLARE @SchemaName SYSNAME = N'dbo'; DECLARE @TableName SYSNAME = N'SampleTable'; DECLARE @Id INT = 1; DECLARE @NewValue NVARCHAR(100) = N'New Value'; DECLARE @Sql NVARCHAR(MAX) = N'UPDATE ' + QUOTENAME(@SchemaName) + N'.' + QUOTENAME(@TableName) + N' SET [Col1] = @NewValue WHERE [Id] = @Id;'; EXEC [sys].[sp_executesql] @Sql, N'@Id INT, @NewValue NVARCHAR(100)', @Id = @Id, @NewValue = @NewValue;
QUOTENAME handles object names. Pass data values as parameters.
Return the affected-row count from a stored procedure:
CREATE OR ALTER PROCEDURE [dbo].[p_UpdateSample] @Id INT, @NewValue NVARCHAR(100), @RowsUpdated INT OUTPUT AS BEGIN SET NOCOUNT ON; UPDATE [dbo].[SampleTable] SET [Col1] = @NewValue WHERE [Id] = @Id; SET @RowsUpdated = @@ROWCOUNT; END; GO DECLARE @RowsUpdated INT; EXEC [dbo].[p_UpdateSample] @Id = 1, @NewValue = N'New Value', @RowsUpdated = @RowsUpdated OUTPUT; SELECT @RowsUpdated AS [RowsUpdated];
Update inside a natively compiled stored procedure:
-- SampleMemoryTable is an existing memory-optimized table CREATE PROCEDURE [dbo].[p_UpdateSampleNative] @Id INT, @NewValue NVARCHAR(100) WITH NATIVE_COMPILATION, SCHEMABINDING, EXECUTE AS OWNER AS BEGIN ATOMIC WITH ( TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'us_english' ) UPDATE [dbo].[SampleMemoryTable] SET [Col1] = @NewValue WHERE [Id] = @Id; END; GO EXEC [dbo].[p_UpdateSampleNative] @Id = 1, @NewValue = N'New Value';
Create the procedure once. Native procedures have a smaller supported T-SQL surface. Native procedure reference.
Update a local return table inside a function:
CREATE OR ALTER FUNCTION [dbo].[f_SampleValues]() RETURNS @Result TABLE ( [Id] INT, [Col1] NVARCHAR(100) ) AS BEGIN INSERT INTO @Result ([Id], [Col1]) SELECT [Id], [Col1] FROM [dbo].[SampleTable]; UPDATE @Result SET [Col1] = N'Unknown' WHERE [Col1] IS NULL; RETURN; END; GO SELECT [Id], [Col1] FROM [dbo].[f_SampleValues]();
A T-SQL function can update its local table variables. It cannot update permanent tables.
Full UPDATE syntax and restrictions on Microsoft Learn.
