OUTPUT Clause – Log, Sync, and Debug Like a Pro
The OUTPUT
clause in SQL Server is a powerful feature that allows you to capture the rows affected by INSERT
, UPDATE
, DELETE
, or MERGE
statements. This capability is incredibly valuable for auditing, data synchronization, debugging, and building complex data workflows. This comprehensive guide will dive deep into the OUTPUT
clause, exploring its syntax, use cases, best practices, and advanced techniques. By the end of this article, you’ll be equipped to use the OUTPUT
clause like a pro, improving your SQL development and troubleshooting capabilities.
Table of Contents
- Introduction to the OUTPUT Clause
- What is the OUTPUT Clause?
- Why Use the OUTPUT Clause?
- Basic Syntax
- OUTPUT Clause with INSERT Statements
- Capturing Inserted Values
- Using OUTPUT with IDENTITY Columns
- Handling Multiple Row Inserts
- OUTPUT Clause with UPDATE Statements
- Capturing Updated Values (Inserted and Deleted)
- Using OUTPUT with Triggers
- Tracking Changes in Audit Tables
- OUTPUT Clause with DELETE Statements
- Capturing Deleted Values
- Using OUTPUT to Archive Deleted Data
- Deleting Data Based on Output Values
- OUTPUT Clause with MERGE Statements
- Understanding MERGE Statement
- Capturing Actions Performed by MERGE (Inserted, Updated, Deleted)
- Complex MERGE Scenarios with OUTPUT
- Advanced OUTPUT Clause Techniques
- Using OUTPUT INTO Table Variables
- Using OUTPUT INTO Temporary Tables
- Combining OUTPUT with Other SQL Features
- OUTPUT Clause for Logging and Auditing
- Implementing Auditing with OUTPUT
- Logging Data Changes Effectively
- Meeting Compliance Requirements
- OUTPUT Clause for Data Synchronization
- Synchronizing Data Between Tables
- Implementing Change Tracking
- Building Data Replication Solutions
- OUTPUT Clause for Debugging
- Troubleshooting Data Issues
- Verifying Data Transformations
- Identifying Errors in Data Operations
- Best Practices and Performance Considerations
- Writing Efficient OUTPUT Statements
- Avoiding Common Pitfalls
- Optimizing Performance with Indexes
- Real-World Examples and Use Cases
- Inventory Management
- Order Processing
- User Activity Tracking
- Conclusion
1. Introduction to the OUTPUT Clause
1.1 What is the OUTPUT Clause?
The OUTPUT
clause is a Transact-SQL (T-SQL) feature in SQL Server that allows you to retrieve information about the rows affected by data modification language (DML) statements, specifically INSERT
, UPDATE
, DELETE
, and MERGE
. It essentially captures a “snapshot” of the data that has been modified, providing both the old values (for UPDATE
and DELETE
) and the new values (for INSERT
and UPDATE
).
Think of it as a built-in mechanism to track changes directly within your data modification operations. Instead of needing separate SELECT
statements to verify or audit changes, you can capture the affected data inline with the DML operation itself.
1.2 Why Use the OUTPUT Clause?
The OUTPUT
clause offers numerous benefits, making it a valuable tool for various scenarios:
- Auditing: Easily track changes made to your data, including who made the change, when it occurred, and what values were modified. This is crucial for compliance and data governance.
- Data Synchronization: Synchronize data between tables or databases by capturing changes and applying them elsewhere. This is useful for creating data replicas or implementing change data capture (CDC).
- Debugging: Troubleshoot data-related issues by examining the data before and after modifications. This can help you identify errors in your data transformations or logic.
- Workflow Automation: Use the captured data to trigger subsequent actions or processes. For example, you could use the
OUTPUT
clause to update related tables or send notifications based on data changes. - Performance: In some cases, using the
OUTPUT
clause can be more efficient than separateSELECT
statements, as it retrieves the data directly during the modification operation.
1.3 Basic Syntax
The basic syntax of the OUTPUT
clause is as follows:
<DML_statement>
OUTPUT <column_specifications>
[INTO <table_variable | table_name> (<column_list>)]
[WHERE <predicate>];
Where:
<DML_statement>
: TheINSERT
,UPDATE
,DELETE
, orMERGE
statement.OUTPUT
: The keyword that indicates the use of theOUTPUT
clause.<column_specifications>
: Specifies the columns to be captured. You can use theINSERTED
andDELETED
prefixes to refer to the new and old values, respectively. For example,INSERTED.Column1
refers to the new value ofColumn1
after an insert or update, whileDELETED.Column1
refers to the old value ofColumn1
before an update or delete.INTO <table_variable | table_name>
: Optionally specifies a table variable or a temporary table where the output data will be stored.<column_list>
: Specifies the columns in the table variable or temporary table that will receive the output data. This is required when using theINTO
clause.WHERE <predicate>
: An optionalWHERE
clause that filters the rows captured by theOUTPUT
clause.
2. OUTPUT Clause with INSERT Statements
2.1 Capturing Inserted Values
When used with INSERT
statements, the OUTPUT
clause allows you to capture the values that were inserted into a table. This is particularly useful for retrieving automatically generated values, such as identity values or values generated by triggers.
Example:
Suppose you have a table called Products
with the following structure:
CREATE TABLE Products (
ProductID INT IDENTITY(1,1) PRIMARY KEY,
ProductName VARCHAR(255) NOT NULL,
Price DECIMAL(10, 2)
);
You can use the OUTPUT
clause to capture the newly generated ProductID
when inserting a new product:
INSERT INTO Products (ProductName, Price)
OUTPUT INSERTED.ProductID, INSERTED.ProductName, INSERTED.Price
VALUES ('New Product', 19.99);
This will return a result set containing the ProductID
, ProductName
, and Price
of the newly inserted row.
2.2 Using OUTPUT with IDENTITY Columns
The OUTPUT
clause is particularly useful for retrieving the identity value generated by an IDENTITY
column. This is often necessary when you need to reference the newly inserted row in other tables or operations.
Example:
Continuing with the Products
table example, you can capture the ProductID
and insert it into another table, such as an OrderItems
table:
DECLARE @OutputTable TABLE (ProductID INT);
INSERT INTO Products (ProductName, Price)
OUTPUT INSERTED.ProductID INTO @OutputTable
VALUES ('Another Product', 29.99);
-- Now you can use the @OutputTable to get the ProductID for inserting into OrderItems table
INSERT INTO OrderItems (ProductID, Quantity, Price)
SELECT ProductID, 1, (SELECT Price FROM Products WHERE ProductID = (SELECT ProductID FROM @OutputTable))
FROM @OutputTable;
In this example, we declare a table variable @OutputTable
to store the ProductID
. The OUTPUT INTO
clause inserts the ProductID
into this table variable. We then use the @OutputTable
to retrieve the ProductID
and insert it into the OrderItems
table.
2.3 Handling Multiple Row Inserts
The OUTPUT
clause works seamlessly with multiple row inserts. It will return a row for each inserted row, allowing you to capture the generated values for each new row.
Example:
DECLARE @OutputTable TABLE (ProductID INT, ProductName VARCHAR(255));
INSERT INTO Products (ProductName, Price)
OUTPUT INSERTED.ProductID, INSERTED.ProductName INTO @OutputTable
VALUES ('Product A', 9.99), ('Product B', 14.99), ('Product C', 24.99);
SELECT * FROM @OutputTable;
This will insert three new products and return a result set with three rows, each containing the ProductID
and ProductName
of the corresponding inserted product. The data is stored in the table variable `@OutputTable`.
3. OUTPUT Clause with UPDATE Statements
3.1 Capturing Updated Values (Inserted and Deleted)
When used with UPDATE
statements, the OUTPUT
clause provides both the old values (using the DELETED
prefix) and the new values (using the INSERTED
prefix) of the updated columns. This allows you to track the changes made to the data.
Example:
UPDATE Products
SET Price = Price * 1.10 -- Increase price by 10%
OUTPUT DELETED.ProductID, DELETED.ProductName, DELETED.Price AS OldPrice, INSERTED.Price AS NewPrice
WHERE ProductName LIKE '%Product%';
This will increase the price of all products with names containing “Product” by 10% and return a result set containing the ProductID
, ProductName
, the original price (OldPrice
), and the new price (NewPrice
) for each updated product.
3.2 Using OUTPUT with Triggers
The OUTPUT
clause can be used in conjunction with triggers. However, it’s important to understand how they interact. If a trigger modifies the data that is also being captured by the OUTPUT
clause, the OUTPUT
clause will reflect the changes made by the trigger.
Example:
Suppose you have a trigger that logs all price changes to an audit table:
CREATE TRIGGER TR_Products_Update
ON Products
AFTER UPDATE
AS
BEGIN
INSERT INTO ProductAudit (ProductID, ProductName, OldPrice, NewPrice, UpdateDate)
SELECT DELETED.ProductID, DELETED.ProductName, DELETED.Price, INSERTED.Price, GETDATE()
FROM DELETED
INNER JOIN INSERTED ON DELETED.ProductID = INSERTED.ProductID;
END;
You can still use the OUTPUT
clause to capture the updated values, even though the trigger is also modifying the data:
UPDATE Products
SET Price = Price * 1.15 -- Increase price by 15%
OUTPUT DELETED.ProductID, DELETED.ProductName, DELETED.Price AS OldPrice, INSERTED.Price AS NewPrice
WHERE ProductName LIKE '%Product%';
The OUTPUT
clause will return the price changes *after* the trigger has executed and updated the audit table.
3.3 Tracking Changes in Audit Tables
One of the most common uses of the OUTPUT
clause with UPDATE
statements is to track changes in audit tables. This provides a historical record of data modifications, which is essential for compliance and data governance.
Example:
Instead of using a trigger (as in the previous example), you can use the OUTPUT INTO
clause to directly insert the changes into an audit table:
CREATE TABLE ProductAudit (
AuditID INT IDENTITY(1,1) PRIMARY KEY,
ProductID INT,
ProductName VARCHAR(255),
OldPrice DECIMAL(10, 2),
NewPrice DECIMAL(10, 2),
UpdateDate DATETIME
);
UPDATE Products
SET Price = Price * 1.20 -- Increase price by 20%
OUTPUT INSERTED.ProductID, INSERTED.ProductName, DELETED.Price, INSERTED.Price, GETDATE()
INTO ProductAudit (ProductID, ProductName, OldPrice, NewPrice, UpdateDate)
WHERE ProductName LIKE '%Product%';
This will update the prices and simultaneously insert a record into the ProductAudit
table, capturing the ProductID
, ProductName
, original price, new price, and the update date.
4. OUTPUT Clause with DELETE Statements
4.1 Capturing Deleted Values
When used with DELETE
statements, the OUTPUT
clause allows you to capture the values of the rows that were deleted. This is useful for archiving deleted data or performing other actions based on the deleted rows.
Example:
DELETE FROM Products
OUTPUT DELETED.ProductID, DELETED.ProductName, DELETED.Price
WHERE Price < 5.00;
This will delete all products with a price less than $5.00 and return a result set containing the ProductID
, ProductName
, and Price
of the deleted products.
4.2 Using OUTPUT to Archive Deleted Data
A common use case for the OUTPUT
clause with DELETE
statements is to archive deleted data. This involves capturing the deleted rows and inserting them into an archive table.
Example:
CREATE TABLE ProductArchive (
ArchiveID INT IDENTITY(1,1) PRIMARY KEY,
ProductID INT,
ProductName VARCHAR(255),
Price DECIMAL(10, 2),
DeletionDate DATETIME
);
DELETE FROM Products
OUTPUT DELETED.ProductID, DELETED.ProductName, DELETED.Price, GETDATE()
INTO ProductArchive (ProductID, ProductName, Price, DeletionDate)
WHERE Price < 5.00;
This will delete the products with a price less than $5.00 and insert a record into the ProductArchive
table, capturing the ProductID
, ProductName
, Price
, and the deletion date.
4.3 Deleting Data Based on Output Values
You can use the OUTPUT
clause to delete data from other tables based on the values of the deleted rows. This is useful for maintaining data integrity and consistency across related tables.
Example:
Suppose you have an OrderItems
table that references the Products
table. You want to delete all order items associated with a deleted product.
DECLARE @DeletedProducts TABLE (ProductID INT);
DELETE FROM Products
OUTPUT DELETED.ProductID INTO @DeletedProducts
WHERE Price > 100.00;
DELETE FROM OrderItems
WHERE ProductID IN (SELECT ProductID FROM @DeletedProducts);
This will delete all products with a price greater than $100.00 and capture their ProductID
s into the @DeletedProducts
table variable. Then, it will delete all order items associated with those deleted products.
5. OUTPUT Clause with MERGE Statements
5.1 Understanding MERGE Statement
The MERGE
statement is a powerful feature in SQL Server that allows you to perform INSERT
, UPDATE
, and DELETE
operations in a single statement, based on whether rows exist in a target table that match rows in a source table. It's particularly useful for synchronizing data between tables.
5.2 Capturing Actions Performed by MERGE (Inserted, Updated, Deleted)
When used with MERGE
statements, the OUTPUT
clause can capture the actions performed by the MERGE
statement (INSERT
, UPDATE
, or DELETE
) along with the affected data. This provides a comprehensive view of the changes made during the merge operation.
Example:
Suppose you have a SourceProducts
table and a TargetProducts
table. You want to merge the data from the source table into the target table, inserting new products, updating existing products, and deleting products that no longer exist in the source table.
CREATE TABLE SourceProducts (
ProductID INT PRIMARY KEY,
ProductName VARCHAR(255) NOT NULL,
Price DECIMAL(10, 2)
);
CREATE TABLE TargetProducts (
ProductID INT PRIMARY KEY,
ProductName VARCHAR(255) NOT NULL,
Price DECIMAL(10, 2)
);
MERGE TargetProducts AS TARGET
USING SourceProducts AS SOURCE
ON TARGET.ProductID = SOURCE.ProductID
WHEN MATCHED AND TARGET.Price <> SOURCE.Price THEN
UPDATE SET TARGET.Price = SOURCE.Price
WHEN NOT MATCHED BY TARGET THEN
INSERT (ProductID, ProductName, Price)
VALUES (SOURCE.ProductID, SOURCE.ProductName, SOURCE.Price)
WHEN NOT MATCHED BY SOURCE THEN
DELETE
OUTPUT $action, DELETED.ProductID, DELETED.ProductName, DELETED.Price, INSERTED.ProductID, INSERTED.ProductName, INSERTED.Price;
In this example, the $action
column in the OUTPUT
clause indicates the action performed by the MERGE
statement ('INSERT'
, 'UPDATE'
, or 'DELETE'
). The DELETED
and INSERTED
prefixes capture the old and new values, respectively. Note that the DELETED
columns will be NULL
for inserted rows, and the INSERTED
columns will be NULL
for deleted rows.
5.3 Complex MERGE Scenarios with OUTPUT
The OUTPUT
clause can handle more complex MERGE
scenarios, such as conditional updates or deletions based on specific criteria.
Example:
Modify the previous example to only update the price if the new price is greater than the old price and to only delete products that are inactive.
First add an `IsActive` column to both tables:
ALTER TABLE SourceProducts ADD IsActive BIT NOT NULL DEFAULT(1);
ALTER TABLE TargetProducts ADD IsActive BIT NOT NULL DEFAULT(1);
Then update the merge statement:
MERGE TargetProducts AS TARGET
USING SourceProducts AS SOURCE
ON TARGET.ProductID = SOURCE.ProductID
WHEN MATCHED AND TARGET.Price <> SOURCE.Price AND SOURCE.Price > TARGET.Price THEN
UPDATE SET TARGET.Price = SOURCE.Price
WHEN NOT MATCHED BY TARGET THEN
INSERT (ProductID, ProductName, Price, IsActive)
VALUES (SOURCE.ProductID, SOURCE.ProductName, SOURCE.Price, SOURCE.IsActive)
WHEN NOT MATCHED BY SOURCE AND TARGET.IsActive = 0 THEN
DELETE
OUTPUT $action, DELETED.ProductID, DELETED.ProductName, DELETED.Price, INSERTED.ProductID, INSERTED.ProductName, INSERTED.Price;
This MERGE
statement will only update the price if the source price is greater than the target price, and it will only delete products that are not found in the source table and are marked as inactive in the target table. The OUTPUT
clause will still capture all the actions performed by the MERGE
statement.
6. Advanced OUTPUT Clause Techniques
6.1 Using OUTPUT INTO Table Variables
Table variables provide a convenient way to store the output of the OUTPUT
clause within the scope of a single batch or stored procedure. This allows you to process the captured data without needing to create temporary tables.
Example:
DECLARE @ProductChanges TABLE (
Action VARCHAR(10),
ProductID INT,
ProductName VARCHAR(255),
OldPrice DECIMAL(10, 2),
NewPrice DECIMAL(10, 2)
);
UPDATE Products
SET Price = Price * 1.05 -- Increase price by 5%
OUTPUT 'UPDATE', DELETED.ProductID, DELETED.ProductName, DELETED.Price, INSERTED.Price
INTO @ProductChanges (Action, ProductID, ProductName, OldPrice, NewPrice)
WHERE ProductName LIKE '%Product%';
SELECT * FROM @ProductChanges;
This example updates the price of products and stores the changes in the @ProductChanges
table variable. The SELECT
statement then retrieves the data from the table variable.
6.2 Using OUTPUT INTO Temporary Tables
Temporary tables (both local and global) can also be used to store the output of the OUTPUT
clause. This is useful when you need to share the captured data between different batches or stored procedures.
Example:
CREATE TABLE #ProductChanges (
Action VARCHAR(10),
ProductID INT,
ProductName VARCHAR(255),
OldPrice DECIMAL(10, 2),
NewPrice DECIMAL(10, 2)
);
UPDATE Products
SET Price = Price * 1.10 -- Increase price by 10%
OUTPUT 'UPDATE', DELETED.ProductID, DELETED.ProductName, DELETED.Price, INSERTED.Price
INTO #ProductChanges (Action, ProductID, ProductName, OldPrice, NewPrice)
WHERE ProductName LIKE '%Product%';
SELECT * FROM #ProductChanges;
This is similar to the table variable example, but it uses a local temporary table (#ProductChanges
) to store the output. The temporary table will be automatically dropped when the connection is closed or when it goes out of scope.
6.3 Combining OUTPUT with Other SQL Features
The OUTPUT
clause can be combined with other SQL features, such as JOIN
s, subqueries, and user-defined functions, to create more complex and powerful data modification operations.
Example:
Suppose you want to update the price of products based on a discount provided in a separate table called Discounts
.
CREATE TABLE Discounts (
ProductID INT,
Discount DECIMAL(5, 2)
);
UPDATE Products
SET Price = Price * (1 - d.Discount)
OUTPUT DELETED.ProductID, DELETED.ProductName, DELETED.Price, INSERTED.Price
FROM Products p
INNER JOIN Discounts d ON p.ProductID = d.ProductID
WHERE p.ProductName LIKE '%Product%';
This will update the price of products based on the discount specified in the Discounts
table and capture the changes using the OUTPUT
clause. The JOIN
allows you to access data from multiple tables within the UPDATE
statement.
7. OUTPUT Clause for Logging and Auditing
7.1 Implementing Auditing with OUTPUT
As demonstrated previously, the OUTPUT
clause is excellent for creating audit trails of data modifications. Instead of writing separate logging routines, you can directly capture the changes alongside the DML operations. This ensures that the audit trail is always up-to-date and consistent with the data modifications.
7.2 Logging Data Changes Effectively
When logging data changes, consider what information is most relevant to your auditing requirements. This may include the user who made the change, the date and time of the change, the type of operation performed (INSERT
, UPDATE
, DELETE
), and the old and new values of the modified columns.
7.3 Meeting Compliance Requirements
Many industries and regulations require detailed audit trails of data modifications. The OUTPUT
clause can help you meet these compliance requirements by providing a reliable and auditable record of data changes.
When designing your auditing solution, be sure to consider the following:
- Retention period: How long should the audit data be retained?
- Access control: Who should have access to the audit data?
- Reporting: What types of reports are required from the audit data?
- Security: How can the audit data be protected from unauthorized modification or deletion?
8. OUTPUT Clause for Data Synchronization
8.1 Synchronizing Data Between Tables
The OUTPUT
clause can be used to synchronize data between tables, either within the same database or across different databases. This involves capturing the changes made to the source table and applying those changes to the target table.
8.2 Implementing Change Tracking
Change tracking is a mechanism for identifying which rows have been modified in a table since a specific point in time. The OUTPUT
clause can be used to implement change tracking by capturing the changes made to a table and storing them in a change tracking table.
Example:
CREATE TABLE ProductChanges (
ChangeID BIGINT IDENTITY(1,1) PRIMARY KEY,
ProductID INT,
ProductName VARCHAR(255),
Price DECIMAL(10, 2),
ChangeType VARCHAR(10), -- 'INSERT', 'UPDATE', 'DELETE'
ChangeDate DATETIME
);
For inserts, you can use:
INSERT INTO Products (ProductName, Price)
OUTPUT INSERTED.ProductID, INSERTED.ProductName, INSERTED.Price, 'INSERT', GETDATE()
INTO ProductChanges (ProductID, ProductName, Price, ChangeType, ChangeDate)
VALUES ('Another Product', 34.99);
And similarly for Updates and Deletes.
8.3 Building Data Replication Solutions
Data replication involves copying data from one database to another. The OUTPUT
clause can be used to build data replication solutions by capturing the changes made to the source database and applying those changes to the target database.
This can involve techniques like:
- Transactional Replication: SQL Server's built-in replication feature, but
OUTPUT
can supplement this. - Snapshot Replication: Periodically copying the entire dataset.
- Custom Replication Solutions: Using the
OUTPUT
clause and other T-SQL features to build a custom replication solution.
9. OUTPUT Clause for Debugging
9.1 Troubleshooting Data Issues
The OUTPUT
clause is an invaluable tool for debugging data-related issues. By capturing the data before and after modifications, you can quickly identify errors in your data transformations or logic.
9.2 Verifying Data Transformations
When performing complex data transformations, it's important to verify that the transformations are working correctly. The OUTPUT
clause allows you to capture the data before and after the transformation, making it easy to compare the results and identify any discrepancies.
9.3 Identifying Errors in Data Operations
By examining the output of the OUTPUT
clause, you can often identify the root cause of errors in your data operations. For example, if you're seeing unexpected values in the output, it may indicate an issue with your WHERE
clause, your SET
clause, or your data source.
10. Best Practices and Performance Considerations
10.1 Writing Efficient OUTPUT Statements
To ensure optimal performance, it's important to write efficient OUTPUT
statements. This includes:
- Specifying only the necessary columns: Avoid capturing unnecessary columns in the
OUTPUT
clause, as this can increase the overhead of the operation. - Using appropriate data types: Use appropriate data types for the columns in the table variable or temporary table that stores the output.
- Avoiding unnecessary operations: Avoid performing unnecessary operations within the
OUTPUT
clause, such as complex calculations or string manipulations.
10.2 Avoiding Common Pitfalls
There are several common pitfalls to avoid when using the OUTPUT
clause:
- Incorrect use of
INSERTED
andDELETED
prefixes: Make sure you're using the correct prefixes to refer to the old and new values. - Forgetting the
INTO
clause: If you're using theOUTPUT
clause to store the output in a table variable or temporary table, make sure you include theINTO
clause. - Not handling NULL values: Be aware that the
DELETED
columns will beNULL
for inserted rows, and theINSERTED
columns will beNULL
for deleted rows. - Triggers and OUTPUT interactions: Remember that triggers can modify data, and the `OUTPUT` clause will reflect those changes.
10.3 Optimizing Performance with Indexes
Indexes can significantly improve the performance of OUTPUT
statements, especially when dealing with large tables. Make sure you have appropriate indexes on the columns used in the WHERE
clause and the JOIN
conditions.
Consider the following:
- Clustered Indexes: The clustered index defines the physical order of the data in the table.
- Non-Clustered Indexes: Non-clustered indexes provide an alternative way to access the data.
- Covering Indexes: A covering index includes all the columns needed for a query, avoiding the need to access the base table.
11. Real-World Examples and Use Cases
11.1 Inventory Management
In an inventory management system, the OUTPUT
clause can be used to track inventory adjustments, such as when products are added, removed, or transferred. This information can be used to update stock levels, generate reports, and identify potential discrepancies.
11.2 Order Processing
In an order processing system, the OUTPUT
clause can be used to track changes to orders, such as when orders are created, updated, or cancelled. This information can be used to update order statuses, generate invoices, and track customer activity.
11.3 User Activity Tracking
The OUTPUT
clause can be used to track user activity in an application, such as when users log in, log out, create new accounts, or modify their profiles. This information can be used to analyze user behavior, identify security threats, and improve the user experience.
12. Conclusion
The OUTPUT
clause is a versatile and powerful feature in SQL Server that can significantly enhance your data management capabilities. By capturing the rows affected by DML statements, you can streamline auditing, data synchronization, debugging, and workflow automation. Mastering the OUTPUT
clause will elevate your SQL skills and enable you to build more robust