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.. New
Trang 1■ 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 anEND TRY
■ Don’t place one after anIForWHILEcondition
■ You must place one before any CTE
■ A statement terminator is required following aMERGEcommand
Best Practice
As 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:
Trang 2SELECT 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 theDECLAREcommand TheDECLAREcommand 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 thetableand
cursor The deprecatedtext,ntext, andimagedata types are only available for table columns, and
not for variables Multiple comma-separated variables can be declared with a singleDECLAREcommand
Variable default and scope
The scope, or application and duration, of the variable extends only to the current batch Newly
declared variables default toNULLand 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 aGO), so the results
of the threeSELECTstatements appear at the conclusion of the script:
Trang 3DECLARE @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:
-
(1 row(s) affected)
-
(1 row(s) affected) Msg 137, Level 15, State 2, Line 2 Must declare the scalar variable "@Test"
The firstSELECTreturns twoNULLvalues After the variables have been initialized, they properly
return the sample values When the batch concludes (due to theGOterminator), so do the variables
Error message 137 is the result of the finalSELECTstatement
Variables are local in scope and do not extend to other batches or called stored procedures
Using the set and select commands
Both theSETcommand and theSELECTcommand can assign the value of an expression to a variable
The main difference between the two is that aSELECTcan retrieve data from a data source (e.g., table,
subquery, or view) and can include the otherSELECTclauses as well (e.g.,FROM, WHERE), whereas a
SETis limited to retrieving data from expressions BothSETandSELECTcan include functions Use
the simplerSETcommand 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 aSETcommand uses a scalar subquery that
accesses a data source This is a best practice if you want to ensure that the variable is set toNULLif no
rows qualify, and that you get an error if more than one row qualifies
Of course, aSELECTstatement may retrieve multiple columns Each column may be assigned to a
vari-able If theSELECTstatement retrieves multiple rows, then the values from the last row are stored in
the variables No error will be reported
Trang 4The following SQL batch creates two variables and initializes one of them TheSELECTstatement will
retrieve 32 rows, ordered byPersonID ThePersonIDand theLastNameof the last person returned
by theSELECTwill 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 theSELECTstatement, theSELECTdoes not affect the variables In the
following query, there is no person with a PersonIDof 100, so theSELECTstatement 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 finalSELECTstatement reports the value of @TempIDand@TempLastName, and indeed they are
still99andNULL, respectively The firstSELECTdid 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
Trang 5SET @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 theSELECTstatement includes aWHEREclause, the following syntax works well, although
those not familiar with it may be confused:
SELECT @Variable = expression WHERE BooleanExpression;
TheWHEREclause functions as a conditionalIFstatement If the Boolean expression is true, then the
SELECTtakes place If not, theSELECTis performed but the@Variableis not altered in any way
because theSELECTcommand 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 aWHEREclause:
Trang 6USE 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
When 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 aSELECTstatement and a subquery
This section demonstrates a real-world use of multiple assignment variables, but because it’s an unusual
use of theSELECTstatement, here it is in its basic form:
SELECT @variable = @variable + d.column
FROM datasource;
Trang 7Each 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 theAdventureWorks2008sample database
from theHumanResources.Departmenttable:
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 theORDER 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 asEXISTS,IN, and
CASE— offset the limitations of IFandWHILE
Trang 8This is your grandfather’sIF The T-SQLIFcommand determines the execution of only the next single
statement — oneIF, one command In addition, there’s noTHENand noEND IFcommand to
termi-nate theIFblock:
IF Condition
Statement;
In the following script, theIFcondition 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
AnIFcommand that can control only a single command is less than useful However, aBEGIN/END
block can make multiple commands appear to theIFcommand 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
BEGINandEND, causing theRAISERRORto execute regardless of the actual error condition It’s an easy
mistake to make
If exists()
While theIFcommand may seem limited, the condition clause can include several powerful SQL
features similar to aWHEREclause, such as IF EXISTS()andIF IN()
TheIF EXISTS()structure uses the presence of any rows returned from a SQLSELECTstatement
as a condition Because it looks for any row, theSELECTstatement should select all columns (*) This
method is faster than checking an@@rowcount >0condition, because the total number of rows isn’t
required As soon as a single row satisfies theIF EXISTS(), the query can move on
Trang 9The following example script uses theIF 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 betweenSELECT *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 optionalELSEcommand defines code that is executed only when theIFcondition is false Like
IF,ELSEcontrols only the next single command orBEGIN/ENDblock:
IF Condition
Single line or begin/end block of code;
ELSE
Single line or begin/end block of code;
While
TheWHILEcommand is used to loop through code while a condition is still true Just like theIF
com-mand, theWHILEcommand determines the execution of only the following single T-SQL command To
control a full block of commands,BEGIN/ENDis used
Some looping methods differ in the timing of the conditional test The T-SQLWHILEworks in the
following order:
1 TheWHILEcommand tests the condition If the condition is true,WHILEexecutes the follow-ing command or block of code; if not, it skips the followfollow-ing 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
WHILEcommand
The following short script demonstrates using theWHILEcommand to perform a loop:
DECLARE @Temp INT;
SET @Temp = 0;
WHILE @Temp < 3 BEGIN;
PRINT ‘tested condition’ + STR(@Temp);
SET @Temp = @Temp + 1;
END;
Trang 10tested condition 0
tested condition 1
tested condition 2
TheCONTINUEandBREAKcommands enhance theWHILEcommand for more complex loops The
CONTINUEcommand immediately jumps back to theWHILEcommand The condition is tested as
normal
TheBREAKcommand immediately exits the loop and continues with the script as if theWHILE
condition were false The following pseudocode (not intended to actually run) demonstrates theBREAK
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-SQLGOTOcommand with bad memories of 1970s-style spaghetti-BASIC, this
GOTOcommand 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 theGOTOcommand to branch to theErrorHandler:label, bypassing
the‘more code’:
GOTO ErrorHandler;
Print ‘more code’;
ErrorHandler:
Print ‘Logging the error’;
Result:
Logging the error