Nielsen c21.tex V4 - 07/23/2009 4:48pm Page 562 Part IV Developing with SQL Server ■ An application can submit a T-SQL batch using ADO or ODBC for execution. ■ A SQL script may be executed by running the SQLCMD command-line utility and passing the SQL script file as a parameter. ■ The SQLCMD utility has several parameters and may be configured to meet nearly any command-line need. T-SQL formatting Throughout this book, T-SQL code has been formatted for readability; this section specifies the details of formatting T-SQL code. Statement termination The ANSI SQL standard is to place a semicolon (;) at the end of each command in order to terminate it. When programming T-SQL, the semicolon is optional. Most other database products (including Access) do require semicolons. There are a few rules about using the semicolon: ■ Don’t place one after an END TRY. ■ Don’t place one after an IF or WHILE condition. ■ You must place one before any CTE. ■ A statement terminator is required following a MERGE command. Best Practice A s a best practice and for improved readability, I recommend using the semicolon. In future versions of SQL Server this may become a requirement, so making the change now may pay off later. Line continuation T-SQL commands, by their nature, tend to be long. I have written production queries with multiple joins and subqueries that were a few pages long. I like that T-SQL ignores spaces and end-of-line returns. This smart feature means that long lines can be continued without a special line-continuation character, which makes T-SQL code significantly more readable. Comments T-SQL accepts both simple comments and bracketed comments within the same batch. The simple comment begins with two hyphens and concludes with an end-of-line: This is a simple comment Simple comments may be embedded within a single SQL command: 562 www.getcoolebook.com Nielsen c21.tex V4 - 07/23/2009 4:48pm Page 563 Programming with T-SQL 21 SELECT FirstName, LastName selects the columns FROM Persons the source table WHERE LastName LIKE ‘Hal%’; the row restriction Management Studio’s Query Editor can apply or remove simple comments to all selected lines. Select either Edit ➪ Advanced ➪ Comment Selection (Ctrl+K, Ctrl+C) or Edit ➪ Advanced ➪ Uncomment Selection (Ctrl+K, Ctrl+U), respectively. Bracketed comments begin with /* and conclude with */. These comments are useful for commenting out a block of lines such as a code header or large test query: /* Order table Insert Trigger Paul Nielsen ver 1.0 July 21, 2006 Logic: etc. ver 1.1: July 31, 2006, added xyz */ A benefit of bracketed comments is that a large multi-line query within the comments may be selected and executed without altering the comments. A GO batch terminator inside a bracketed comment block will terminate the batch, and the statements after the GO will be executed as a new non-commented batch. Variables Every language requires variables to temporarily store values in memory. T-SQL variables are created with the DECLARE command. The DECLARE command is followed by the variable name and data type. The available data types are similar to those used to create tables, with the addition of the table and cursor. The deprecated text, ntext,andimage data types are only available for table columns, and not for variables. Multiple comma-separated variables can be declared with a single DECLARE command. Variable default and scope The scope, or application and duration, of the variable extends only to the current batch. Newly declared variables default to NULL and must be initialized if you want them to have a value in an expression. Remember that null added with a value yields null. New for SQL Server 2008 is the ability to initialize a variable to a value while declaring it, which saves an extra line of code: DECLARE @x INT =0; The following script creates two test variables and demonstrates their initial value and scope. The entire script is a single execution, even though it’s technically two batches (separated by a GO), so the results of the three SELECT statements appear at the conclusion of the script: 563 www.getcoolebook.com Nielsen c21.tex V4 - 07/23/2009 4:48pm Page 564 Part IV Developing with SQL Server DECLARE @Test INT , @TestTwo NVARCHAR(25); SELECT @Test, @TestTwo; SET @Test = 1; SET @TestTwo = ‘a value’; SELECT @Test, @TestTwo ; GO SELECT @Test AS BatchTwo, @TestTwo; Result of the entire script: NULL NULL (1 row(s) affected) 1 a value (1 row(s) affected) Msg 137, Level 15, State 2, Line 2 Must declare the scalar variable "@Test". The first SELECT returns two NULL values. After the variables have been initialized, they properly return the sample values. When the batch concludes (due to the GO terminator), so do the variables. Error message 137 is the result of the final SELECT statement. Variables are local in scope and do not extend to other batches or called stored procedures. Using the set and select commands Both the SET command and the SELECT command can assign the value of an expression to a variable. The main difference between the two is that a SELECT can retrieve data from a data source (e.g., table, subquery, or view) and can include the other SELECT clauses as well (e.g., FROM, WHERE), whereas a SET is limited to retrieving data from expressions. Both SET and SELECT can include functions. Use the simpler SET command when you only need to assign a function result or constant to a variable and don’t need the Query Optimizer to consider a data source. A detailed exception to the preceding paragraph is when a SET command uses a scalar subquery that accesses a data source. This is a best practice if you want to ensure that the variable is set to NULL if no rows qualify, and that you get an error if more than one row qualifies. Of course, a SELECT statement may retrieve multiple columns. Each column may be assigned to a vari- able. If the SELECT statement retrieves multiple rows, then the values from the last row are stored in the variables. No error will be reported. 564 www.getcoolebook.com Nielsen c21.tex V4 - 07/23/2009 4:48pm Page 565 Programming with T-SQL 21 The following SQL batch creates two variables and initializes one of them. The SELECT statement will retrieve 32 rows, ordered by PersonID.ThePersonID and the LastName of the last person returned by the SELECT will be stored in the variables: USE Family; DECLARE @TempID INT, @TempLastName VARCHAR(25); SET @TempID = 99; SELECT @TempID = PersonID, @TempLastName = LastName FROM Person ORDER BY PersonID; SELECT @TempID, @TempLastName; Result: Campbell The preceding code demonstrates a common coding mistake. Never use a SELECT to popu- late a variable unless you’re sure that it will return only a single row. If no rows are returned from the SELECT statement, the SELECT does not affect the variables. In the following query, there is no person with a PersonID of 100,sotheSELECT statement does not affect either variable: DECLARE @TempID INT, @TempLastName VARCHAR(25); SET @TempID = 99; SELECT @TempID = PersonID, @TempLastName = LastName FROM Person WHERE PersonID = 100 ORDER BY PersonID; SELECT @TempID, @TempLastName; The final SELECT statement reports the value of @TempID and @TempLastName, and indeed they are still 99 and NULL, respectively. The first SELECT did not alter its value: NULL Incrementing variables T-SQL finally has the increment variable feature, which saves a few keystrokes when coding and certainly looks cleaner and more modern. The basic idea is that an operation and equals sign will perform that function on the variable. For example, the code 565 www.getcoolebook.com Nielsen c21.tex V4 - 07/23/2009 4:48pm Page 566 Part IV Developing with SQL Server SET @x += 5; is the logical equivalent of SET@x=@x+5 The next short script walks through addition, subtraction, and multiplication using the new variable increment feature: DECLARE @x INT = 1 SET @x += 5 SELECT @x SET @x -=3 SELECT @x SET @x *= 2 SELECT @x Result (of whole batch): 6 3 6 Conditional select Because the SELECT statement includes a WHERE clause, the following syntax works well, although those not familiar with it may be confused: SELECT @Variable = expression WHERE BooleanExpression; The WHERE clause functions as a conditional IF statement. If the Boolean expression is true, then the SELECT takes place. If not, the SELECT is performed but the @Variable is not altered in any way because the SELECT command has no effect. Using variables within SQL queries One of my favorite features of T-SQL is that variables may be used with SQL queries without having to build any complex dynamic SQL strings to concatenate the variables into the code. Dynamic SQL still has its place, but the single value can simply be modified with a variable. Anywhere an expression can be used within a SQL query, a variable may be used in its place. The following code demonstrates using a variable in a WHERE clause: 566 www.getcoolebook.com Nielsen c21.tex V4 - 07/23/2009 4:48pm Page 567 Programming with T-SQL 21 USE OBXKites; DECLARE @ProductCode CHAR(10); SET @Code = ‘1001’; SELECT ProductName FROM Product WHERE Code = @ProductCode; Result: Name Basic Box Kite 21 inch Debugging T-SQL W hen a syntax error is found, the Query Editor will display the error and the line number of the error within the batch. Double-clicking on the error message will place the cursor near the offending line. Often the error won’t occur at the exact word that is reported as the error. The error location reported simply reflects how far SQL Server’s parser got before it detected the error. Usually the actual error is somewhere just before or after the reported error. Nevertheless, the error messages are generally close. SQL Server 2008 brings back the T-SQL debugger, which is great for debugging variables, and flow of control, but it can’t help when debugging a query. Most of the debugging I need to do involves checking the contents of a temp table or table variable to see the output of a query. Inserting a SELECT command and running the batch up to that SELECT command works well for my purposes. My other debugging technique is to double-check the source data. Seriously. More than half the time when I don’t get what I expect from a T-SQL query or batch, the problem is not the code, but the data. SQL is basically asking a question of the data and returning the result. If the data isn’t what you thought it was, then neither will the answer be what you expected. This is why I’m such a stickler for unit testing using a small set of sample data. Multiple assignment variables A multiple assignment variable, sometimes called an aggregate concatenation, is a fascinating method that appends a variable to itself using a SELECT statement and a subquery. This section demonstrates a real-world use of multiple assignment variables, but because it’s an unusual use of the SELECT statement, here it is in its basic form: SELECT @variable = @variable + d.column FROM datasource; 567 www.getcoolebook.com Nielsen c21.tex V4 - 07/23/2009 4:48pm Page 568 Part IV Developing with SQL Server Each row from the derived table is appended to the variable, changing the vertical column in the under- lying table into a horizontal list. This type of data retrieval is quite common. Often a vertical list of values is better reported as a single comma-delimited horizontal list than as a subreport or another subheading level several inches long. A short horizontal list is more readable and saves space. The following example builds a list of departments in the AdventureWorks2008 sample database from the HumanResources.Department table: USE AdventureWorks2008; Declare @MAV VARCHAR(max) SELECT @MAV = Coalesce(@MAV + ‘, ’ + Name, Name) FROM (select name from HumanResources.Department) D order by name Select @MAV Result: Changed Name, Document Control, Engineering, Executive, Facilities and Maintenance, Finance, Human Resources, Information Services, Marketing, Production, Production Control, Purchasing, Quality Assurance, Research and Development, Sales, Shipping and Receiving The problem with multiple assignment variables is that Microsoft is vague about their behavior. The order of the denormalized data isn’t guaranteed, but queries do seem to respond to the ORDER BY clause. It’s not documented in BOL but it has been documented in MSKB article Q287515. It performs very well, but I’m cautious about using it when the result is order dependent. The multiple assignment variable may be used with an UPDATE command to merge multiple rows during the update. For more details, turn back to Chapter 12, ‘‘Aggregating Data.’’ An alternate method of denormalizing a list is the XML PATH method: Select [text()] = Name + ‘,’ FROM (select distinct name from HumanResources.Department) D order by Name FOR XML PATH(’’) For more details on XML, see Chapter 18, ‘‘Manipulating XML Data.’’ Procedural Flow At first glance, it would appear that T-SQL is weak in procedural-flow options. While it’s less rich than some other languages, it suffices. The data-handling Boolean extensions — such as EXISTS, IN,and CASE — offset the limitations of IF and WHILE. 568 www.getcoolebook.com Nielsen c21.tex V4 - 07/23/2009 4:48pm Page 569 Programming with T-SQL 21 If This is your grandfather’s IF.TheT-SQLIF command determines the execution of only the next single statement — one IF, one command. In addition, there’s no THEN and no END IF command to termi- nate the IF block: IF Condition Statement; In the following script, the IF condition should return a false, preventing the next command from executing: IF 1 = 0 PRINT ‘Line One’; PRINT ‘Line Two’; Result: Line Two The IF statement is not followed by a semicolon; in fact, a semicolon will cause an error. That’s because the IF statement is actually a prefix for the following statement; the two are compiled as a single statement. Begin/end An IF command that can control only a single command is less than useful. However, a BEGIN/END block can make multiple commands appear to the IF command as the next single command: IF Condition Begin; Multiple lines; End; I confess: Early one dreary morning a couple of years ago, I spent an hour trying to debug a stored pro- cedure that always raised the same error no matter what I tried, only to realize that I had omitted the BEGIN and END, causing the RAISERROR to execute regardless of the actual error condition. It’s an easy mistake to make. If exists() While the IF command may seem limited, the condition clause can include several powerful SQL features similar to a WHERE clause, such as IF EXISTS() and IF IN(). The IF EXISTS() structure uses the presence of any rows returned from a SQL SELECT statement as a condition. Because it looks for any row, the SELECT statement should select all columns (*). This method is faster than checking an @@rowcount >0 condition, because the total number of rows isn’t required. As soon as a single row satisfies the IF EXISTS(), the query can move on. 569 www.getcoolebook.com Nielsen c21.tex V4 - 07/23/2009 4:48pm Page 570 Part IV Developing with SQL Server The following example script uses the IF EXISTS() technique to process orders only if any open orders exist: USE OBXKITES; IF EXISTS(SELECT * FROM [ORDER] WHERE Closed = 0) BEGIN; PRINT ‘Process Orders’; END; There is effectively no difference between SELECT * or selecting a column. However, selecting all columns enables SQL Server to select the best column from an index and might, in some situations, be slightly faster. If/else The optional ELSE command defines code that is executed only when the IF condition is false. Like IF, ELSE controls only the next single command or BEGIN/END block: IF Condition Single line or begin/end block of code; ELSE Single line or begin/end block of code; While The WHILE command is used to loop through code while a condition is still true. Just like the IF com- mand, the WHILE command determines the execution of only the following single T-SQL command. To control a full block of commands, BEGIN/END is used. Some looping methods differ in the timing of the conditional test. The T-SQL WHILE works in the following order: 1. The WHILE command tests the condition. If the condition is true, WHILE executes the follow- ing command or block of code; if not, it skips the following command or block of code and moves on. 2. Once the following command or block of code is complete, flow of control is returned to the WHILE command. The following short script demonstrates using the WHILE command to perform a loop: DECLARE @Temp INT; SET @Temp = 0; WHILE @Temp < 3 BEGIN; PRINT ‘tested condition’ + STR(@Temp); SET @Temp = @Temp + 1; END; 570 www.getcoolebook.com Nielsen c21.tex V4 - 07/23/2009 4:48pm Page 571 Programming with T-SQL 21 Result: tested condition 0 tested condition 1 tested condition 2 The CONTINUE and BREAK commands enhance the WHILE command for more complex loops. The CONTINUE command immediately jumps back to the WHILE command. The condition is tested as normal. The BREAK command immediately exits the loop and continues with the script as if the WHILE condition were false. The following pseudocode (not intended to actually run) demonstrates the BREAK command: CREATE PROCEDURE MyLife() AS WHILE Not @@Eyes2blurry = 1 BEGIN; EXEC Eat; INSERT INTO Book(Words) FROM Brain(Words) WHERE Brain.Thoughts IN(’Make sense’, ‘Good Code’, ‘Best Practice’); IF @SciFi_Eureka = ‘On’ BREAK; END; Goto Before you associate the T-SQL GOTO command with bad memories of 1970s-style spaghetti-BASIC, this GOTO command is limited to jumping to a label within the same batch or procedure, and is rarely used for anything other than jumping to an error handler at the close of the batch or procedure. The label is created by placing a colon after the label name: LabelName: The following code sample uses the GOTO command to branch to the ErrorHandler: label, bypassing the ‘more code’: GOTO ErrorHandler; Print ‘more code’; ErrorHandler: Print ‘Logging the error’; Result: Logging the error 571 www.getcoolebook.com . 07/23/2009 4:48pm Page 562 Part IV Developing with SQL Server ■ An application can submit a T -SQL batch using ADO or ODBC for execution. ■ A SQL script may be executed by running the SQLCMD command-line. passing the SQL script file as a parameter. ■ The SQLCMD utility has several parameters and may be configured to meet nearly any command-line need. T -SQL formatting Throughout this book, T -SQL code. effect. Using variables within SQL queries One of my favorite features of T -SQL is that variables may be used with SQL queries without having to build any complex dynamic SQL strings to concatenate