Вы находитесь на странице: 1из 13

Implementing the OUTPUT Clause in SQL Server 2008

22 July 2010
by Robert Sheldon

In retrospect, it was probably the inclusion of the OUTPUT clause in the MERGE statement that gave
SQL Server 2008 its most powerful SQL enhancement.. It isn't the easiest of features to explain, but
Bob does it in his usual clear and careful way.

In SQL Server 2005 and 2008, you can add an OUTPUT clause to your data manipulation language (DML)
statements. The clause returns a copy of the data that youve inserted into or deleted from your tables. You can
return that data to a table variable, a temporary or permanent table, or to the processing application thats calls
the DML statement. You can then use that data for such purposes as archiving, confirmation messaging, or other
application requirements.
Both SQL Server 2005 and 2008 let you add an OUTPUT clause to INSERT, UPDATE, or DELETE statements.
SQL Server 2008 also lets you add the clause to MERGE statements. In this article, I demonstrate how to include
an OUTPUT clause in each one of these statements. I developed the examples used for the article on a local
instance of SQL Server 2008. The examples also work in SQL Server 2005, except for those related to the
MERGE statement.
If you want to try the examples in this article, you should first run the following script to create the Books table in a
SQL Server database:
IF OBJECT_ID ('Books', 'U') IS NOT NULL
DROP TABLE dbo.Books;
CREATE TABLE dbo.Books
(
BookID int NOT NULL PRIMARY KEY,
BookTitle nvarchar(50) NOT NULL,
ModifiedDate datetime NOT NULL
);

The examples are all performed against the Books table. If you run the statements, do so in the order presented
in the article, if you want your results to match the results shown here.

Using an OUTPUT Clause in an INSERT Statement


When you insert data into a table, you can use the OUTPUT clause to return a copy of the data thats been
inserted into the table. The OUTPUT clause takes two basic forms: OUTPUT and OUTPUT INTO. Use the
OUTPUT form if you want to return the data to the calling application. Use the OUTPUT INTO form if you want to
return the data to a table or a table variable.
For most of these examples, I return the outputted data only to a table variable. The process for returning data to
a table is the same as that for a variable. If in running these examples, you prefer to return the data to a table
instead of a variable, simply create a table with the same columns as the variable, then specify the table rather
than the variable in the OUTPUT clause.

NOTE: Returning data to the processing application is even simpler than returning it to a table or variable. As we
work through the first example, Ill point out how thats done.

In the first example, I declare a table variable named @InsertOutput1 with the same column structure as the
Books table. I then insert a row into the Books table and output the inserted data to the variable:
-- declare @InsertOutput1 table variable
DECLARE @InsertOutput1 table
(
BookID int,
BookTitle nvarchar(50),
ModifiedDate datetime
);
-- insert new row into Books table
INSERT INTO Books
OUTPUT INSERTED.*
INTO @InsertOutput1
VALUES(101, 'One Hundred Years of Solitude', GETDATE());
-- view inserted row in Books table
SELECT * FROM Books;
-- view output row in @InsertOutput1 variable
SELECT * FROM @InsertOutput1;

As you can see in the INSERT statement, the OUTPUT clause is placed between the INSERT and VALUES
clause. Because Im outputting the data to a variable, the OUTPUT clause includes two parts: the OUTPUT
subclause and the INTO subclause.
The OUTPUT subclause includes the keyword INSERTED, followed a period and asterisk. The keyword is a
column prefix that specifies which values to output. The asterisk indicates that all column values inserted into the
Books table (BookID, BookTitle, ModifiedDate) should be outputted to the variable.
The INTO subclause defines the table or table variable that the OUTPUT clause targets. In this case, the target is
the @InsertOutput1 variable. If you want to return the information to the calling application, rather than the
variable (or a table), you simply omit the INTO subclause.
Thats all there is to defining an OUTPUT clause. After the INSERT statement, I include a couple SELECT
statements to view the contents of the table and the variable. Because this is the first row inserted into the table,
the results returned by both statements are the same, as shown in the following table:
BookID
101

BookTitle
One Hundred Years of Solitude

ModifiedDate
2010-07-17 12:21:31.300

As you can see, the values that were inserted into the Books table are also returned to the @InsertOutput1
variable. You could just as easily have saved the information to a table. Or you could have eliminated the INTO
subclause and sent the data to the processing application.

Now lets look at another example. In the next set of statements, I first declare the @InsertOutput2 table variable
with the same column structure as the Books table. Then I insert a second row into the Books table and output
the values to the new variable:
-- declare @InsertOutput2 table variable
DECLARE @InsertOutput2 table
(
BookID int,
BookTitle nvarchar(50),
ModifiedDate datetime
);
-- insert new row into Books table
INSERT INTO Books
OUTPUT
INSERTED.BookID,
INSERTED.BookTitle,
INSERTED.ModifiedDate
INTO @InsertOutput2
VALUES(102, 'Pride and Prejudice', GETDATE());
-- view inserted row in Books table
SELECT * FROM Books;
-- view output row in @InsertOutput2 variable
SELECT * FROM @InsertOutput2;

The primary difference between this INSERT statement and the one in the previous example as that I now use
the INSERTED column prefix in the OUTPUT subclause to specify each column (rather than using the asterisk).
Notice that Ive separated the INSERTED column specifications with commas.
Because I inserted a second row into the Books table, the first SELECT statement now returns two rows, as
shown in the following results:
BookID
101
102

BookTitle
One Hundred Years of Solitude
Pride and Prejudice

ModifiedDate
2010-07-17 12:21:31.300
2010-07-17 12:23:22.100

However, because Im inserting only one row into the Books table, the @InsertOutput2 variable contains only one
row. As a result, the second SELECT statement returns only one row, as shown in the following table:
BookID
102

BookTitle
Pride and Prejudice

ModifiedDate
2010-07-17 12:23:22.100

In some cases, you dont want to output all inserted values, so you must define your OUTPUT clause accordingly.
In the next example, I output only the BookID and BookTitle columns:
-- declare @InsertOutput2 table variable
DECLARE @InsertOutput3 table
(

BookID int,
BookTitle nvarchar(50)
);
-- insert new row into Books table
INSERT INTO Books
OUTPUT
INSERTED.BookID,
INSERTED.BookTitle
INTO @InsertOutput3
VALUES(103, 'The Great Gatsby', GETDATE());
-- view inserted row in Books table
SELECT * FROM Books;
-- view output row in @InsertOutput3 variable
SELECT * FROM @InsertOutput3;

As before, I first declare a table variable, but this one includes only the BookID and BookTitle columns. Next, I
define an INSERT statement, which includes an OUTPUT clause that reflects that only two columns will be
outputted. That means, in the OUTPUT subclause, I add only two INSERTED column prefixes and specify the
BookID and BookTitle columns.
When you run the example, the first SELECT statement, as expected, returns the three rows shown in the
following table:
BookID
101
102
103

BookTitle
One Hundred Years of Solitude
Pride and Prejudice
The Great Gatsby

ModifiedDate
2010-07-17 12:21:31.300
2010-07-17 12:23:22.100
2010-07-17 12:25:25.067

However, the second SELECT statement returns only the BookID and BookTitle columns of the newly inserted
row:
BookID
103

BookTitle
The Great Gatsby

Now lets look at another variation on the OUTPUT clause. Once more, I define a table variable (@InsertOutput4)
that includes three columns, but this time the columns have different names and are in a different order:
-- declare @InsertOutput4 table variable
DECLARE @InsertOutput4 table
(
Title nvarchar(50),
TitleID int,
TitleAddDate datetime
);
-- insert new row into Books table
INSERT INTO Books
OUTPUT

INSERTED.BookID,
INSERTED.BookTitle,
INSERTED.ModifiedDate
INTO @InsertOutput4
(TitleID, Title, TitleAddDate)
VALUES(104, 'Mrs. Dalloway', GETDATE());
-- view inserted row in Books table
SELECT * FROM Books;
-- view output row in @InsertOutput4 variable
SELECT * FROM @InsertOutput4;

Because the target column names and order dont match the Books table, I add the target column names to the
INTO subclauseenclosed in parentheses and separated by commas. The order of the target column names
must also correspond to the order in which the columns are being inserted and outputted. As a result, the BookID
column is matched to the TitleID column, BookTitle to Title, and ModifiedDate to TitleAddDate.
I then add the SELECT statements to verify the results. The first SELECT statement returns all four rows:
BookID
101
102
103
104

BookTitle
One Hundred Years of Solitude
Pride and Prejudice
The Great Gatsby
Mrs. Dalloway

ModifiedDate
2010-07-17 12:34:16.467
2010-07-17 12:34:24.580
2010-07-17 12:34:38.330
2010-07-17 12:36:10.237

The second SELECT statement returns only the newly inserted row, with the columns names and values as they
appear in the @InsertOutput4 variable:
Title
Mrs. Dalloway

TitleID
104

TitleAddDate
2010-07-17 12:36:10.237

You should now have a good sense of how to use an OUTPUT clause in an INSERT statement. Note, however,
that most of the principles explained here apply to other DML statements as well, so lets move on to the UPDATE
statement.

Using an OUTPUT Clause in an UPDATE Statement


In the previous examples, the OUTPUT clause includes the INSERTED column prefix in the OUTPUT subclause.
However, the OUTPUT clause supports a second column prefixDELETED. The DELETED prefix returns the
values that have been deleted from a table.
This is important because an UPDATE operation is actually two operationsa deletion and an insertion. As a
result, you use both the INSERTED and DELETED column prefixes when adding an OUTPUT clause to an
UPDATE statement. Lets look at a few examples to demonstrate how this works.
In the following series of statements, I declare the @UpdateOutput1 variable, delete a row from the Books table,
and view the results of that process:
-- declare @UpdateOutput1 table variable

DECLARE @UpdateOutput1 table


(
OldBookID int,
NewBookID int,
BookTitle nvarchar(50),
OldModifiedDate datetime,
NewModifiedDate datetime
);
-- update row in Books table
UPDATE Books
SET
BookID = 105,
ModifiedDate = GETDATE()
OUTPUT
DELETED.BookID,
INSERTED.BookID,
INSERTED.BookTitle,
DELETED.ModifiedDate,
INSERTED.ModifiedDate
INTO @UpdateOutput1
WHERE BookTitle = 'Mrs. Dalloway';
-- view updated row in Books table
SELECT * FROM Books;
-- view output row in @UpdateOutput1 variable
SELECT * FROM @UpdateOutput1;

Notice first that the @UpdateOutput1 variable is a bit different from the table variables defined in earlier
examples. In this case, were capturing the values associated with the update operationsthose values that are
deleted and those that are inserted. Because I plan to update the BookID and ModifiedDate columns, I want to
preserve both the old and new values, along with the BookTitle column, which is not being updated.
After I create the @UpdateOutput1 variable, I define the UPDATE statement. Note that Ive inserted an OUTPUT
clause between the SET and WHERE clauses and that the OUTPUT subclause contains both DELETED and
INSERTED column prefixes, separated by commas. I include a DELETED and INSERTED column prefix for each
of the two columns Im updating and an INSERTED column prefix for the column that will remain unchanged. The
INTO subclause specifies the table variable, as you saw in previous examples.
When you run the first SELECT statement, the Books table should reflect the modified values, as shown in the
following results:
BookID
101
102
103
105

BookTitle
One Hundred Years of Solitude
Pride and Prejudice
The Great Gatsby
Mrs. Dalloway

ModifiedDate
2010-07-17 12:34:16.467
2010-07-17 12:34:24.580
2010-07-17 12:34:38.330
2010-07-17 12:36:10.237

As you can see, the BookID and ModifiedDate values have changed for the Mrs. Dalloway row. The second
SELECT statement shows the specifics of the modifications, as theyve been returned to the @UpdateOutput1
variable:
OldBookID
104

NewBookID
105

BookTitle
Mrs. Dalloway

OldModifiedDate
2010-07-17 13:09:10.580

NewModifiedDate
2010-07-17 13:09:39.683

When you specify the column prefixes in your OUTPUT subclause, you can create an expression that uses the
prefixes to calculate a value. For instance, in the following example, I calculate the difference between the old and
new ModifiedDate values:
-- declare @UpdateOutput1 table variable
DECLARE @UpdateOutput2 table
(
OldBookID int,
NewBookID int,
BookTitle nvarchar(50),
OldModifiedDate datetime,
NewModifiedDate datetime,
DiffInSeconds int
);
-- update row in Books table
UPDATE Books
SET
BookID = BookID + 1,
ModifiedDate = GETDATE()
OUTPUT
DELETED.BookID,
INSERTED.BookID,
INSERTED.BookTitle,
DELETED.ModifiedDate,
INSERTED.ModifiedDate,
DATEDIFF(ss, DELETED.ModifiedDate, INSERTED.ModifiedDate)
INTO @UpdateOutput2
WHERE BookTitle = 'Mrs. Dalloway';
-- view updated row in Books table
SELECT * FROM Books;
-- view output row in @UpdateOutput2 variable
SELECT * FROM @UpdateOutput2;

The example is different from the previous example in a couple ways. First, the table variable includes an extra
field (DiffInSeconds) to show the difference in seconds between the two modified dates. Next, the OUTPUT
subclause includes an additional column definition in which the DATEDIFF function is used to calculate the
difference between the deleted and inserted modified dates.
Once again, the first SELECT statement returns the content of the Books table, after the last row has been
modified:

BookID
101
102
103
106

BookTitle
One Hundred Years of Solitude
Pride and Prejudice
The Great Gatsby
Mrs. Dalloway

ModifiedDate
2010-07-17 12:34:16.467
2010-07-17 12:34:24.580
2010-07-17 12:34:38.330
2010-07-17 12:36:10.237

The results from the second SELECT statement show the DiffInSeconds column, which reflects the difference in
seconds between the two updates:
OldBookID
105

NewBookID
106

BookTitle
Mrs.
Dalloway

OldModifiedDate
2010-07-17
13:20:34.787

NewModifiedDate
2010-07-17
13:21:26.753

DiffInSeconds
52

Once you understand how to use the OUTPUT clause in an UPDATE statement, you can see that you have all
the elements necessary to add an OUTPUT clause to a DELETE statement, so lets look at how that works.

Using an OUTPUT Clause in a DELETE Statement


In a DELETE statement, you add the OUTPUT clause between the DELETE and WHERE clauses. The following
set of statements declare the @DeleteOutput1 table variable, delete a row from the Books table, and verifies the
deletion:
-- declare @DeleteOutput1 table variable
DECLARE @DeleteOutput1 table
(
BookID int,
BookTitle nvarchar(50),
ModifiedDate datetime
);
-- delete row in Books table
DELETE Books
OUTPUT DELETED.*
INTO @DeleteOutput1
WHERE BookID = 106;
-- view updated row in Books table
SELECT * FROM Books;
-- view output row in @DeleteOutput1 variable
SELECT * FROM @DeleteOutput1;

Theres nothing new about the @DeleteOutput1 table variable. I declare it with the same table structure used in
the Books table, as you saw in previous examples.
In the DELETE statement, the OUTPUT subclause uses the asterisk with the DELETED column prefix to indicate
that all columns should be outputted to the @DeleteOutput1 variable. The first SELECT statement returns the
following results:
BookID
101

BookTitle
One Hundred Years of Solitude

ModifiedDate
2010-07-17 13:09:10.573

102
103

Pride and Prejudice


The Great Gatsby

2010-07-17 13:09:10.577
2010-07-17 13:09:10.580

As you can see, the last row has been deleted from the table. The second SELECT statement confirms that the
deleted row has been returned to the @DeleteOutput1 variable:
BookID
106

BookTitle
Mrs. Dalloway

ModifiedDate
2010-07-17 13:21:26.753

The next example is similar to the last except that I specify the column prefixes in the OUTPUT subclause, rather
than using as asterisk:
-- declare @DeleteOutput2 table variable
DECLARE @DeleteOutput2 table
(
BookID int,
BookTitle nvarchar(50),
ModifiedDate datetime
);
-- delete row in Books table
DELETE Books
OUTPUT
DELETED.BookID,
DELETED.BookTitle,
DELETED.ModifiedDate
INTO @DeleteOutput2
WHERE BookID = 103;
-- view updated row in Books table
SELECT * FROM Books;
-- view output row in @DeleteOutput2 variable
SELECT * FROM @DeleteOutput2;

As the example shows, an OUTPUT clause in a DELETE statement is similar to one in an INSERT or UPDATE
statement. The key is to ensure that youve specified your column prefixes correctly. Not surprisingly, the first
SELECT statement verifies that another row has been deleted from the Books table:
BookID
101
102

BookTitle
One Hundred Years of Solitude
Pride and Prejudice

ModifiedDate
2010-07-17 13:09:10.573
2010-07-17 13:09:10.577

The deleted row has been deleted has been added to the @DeleteOutput2 variable, as shown in the results from
the second SELECT statement:
BookID
103

BookTitle
The Great Gatsby

ModifiedDate
2010-07-17 13:09:10.580

Youre not limited to one OUTPUT clause in a DML statement. For instance, there might be times when you want
to output the results to the processing application as well as to a variable or table. In such a case, you can include

multiple clauses. The following example is similar to the previous one, except that Ive added another OUTPUT
clause:
-- declare @DeleteOutput2 table variable
DECLARE @DeleteOutput3 table
(
BookID int,
BookTitle nvarchar(50),
ModifiedDate datetime
);
-- delete row in Books table
DELETE Books
OUTPUT
DELETED.BookID,
DELETED.BookTitle,
DELETED.ModifiedDate
INTO @DeleteOutput3
OUTPUT
DELETED.BookID,
DELETED.BookTitle
WHERE BookID = 103;
-- view updated row in Books table
SELECT * FROM Books;
-- view output row in @DeleteOutput3 variable
SELECT * FROM @DeleteOutput3;

The first OUTPUT clause is the same as the last example. However, the second OUTPUT clause is new. Notice
that the clause does not include an INTO subclause. This indicates that the outputted data will be sent to the
calling application. Also notice that the set of column prefixes is different in the two OUTPUT clauses. The
prefixes can be the same or different.
If you were to run the statements in this example after running the previous example, no data will be sent to the
variable or to the calling application because that row has already been deleted. However, if the row exists, the
appropriate data would be sent to both targets.

Using an OUTPUT Clause in a MERGE Statement


When working in SQL Server 2008, you can add an OUTPUT clause to a MERGE statement. The process is
similar to adding the clause to an UPDATE statement; you use both the INSERTED and DELETED column
prefixes.
Lets look at an example that demonstrates how this work. First, however, we must create a second table to
support the MERGE statement. The following script creates the Book2 table and populates it with two rows:
-- create second table and populate
IF OBJECT_ID ('Books2', 'U') IS NOT NULL

DROP TABLE dbo.Books2;


CREATE TABLE dbo.Books2
(
BookID int NOT NULL PRIMARY KEY,
BookTitle nvarchar(50) NOT NULL,
ModifiedDate datetime NOT NULL
);
INSERT INTO Books2
VALUES(101, '100 Years of Solitude', GETDATE());
INSERT INTO Books2
VALUES(102, 'Pride & Prejudice', GETDATE());
Once weve created the Books2 table, we can try a MERGE statement. In the
following example, I declare the @MergeOutput1 variable, merge data from the Books table
into the Books2 table, and view the results:
-- declare @MergeOutput1 table variable
DECLARE @MergeOutput1 table
(
ActionType nvarchar(10),
BookID int,
OldBookTitle nvarchar(50),
NewBookTitle nvarchar(50),
ModifiedDate datetime
);
-- use MERGE statement to perform update on Book2
MERGE Books2 AS b2
USING Books AS b1
ON (b2.BookID = b1.BookID)
WHEN MATCHED
THEN UPDATE
SET b2.BookTitle = b1.BookTitle
OUTPUT
$action,
INSERTED.BookID,
DELETED.BookTitle,
INSERTED.BookTitle,
INSERTED.ModifiedDate
INTO @MergeOutput1;
-- view Books table
SELECT * FROM Books;
-- view updated rows in Books2 table
SELECT * FROM Books2;
-- view output rows in @MergeOutput1 variable
SELECT * FROM @MergeOutput1;

For the @MergeOutput1 variable, I include columns for both the old and new titles. I also include a column
named ActionType. The column reflects the type of modification that is performed on the table when the rows are
merged.
Now lets look at the OUTPUT subclause. Notice that, in addition to the INSERTED and DELETED column
prefixes, I include the $action variable. The built-in parameter returns one of three nvarchar(10) valuesINSERT,
UPDATE, or DELETEand is available only to the MERGE statement. The value returned depends on the action
performed on the row.
The first two SELECT statements return the data in the Books and Books2 tables after the MERGE. The results,
which are identical, are shown in the following table:
BookID
101
102

BookTitle
One Hundred Years of Solitude
Pride and Prejudice

ModifiedDate
2010-07-17 13:09:10.573
2010-07-17 13:09:10.577

The results from the third SELECT statement, which calls the @MergeOutput1 variable, reflect the modifications
made to the Books2 table:
ActionType
UPDATE
UPDATE

BookID
101
102

OldBookTitle
100 Years of Solitude
Pride & Prejudice

NewBookTitle
One Hundred Years of Solitude
Pride and Prejudice

ModifiedDate
2010-07-17 15:54:11.580
2010-07-17 15:54:11.580

Whether you use want to output your data modifications to a processing application, a table, or a variable, the
OUTPUT clause is an easy, effective way to capture that data. By using the clause, you can reduce the number of
calls to the database, eliminate the need for auditing triggers, archive modified data, or pass data on to an
application for further processing. The OUTPUT clause is a flexible and easy to useand it offers big benefits.
For further information on the clause, see the topic OUTPUT Clause (Transact-SQL) in SQL Server Books
Online.

Вам также может понравиться