Editor's note: This is the fourth and final part of a series on new features in T-SQL in SQL Server 2012. Check out the other parts of this series to learn more about the joy of data values, T-SQL functions and T-SQL formatting.
There might be times when you want to access data from a previous row in a result set or from a row that follows, without having to create a self-join. That's where the LAG and LEAD functions come in. The LAG function lets you retrieve data from a previous row, and the LEAD function lets you pull data from a row that follows. You can then compare that data to values in the current row.
LAG and LEAD
Let's look at an example to demonstrate how this works. The following SELECT statement retrieves data from the SalesOrderHeader table and groups the information based on year:
SELECT YEAR(OrderDate) AS OrderYear,
SUM(SubTotal) AS Subtotal,
LAG(SUM(SubTotal), 1, 0) OVER (ORDER BY YEAR(OrderDate)) AS LastYear,
LEAD(SUM(SubTotal), 1, 0) OVER (ORDER BY YEAR(OrderDate)) AS NextYear
GROUP BY YEAR(OrderDate)
ORDER BY YEAR(OrderDate);
For each year, the T-SQL statements retrieve the total of Subtotal values, plus the totals from the previous year and the year to follow. To retrieve the totals from the previous year, the statement uses the LAG function in the third column expression. The function takes three arguments. The first is an expression that returns a scalar value, in this case, a total of the SubTotal values. The second argument specifies the number of rows to go back. Because 1 is used, the data will be taken from the previous row. The final argument specifies the value to return if the preceding row returns a NULL. In this case, 0 will be used.
For more on T-SQL statements
T-SQL statements in subqueries- the basics
T-SQL statements and performance issues
The LAG function also requires an OVER clause to determine the order of the data before the function is applied. This ensures that you pull your value from the correct row.
To retrieve data from the row that follows the current row, the SELECT statement uses the LEAD function, which works just like the LAG function except it moves forward in the result set, rather than backward. Because these two functions are used, the SELECT statement returns the results shown in the following table:
Each row includes the total sales for the current year, the previous year, and the year to follow. For example, in the first row, the LastYear value is 0.00. Because there are no previous years, the total for that year is NULL. As a result, the LAG function converts the NULL to 0.00. The same holds true for the last row. Because it represents the last year for which results are available, the NextYear value is 0.00, which was converted from NULL.
Before SQL Server 2012, the EXECUTE statement's WITH clause supported only the RECOMPILE option. However, the clause now also supports the RESULTS SETS option, which helps you better control the result sets a stored procedure returns. One of the things you can do with that option is to redefine the result set definition, without having to change the stored procedure itself.
For example, the AdventureWorks2012 database includes the stored procedure uspGetEmployeeManagers. The following table shows the columns and their data types that procedure returned. [The Name data type is a user-defined type based on the NVARCHAR(50) built-in data type.]
By using the RESULT SETS option, you can change the column name, data type (if an implicit conversion is supported), collation, and nullability. For example, suppose you want to shorten the column names the stored procedure returned and change a couple data types. You can include the RESULT SETS option when you run the EXECUTE statement, as shown in the following example:
EXECUTE uspGetEmployeeManagers 100
WITH RESULT SETS
When you include a result set definition in your WITH clause, you must include both the column names and data types. The collation and nullability are optional. In this case, I've changed the columns names and several of the data types. The following table shows the results returned by the stored procedure.
As you can see, the column names are now different from those defined in the stored procedure. (The change in data types has no visible effect on the result set.) You could have just as easily provided a different set of names and data types. But again, those data types must support implicit conversions from the original types.
In the past, when you wanted to return an error message in a TRY…CATCH construction, you would use RAISEERROR to generate that message, but Transact-SQL now supports the THROW statement, which you can use in the CATCH block to re-throw an exception while preserving the original message. To do so, you simply specify the THROW keyword without any parameters. The following example demonstrates how to include a THROW statement in your CATCH block:
SELECT CONVERT(INT, 'books');
The THROW statement re-throws the original error caught by the CATCH block so the error can be handled at the application level. In the example above, the SELECT statement fails because the string value cannot be converted. As a result, the THROW statement returns the following message:
Msg 245, Level 16, State 1, Line 2
Conversion failed when converting the varchar value 'books' to data type int.
The THROW statement also lets you specify parameters if you want to return a custom message. To do so, provide a message number, the message text and a state to associate with the message. You must use a message number outside of those with predefined messages attached to them. You can use any numerical value between 50000 and 2147483647. The following CATCH block includes a THROW statement that defines a custom message:
DECLARE @string VARCHAR(50) = 'books'
DECLARE @message VARCHAR(100) =
CONCAT('You cannot convert "', @string, '" to the INT data type.')
SELECT CONVERT(INT, @string);
THROW 55000, @message, 1;
The first parameter in the THROW statement is the value 55000. This is the message number, which falls within the acceptable range. The second parameter is the message itself, in this case, is passed to the statement via the @message variable. The third parameter is the value 1, which indicates the state to associate with the message. You can specify between 0 and 255. The THROW statement returns the following message:
Msg 55000, Level 16, State 1, Line 8
You cannot convert "books" to the INT data type.
As you can see, the results include the message and state numbers, plus the level and line numbers. In this case, that line number corresponds to the THROW statement, not the statement that actually produced the error. However, if you refer back to the preceding example, you'll see that the results accurately reflect the error line. This is because when you use the THROW statement without parameters, it merely re-throws the original error.
MOVING AHEAD WITH T-SQL
As you can see, SQL Server 2012 introduces many enhancements to Transact-SQL. You'll also find new system stored procedures and dynamic management views, along with plenty of other updates. Of course, there's a flip side to all these changes -- the deprecated features that will not be supported in future SQL Server versions. You should be just as aware of them as you are of anything new. But for those interested specifically in the latest Transact-SQL changes, the language elements described in these articles should provide a good overview of the many enhancements that are available now and should be available in the foreseeable future.