Tải bản đầy đủ (.pdf) (10 trang)

Hướng dẫn học Microsoft SQL Server 2008 part 72 pps

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (937.74 KB, 10 trang )

Nielsen c28.tex V4 - 07/23/2009 4:58pm Page 672
Part IV Developing with SQL Server
Summary
The data abstraction layer is a key component of your database architecture plan and it plays a major
role in determining the future extensibility and maintenance costs of the database. Even when it seems
that the cost of developing a data abstraction layer and refactoring the existing application code to
hit the data abstraction layer instead of tables might be prohibitively expensive, savvy IT or product
managers understand that in the long run it will save money — and their job.
672
www.getcoolebook.com
Nielsen c29.tex V4 - 07/23/2009 4:59pm Page 673
Dynamic SQL and
Code Generation
IN THIS CHAPTER
Executing dynamic SQL
Parameterized queries
The risk of SQL injection
Generating stored procedures
Alternatives to dynamic SQL
F
olks laugh when they hear that my favorite project is based on the
notion that T-SQL is a great language for code generation. Nordic (New
Object/Relational Design) is essentially a code-generation tool that uses
dynamic SQL to create tables, stored procedures, and views. T-SQL works rather
well for code generation, thank you.
The term dynamic SQL has a couple of differing definitions. Some say it describes
anySQLquerysubmittedbyaclientother than a stored procedure. That’s not
true. SQL submitted from the client is better known as ad-hoc SQL.
It’s more accurate to say that dynamic SQL describes any SQL DML statement
assembled dynamically at runtime as a string and then submitted.
Dynamic SQL is very useful for several tasks:


■ Multiple possible query criteria can be dynamically assembled into
custom
FROM, WHERE,andORDER BY clauses for flexible queries.
■ Code can respond to the schema of the database and generate appropri-
ate triggers, CRUD stored procedures, and views.
■ Dynamic code can auto-generate very consistent stored procedures.
However, note the following issues when developing dynamic SQL:
■ Dynamic SQL that includes user entries in
WHERE clauses can be open
to SQL injection attacks.
■ Poorly written dynamic SQL queries often include extra table references
and perform poorly.
■ T-SQL code that generates T-SQL code can be tricky to debug.
673
www.getcoolebook.com
Nielsen c29.tex V4 - 07/23/2009 4:59pm Page 674
Part IV Developing with SQL Server
Executing Dynamic SQL
The EXECUTE command, or EXEC for short, creates a new instance of the batch as if it were a batch
submitted from some client to the server. While the
EXECUTE command is normally used to call a
stored procedure, it can also be used to execute a T-SQL query or batch:
EXEC[UTE] (’T-SQL batch’);
For example, the following EXEC command executes a simple SELECT statement:
USE Family;
EXEC (’SELECT LastName FROM Person WHERE PersonID = 12;’);
Result:
LastName

Halloway

The security context of executing code should be considered when working with the EXECUTE
command. You can control which user account the Database Engine uses to validate permissions on
objects that are referenced by the module. The following code uses the
EXECUTE AS syntax to execute
the query as the user Joe:
Use OBXKites
EXECUTE AS ‘Joe’ select * from Products
Another aspect of the EXECUTE command is the capability to execute the code at a linked server,
instead of at the local server. The code is submitted to the linked server and the results are returned to
the local server:
EXECUTE (’Code’) AT MAUI/SYDNEY;
sp_executeSQL
Another method of executing dynamic SQL is to use the sp_executesql system stored procedure.
It offers greater compatibility with complex SQL queries than the straight
EXECUTE command. In
several situations I have found that the
EXECUTE command failed to execute the dynamic SQL, but
sp_executesql worked flawlessly:
EXEC sp_Executesql
‘T-SQL query’,
‘Parameters Definition’,
Parameter, Parameter ;
Parameterized queries
Sometimes it’s easier to create queries based on a number of parameters because it usually avoids the
need for concatenating strings. The query and the definition must be Unicode strings.
Parameters provide optimization. If the T-SQL query has the same parameters for each execution, then
these parameters can be passed to
sp_executesql so the SQL query plan can be stored, and future
executions will be optimized. The following example executes the same query from the
Person table

674
www.getcoolebook.com
Nielsen c29.tex V4 - 07/23/2009 4:59pm Page 675
Dynamic SQL and Code Generation 29
in the Family database, but this example uses parameters (the N before the parameters is necessary
because
sp_executesql requires Unicode strings):
EXEC sp_executesql
N’SELECT LastName
FROM Person
WHERE PersonID = @PersonSelect;’,
N’@PersonSelect INT’,
@PersonSelect = 12;
Result:
LastName

Halloway
Developing dynamic SQL code
Building a dynamic SQL string usually entails combining a SELECT column’s literal string with a
more fluid
FROM clause and WHERE clause. While any part of the query can be dynamic, normally the
SELECT @columns is not.
Once the SQL string is complete, the SQL statement is executed by means of the
sp_executesql
command. The example that follows builds both custom FROM and WHERE clauses based on the user’s
requirements.
Within the batch, the
NeedsAnd bit variable tracks the need for an And separator between WHERE
clause conditions. If the product category is specified, then the initial portion of the SELECT statement
includes the required joins to fetch the

ProductCategory table. The WHERE clause portion of the
batch examines each possible user criterion. If the user has specified a criterion for that column, then
the column, with its criterion, is added to the
@SQLWhere string.
Real-world dynamic SQL sometimes includes dozens of complex options. The following code listing uses
three possible columns for optional user criteria:
USE OBXKites;
DECLARE
@SQL NVARCHAR(1024),
@SQLWhere NVARCHAR(1024),
@NeedsAnd BIT,
User Parameters
@ProductName VARCHAR(50),
@ProductCode VARCHAR(10),
@ProductCategory VARCHAR(50);
Initialize Variables
SET @NeedsAnd = 0;
SET @SQLWhere = ‘’;
675
www.getcoolebook.com
Nielsen c29.tex V4 - 07/23/2009 4:59pm Page 676
Part IV Developing with SQL Server
Simulate User’s Requirements
SET @ProductName = NULL;
SET @ProductCode = 1001;
SET @ProductCategory = NULL;
Assembly Dynamic SQL
Set up initial SQL Select
IF @ProductCategory IS NULL
SET @SQL = ‘Select ProductName from Product’;

ELSE
SET @SQL = ‘Select ProductName
from Product
Join ProductCategory
on Product.ProductCategoryID
= ProductCategory.ProductCategoryID’;
Build the Dynamic Where Clause
IF @ProductName IS NOT NULL
BEGIN;
SET @SQLWhere = ‘ProductName = ’ + @ProductName;
SET @NeedsAnd = 1;
END;
IF @ProductCode IS NOT NULL
BEGIN;
IF @NeedsAnd = 1
SET @SQLWhere = @SQLWhere + ‘ and ’;
SET @SQLWhere = ‘Code=’+@ProductCode;
SET @NeedsAnd = 1;
END;
IF @ProductCategory IS NOT NULL
BEGIN;
IF @NeedsAnd = 1
SET @SQLWhere = @SQLWhere + ‘ and ’;
SET @SQLWhere = ‘ProductCategory = ’ + @ProductCategory;
SET @NeedsAnd = 1;
END;
Assemble the select and the where portions of the dynamic SQL
IF @SQLWhere <> ‘’
SET @SQL = @SQL + ‘ where ’ + @SQLWhere + ‘;’;
∼∼Use this for testing and debug use only.

PRINT @SQL;
EXEC sp_executesql @SQL
676
www.getcoolebook.com
Nielsen c29.tex V4 - 07/23/2009 4:59pm Page 677
Dynamic SQL and Code Generation 29
The results shown are both the printed text of the dynamic SQL and the data returned from the execu-
tion of the dynamic SQL statement:
Select ProductName from Product where Code = 1001;
Name

Basic Box Kite 21 inch
Code generation
The following example may seem a bit complex, but it’s a great real-world demonstration of T-SQL
code generation. It’s from the Nordic database and this is the piece of code that actually generates the
stored procedures and views for each class. This procedure is called every time a new class or attribute
is added or changed.
A few points in the code worth noting:

+ CHAR(13) + CHAR(10) are added to the generated code to make i t more readable.
■ The columns (attributes) are built up in the
@SQLStr variable first, then the dynamic FROM
clause is added.
■ A cursor is used to iterate through the columns and tables to build up the custom stored
procedure and view.
■ The
@SQLStr is assembled once and the @GenStr is modified to first create the view and
then create the stored procedure.
■ The dynamic SQL variables
@SQLStr and @GenStr are both declared as NVARCHAR(MAX).

■ Any custom columns are automatically enclosed in square brackets to avoid any syntax errors.
Here’s the code:
Gen Class
CREATE
alter
PROC dbo.GenClass
(@ClassName NVARCHAR(50))
AS
SET NoCount ON
EXEC IncVersion ‘Class Design’
DECLARE @GenStr NVARCHAR(MAX),
@SQLStr NVARCHAR(MAX),
@ClassStr NVARCHAR(MAX),
@CurrentClass CHAR(100),
677
www.getcoolebook.com
Nielsen c29.tex V4 - 07/23/2009 4:59pm Page 678
Part IV Developing with SQL Server
@CurrentAttrib CHAR(100)
SET @ClassName = REPLACE(@ClassName, ‘ ’, ‘’)
data from Object Table
SET @SQLStr = ‘SELECT o.ObjectID, o.ClassID, c.ClassName, o.StateID, ’
+ CHAR(13) + CHAR(10)
+ ‘ sc.ClassName + ’‘:’‘ + StateName as [State],’
+ CHAR(13) + CHAR(10)
+ ‘ ObjectCode as [Object:ObjectCode], Name1 as [Object:Name1],
Name2 as [Object:Name2],’
+ CHAR(13) + CHAR(10) + ‘ NULL as [Object:Description],’ + CHAR(13)
+ CHAR(10)
+ ‘ o.Created as [Object:Created], o.Modified as [Object:Modified],

o.Version as [Object:Version] ’
+ CHAR(13) + CHAR(10)
Walk through custom attributes
DECLARE cAttributes CURSOR FAST_FORWARD
FOR SELECT REPLACE(ClassName, ‘ ’, ‘’), AttributeName
FROM dbo.Attributes(@ClassName)
OPEN cAttributes
prime the cursor
FETCH cAttributes INTO @CurrentClass, @CurrentAttrib
WHILE @@Fetch_Status = 0
BEGIN
SET @SQLStr = @SQLStr + ‘ , CC’ + RTRIM(@CurrentClass) + ‘.’
+ RTRIM(@CurrentAttrib)+‘as[’+RTRIM(@CurrentClass) + ‘:’
+ RTRIM(@CurrentAttrib) + ‘]’ + CHAR(13) + CHAR(10)
fetch next
FETCH cAttributes INTO @CurrentClass, @CurrentAttrib
END
CLOSE cAttributes
DEALLOCATE cAttributes
FROM base metadata tables
SET @SQLStr = @SQLStr + ‘ FROM dbo.Object AS o’ + CHAR(13) + CHAR(10)
+ ‘ JOIN dbo.Class AS c’ + CHAR(13) + CHAR(10)
+ ‘ ON o.ClassID = c.ClassID’ + CHAR(13) + CHAR(10)
+ ‘ LEFT JOIN dbo.State AS s’ + CHAR(13) + CHAR(10)
+ ‘ ON o.StateID = s.StateID’ + CHAR(13) + CHAR(10)
+ ‘ LEFT JOIN dbo.Class AS sc’ + CHAR(13) + CHAR(10)
+ ‘ ON s.ClassID = sc.ClassID’ + CHAR(13) + CHAR(10)
FROM dynamic classes
DECLARE cClasses CURSOR FAST_FORWARD
Set Difference Query

FOR SELECT REPLACE(ClassName, ‘ ’, ‘’)
678
www.getcoolebook.com
Nielsen c29.tex V4 - 07/23/2009 4:59pm Page 679
Dynamic SQL and Code Generation 29
FROM SuperClasses(dbo.GetClassID(@ClassName))
ORDER BY ClassID DESC
OPEN cClasses
FETCH cClasses INTO @CurrentClass prime the cursor
WHILE @@Fetch_Status = 0
BEGIN
SET @SQLStr = @SQLStr + ‘ JOIN dbo.Obj’
+ RTRIM(@CurrentClass)+‘CC’
+ RTRIM(@CurrentClass) + CHAR(13) + CHAR(10)
+ ‘ ON o.ObjectID = CC’
+ RTRIM(@CurrentClass) + ‘.ObjectID’ + CHAR(13) + CHAR(10)
FETCH cClasses INTO @CurrentClass fetch next
END
CLOSE cClasses
DEALLOCATE cClasses
Drop and Create View
SET @GenStr = ‘IF OBJECT_ID(’’v’ + RTRIM(REPLACE(@ClassName, ‘ ’, ‘’))
+ ‘’’)’+‘ISNOTNULL DROP VIEW dbo.v’
+ RTRIM(REPLACE(@ClassName, ‘ ’, ‘’))
EXEC sp_executesql @GenStr
SET @GenStr = ‘CREATE VIEW dbo.v’
+ RTRIM(REPLACE(@ClassName, ‘ ’, ‘’))
+ CHAR(13) + CHAR(10)
+ ‘ AS ’ + CHAR(13) + CHAR(10) + @SQLStr
EXEC sp_executesql @GenStr

Standard Where Clause
SET @SQLStr = @SQLStr + CHAR(13) + CHAR(10)
+ ‘ WHERE o.ObjectID = @ObjectID weirdness aboundeth’
Drop and Create Proc
SET @GenStr = ‘IF OBJECT_ID(’’p’ + RTRIM(REPLACE(@ClassName, ‘ ’, ‘’))
+ ‘’’)’+‘ISNOTNULL DROP PROC dbo.p’
+ RTRIM(REPLACE(@ClassName, ‘ ’, ‘’))
EXEC sp_executesql @GenStr
SET @GenStr = ‘CREATE PROC dbo.p’
+ RTRIM(REPLACE(@ClassName, ‘ ’, ‘’))
+ ‘ (@ObjectID INT) AS SET NoCount ON ’ + @SQLStr
EXEC sp_executesql @GenStr
RETURN
679
www.getcoolebook.com
Nielsen c29.tex V4 - 07/23/2009 4:59pm Page 680
Part IV Developing with SQL Server
For additional examples of code generation, I recommend walking through the AutoAudit utility — it’s
all code-generation that creates triggers, views, and user-defined functions. You can download the latest
version from CodePlex.com.
Preventing SQL Injection
SQL injection is a hacker technique that appends SQL code to a parameter that is later executed as
dynamic SQL. What makes SQL injection so dangerous is that anyone with access to the organization’s
website who can enter data into a text field can attempt an SQL injection attack. There are several
malicious techniques that involve appending code or modifying the
WHERE clause. Before learning how
to prevent it, it’s important to understand how it works, as the following sections explain.
Appending malicious code
Adding a statement terminator, another SQL command, and a comment, a hacker can pass code into the
execute string. For example, if the parameter passed in is

123’; Delete OrderDetail
the parameter, including the delete DDL command, placed within a dynamic SQL string would
execute as a batch:
SELECT *
FROM Customers
WHERE CustomerID = ‘123’; Delete OrderDetail ’
The statement terminator ends the intended code and the delete command looks to SQL Server like
nothing more than the second line in the batch. The quotes would normally cause a syntax error, but
the comment line solves that problem for the hacker. The result? An empty
OrderDetail table.
Other popular appended commands include running
xp_commandshell or setting the sa password.
Or 1=1
Another SQL injection technique is to modify the WHERE clause so that more rows are selected than
intended.
If the user enters the following string into the user text box:
123’ or 1=1
then the 1=1 (always true) condition is injected into the WHERE clause. The injected hyphens comment
out the closing q uote:
SELECT *
FROM Customers
WHERE CustomerID = ‘123’ or 1=1 ’
With every row selected by the SQL statement, what happens next depends on how the rest of the sys-
tem handles multiple rows. Regardless, it’s not what should happen.
680
www.getcoolebook.com
Nielsen c29.tex V4 - 07/23/2009 4:59pm Page 681
Dynamic SQL and Code Generation 29
Password? What password?
Another creative use of SQL injection is to comment out part of the intended code. Suppose the user

enters the following in the web form:
UserName: Joe’
Password : who cares
The resulting SQL statement might read as follows:
SELECT USerID
FROM Users
WHERE UserName = ‘Joe’ ’ AND Password = ‘who cares’
The comment in the username causes SQL Server to ignore the rest of the WHERE clause, including the
password condition.
Preventing SQL Server injection attacks
Several development techniques can prevent SQL injection:
■ Use
EXECUTE AS and carefully define the roles so that statements don’t have permission to
drop tables.
■ Use DRI referential integrity to prevent deleting primary table rows with dependent secondary
table rows.
■ Never let user input mixed with dynamic SQL in a web form execute as submitted SQL.
Always pass all parameters through a stored procedure.
■ Check for and reject parameters that include statement terminators, comments, or
xp_.
■ Test your database using the SQL injection techniques described above.
SQL injection is a real threat. If your application is exposed to entry from the Internet and you haven’t
taken steps to prevent SQL injection, it’s only a matter of time before your database is attacked.
Best Practice
F
or a flexible search procedure in Nordic, I’ve started using a new practice of parsing and joining. Rather
than build a dynamic SQL WHERE clause, I allow the application to pass in a multiple-word search string.
This is parsed into a table with each word becoming a row. The search table is then joined with the various
data points that can be searched. For an example of this technique, turn back to Chapter 28, ‘‘Building Out
the Data Abstraction Layer,’’ or download the latest version of Nordic from CodePlex.com.

681
www.getcoolebook.com

×