Thursday

19-06-2025 Vol 19

OUTPUT Clause — Log, Sync, and Debug Like a Pro

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

  1. Introduction to the OUTPUT Clause
    1. What is the OUTPUT Clause?
    2. Why Use the OUTPUT Clause?
    3. Basic Syntax
  2. OUTPUT Clause with INSERT Statements
    1. Capturing Inserted Values
    2. Using OUTPUT with IDENTITY Columns
    3. Handling Multiple Row Inserts
  3. OUTPUT Clause with UPDATE Statements
    1. Capturing Updated Values (Inserted and Deleted)
    2. Using OUTPUT with Triggers
    3. Tracking Changes in Audit Tables
  4. OUTPUT Clause with DELETE Statements
    1. Capturing Deleted Values
    2. Using OUTPUT to Archive Deleted Data
    3. Deleting Data Based on Output Values
  5. OUTPUT Clause with MERGE Statements
    1. Understanding MERGE Statement
    2. Capturing Actions Performed by MERGE (Inserted, Updated, Deleted)
    3. Complex MERGE Scenarios with OUTPUT
  6. Advanced OUTPUT Clause Techniques
    1. Using OUTPUT INTO Table Variables
    2. Using OUTPUT INTO Temporary Tables
    3. Combining OUTPUT with Other SQL Features
  7. OUTPUT Clause for Logging and Auditing
    1. Implementing Auditing with OUTPUT
    2. Logging Data Changes Effectively
    3. Meeting Compliance Requirements
  8. OUTPUT Clause for Data Synchronization
    1. Synchronizing Data Between Tables
    2. Implementing Change Tracking
    3. Building Data Replication Solutions
  9. OUTPUT Clause for Debugging
    1. Troubleshooting Data Issues
    2. Verifying Data Transformations
    3. Identifying Errors in Data Operations
  10. Best Practices and Performance Considerations
    1. Writing Efficient OUTPUT Statements
    2. Avoiding Common Pitfalls
    3. Optimizing Performance with Indexes
  11. Real-World Examples and Use Cases
    1. Inventory Management
    2. Order Processing
    3. User Activity Tracking
  12. 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 separate SELECT 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>: The INSERT, UPDATE, DELETE, or MERGE statement.
  • OUTPUT: The keyword that indicates the use of the OUTPUT clause.
  • <column_specifications>: Specifies the columns to be captured. You can use the INSERTED and DELETED prefixes to refer to the new and old values, respectively. For example, INSERTED.Column1 refers to the new value of Column1 after an insert or update, while DELETED.Column1 refers to the old value of Column1 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 the INTO clause.
  • WHERE <predicate>: An optional WHERE clause that filters the rows captured by the OUTPUT 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 ProductIDs 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 JOINs, 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 and DELETED 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 the OUTPUT clause to store the output in a table variable or temporary table, make sure you include the INTO clause.
  • Not handling NULL values: Be aware that the DELETED columns will be NULL for inserted rows, and the INSERTED columns will be NULL 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

omcoding

Leave a Reply

Your email address will not be published. Required fields are marked *