Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 40 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
40
Dung lượng
804,57 KB
Nội dung
76 CHAPTER Error handling in SQL Server and applications The ERROR_STATE() function can be used to determine the error state Some system error messages can be raised at different points in the SQL Server engine SQL Server uses the error state to differentiate when these errors are raised The last two properties of an error are the line number and the name of the stored procedure where the error occurred These can be returned using the ERROR_LINE() function and the ERROR_PROCEDURE() function, respectively The ERROR_PROCEDURE() function will return NULL if the error occurs outside a stored procedure Listing is an example of these last two functions inside a stored procedure Listing ERROR_LINE and ERROR_PROCEDURE functions in a stored procedure CREATE PROCEDURE ChildError AS BEGIN RAISERROR('My Error', 11, 1) END GO CREATE PROCEDURE ParentError AS BEGIN EXEC ChildError END GO BEGIN TRY EXEC ParentError END TRY BEGIN CATCH SELECT Error_Line = ERROR_LINE(), Error_Proc = ERROR_PROCEDURE() END CATCH This returns the following result: Error_Line Error_Proc - ChildError Let’s look now at how we can generate our own custom error messages Generate your own errors using RAISERROR The RAISERROR function can be used to generate SQL Server errors and initiate any error processing The basic use of RAISERROR for a dynamic error looks like this: RAISERROR('Invalid Customer', 11, 1) This returns the following when run in SQL Server Management Studio: Msg 50000, Level 11, State 1, Line Invalid Customer The first parameter is the custom error message The second is the severity (or level) Remember that 11 is the minimum severity that will cause a CATCH block to fire The last parameter is the error state Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark Licensed to Kerri Ross Handling errors inside SQL Server 77 RAISERROR can also be used to return user-created error messages The code in listing illustrates this Listing Returning user-created error messages with RAISERROR EXEC sp_addmessage @msgnum = 50001, @severity = 11, @msgtext = 'My custom error', @replace = 'replace'; GO RAISERROR(50001, 11, 1); GO This returns the following result: Msg 50001, Level 11, State 1, Line My custom error The @REPLACE parameter of sp_addmessage says to replace the error if it already exists When RAISERROR is called with a message description rather than an error number, it returns an error number 50000 Ordinary users can specify RAISERROR with severity levels up to 18 To specify severity levels greater than 18, you must be in the sysadmin fixed server role or have been granted ALTER TRACE permissions You must also use the WITH LOG option This option logs the messages to the SQL Server log and the Windows Application Event Log WITH LOG may be used with any severity level Using a severity level of 20 or higher in a RAISERROR statement will cause the connection to close Nesting TRY CATCH blocks TRY CATCH blocks can be nested inside either TRY or CATCH blocks Nesting inside a TRY block looks like listing Listing Nesting TRY CATCH blocks BEGIN TRY PRINT 'One' BEGIN TRY PRINT 1/0 END TRY BEGIN CATCH PRINT 'Caught by the inner catch' END CATCH PRINT 'Two' END TRY BEGIN CATCH PRINT 'Caught by the outer catch' END CATCH Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark Licensed to Kerri Ross 78 CHAPTER Error handling in SQL Server and applications This batch returns the following result: One Caught by the inner catch Two This allows specific statements inside a larger TRY CATCH to have their own error handling A construct like this can be used to selectively handle certain errors and pass any other errors further up the chain Here’s an example in listing Listing Error handling with nested TRY CATCH statements BEGIN TRY PRINT 'One' BEGIN TRY PRINT CAST('Hello' AS DATETIME) END TRY BEGIN CATCH IF ERROR_NUMBER() = 8134 PRINT 'Divide by zero Again.' ELSE BEGIN DECLARE @ErrorNumber INT; DECLARE @ErrorMessage NVARCHAR(4000) DECLARE @ErrorSeverity INT; DECLARE @ErrorState INT; SELECT @ErrorNumber = ERROR_NUMBER(), @ErrorMessage = ERROR_MESSAGE() + ' (%d)', @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(); RAISERROR( @ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber ) END END CATCH PRINT 'Two' END TRY BEGIN CATCH PRINT 'Error: ' + ERROR_MESSAGE() END CATCH This returns the following result: One Error: Conversion failed when converting datetime from character string (241) In the inner CATCH block I’m checking whether we generated error number 8134 (divide by zero) and if so, I print a message For every other error message, I “reraise” or “rethrow” the error to the outer catch block Note the text string that’s added to Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark Licensed to Kerri Ross Handling errors inside SQL Server 79 the error message variable The %d is a placeholder that’s replaced by the first additional parameter passed to RAISERROR, which is @ErrorNumber in my example Books Online has more information on the different types of replace variables that can be used in RAISERROR The error functions can be used in any stored procedure called from inside the CATCH block This allows you to create standardized error-handling modules such as the one in listing Listing An error-handling module CREATE PROCEDURE ErrorHandler AS BEGIN PRINT 'I should log this error:' PRINT ERROR_MESSAGE() END GO BEGIN TRY SELECT 1/0 END TRY BEGIN CATCH EXEC ErrorHandler END CATCH This block of code will return the following results: I should log this error: Divide by zero error encountered This is typically used to handle any errors in the code that logs the error information to a custom error table TRY CATCH and transactions A common use for a TRY CATCH block is to handle transaction processing A common pattern for this is shown in listing Listing Transaction processing in a TRY CATCH block BEGIN TRY BEGIN TRANSACTION INSERT INTO dbo.invoice_header (invoice_number, client_number) VALUES (2367, 19) INSERT INTO dbo.invoice_detail (invoice_number, line_number, part_number) VALUES (2367, 1, 84367) COMMIT TRANSACTION END TRY BEGIN CATCH Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark Licensed to Kerri Ross 80 CHAPTER Error handling in SQL Server and applications IF @@TRANCOUNT > ROLLBACK TRANSACTION And rethrow the error END CATCH Remember that the CATCH block completely consumes the error; therefore it is important to return some type of error or message back to the calling program Handling SQL Server errors on the client The examples in this section use C# as the client application Any NET client application that supports try catch constructs will behave in a similar fashion The key points to learn here are which NET classes are involved in error handling and what methods and properties they expose When a NET application executes a SQL statement that causes an error, it throws a SqlException This can be caught using a try catch block like we saw previously The SQL Server exception could also be caught by catching a plain Exception, but the SqlException class provides additional SQL Server–specific properties A simple example of this in C# is shown in listing 10 Listing 10 Outputting SQL Server–specific error properties with SqlException using System.Data; using System.Data.SqlClient; class Program { SqlConnection conn = new SqlConnection(" "); SqlCommand cmd = new SqlCommand("RAISERROR('My Error', 11, 1)", conn); try { cmd.Connection.Open(); cmd.ExecuteNonQuery(); Console.WriteLine("No error returned"); } catch (SqlException sqlex) { Console.WriteLine("Error Message: " + sqlex.Message); Console.WriteLine("Error Severity: {0}", sqlex.Class.ToString()); Console.WriteLine("Line Number: {0}", sqlex.LineNumber.ToString()); } } This returns the following result: Error Message: My Error Error Severity: 11 Line Number: Exceptions with a severity of 10 or less don’t trigger the catch block on the client The connection is closed if the severity level is 20 or higher; it normally remains open if the severity level is 19 or less You can use RAISERROR to generate severities of 20 or higher and it’ll close the connection and fire the try catch block on the client Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark Licensed to Kerri Ross Handling SQL Server errors on the client 81 The error returned typically indicates that the connection was closed rather than the error text you specified The SqlException class inherits from the System.SystemException and includes many properties that are specific to NET Some key SQL Server–specific properties of the SqlException class are shown in table Table SQLException class properties Property Description Class Error severity level LineNumber Line number in the batch or stored procedure where the error occurred Message Description of the error Number SQL Server error number Procedure Stored procedure name where the error occurred Server SQL Server instance that generated the error Source Provider that generated the error (for example, Net SqlClient Data Provider) State SQL Server error state (the third parameter of RAISERROR) Another interesting property of the SqlException class is the Errors property This is a collection of SqlError objects The SqlError class includes only the SQL Server– specific properties from the SqlException object that are listed in table Because a batch of SQL can generate multiple SQL Server errors, an application needs to check whether multiple errors have occurred The first error in the Errors property will always match the error in the SqlExcpetion’s properties Listing 11 is an example Listing 11 Handling multiple errors with the Errors property using System.Data; using System.Data.SqlClient; class Program { static void Main(string[] args) { SqlConnection conn = new SqlConnection(@"Server=L60\YUKON; ➥Integrated Security=SSPI"); SqlCommand cmd = new SqlCommand( @"RAISERROR('My Error', 11, 17) SELECT 1/0 SELECT * FROM dbo.BadTable", conn); try { cmd.Connection.Open(); cmd.ExecuteReader(); Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark Licensed to Kerri Ross 82 CHAPTER Error handling in SQL Server and applications Console.WriteLine("No error returned"); } catch (SqlException sqlex) { for (int i = 0; i < sqlex.Errors.Count; i++) { Console.WriteLine("Error #{0}: {1}", i.ToString(), sqlex.Errors[i].Message); } } } } This returns the following result: Error #0: My Error Error #1: Divide by zero error encountered Error #2: Invalid object name 'dbo.BadTable' In closing, let’s look at how we can handle SQL Server messages inside our application code Handling SQL Server messages on the client When a message is sent from SQL Server via a PRINT statement or a RAISERROR with a severity level of 10 or less, it generates an event on the NET side You can capture this event by writing a handler for the SqlConnection class’s InfoMessage event The handler for the InfoMessage event takes two parameters: the sender and an instance of SqlInfoMessageEventArgs This class contains three properties The first is the Message that was printed or generated by the RAISERROR statement The second is the Source, which is usually the Net SqlClient Data Provider The third is the Errors property, which is a collection of SqlError objects and behaves just like it did when we saw it earlier Listing 12 is an example Listing 12 Outputting SQL Server messages using System.Data; using System.Data.SqlClient; class Program { static void Main(string[] args) { SqlConnection conn = new SqlConnection(@"Server=L60\YUKON; ➥Integrated Security=SSPI"); SqlCommand cmd = new SqlCommand("PRINT 'Hello'", conn); conn.InfoMessage += new ➥SqlInfoMessageEventHandler(conn_InfoMessage); try { cmd.Connection.Open(); Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark Licensed to Kerri Ross Handling SQL Server errors on the client 83 cmd.ExecuteNonQuery(); cmd.CommandText ="RAISERROR('An error as message', 5, 12)"; cmd.ExecuteNonQuery(); Console.WriteLine("No error returned"); } catch (SqlException sqlex) { Console.WriteLine("First Error Message: " + sqlex.Message); Console.WriteLine("Error Count: {0}", ➥sqlex.Errors.Count.ToString()); } } static void conn_InfoMessage(object sender, SqlInfoMessageEventArgs e) { Console.WriteLine("SQL Server Message: {0}", e.Message); Console.WriteLine("Message Source: {0}", e.Source); Console.WriteLine("Message Count: {0}", e.Errors.Count.ToString()); } } This returns the following result: SQL Server Message: Hello Message Source: Net SqlClient Data Provider Message Count: SQL Server Message: An error as message Message Source: Net SqlClient Data Provider Message Count: No error returned Another interesting characteristic of this approach is that you can capture informational RAISERROR statements as they’re executed rather than when a batch ends Listing 13 shows an example Listing 13 Capturing RAISERROR statements using System.Data; using System.Data.SqlClient; class Program { static void Main(string[] args) { SqlConnection conn = new SqlConnection(@"Server=L60\YUKON; ➥Integrated Security=SSPI"); SqlCommand cmd = new SqlCommand( @"PRINT 'Printed at buffer flush' RAISERROR('Starting', 0, 1) WITH NOWAIT; WAITFOR DELAY '00:00:03'; RAISERROR('Status', 0, 1) WITH NOWAIT; WAITFOR DELAY '00:00:03'; PRINT 'Done';", conn); conn.InfoMessage += new ➥SqlInfoMessageEventHandler(conn_ShortMessage); Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark Licensed to Kerri Ross 84 CHAPTER Error handling in SQL Server and applications try { cmd.Connection.Open(); cmd.ExecuteReader(); Console.WriteLine("No error returned"); } catch (SqlException sqlex) { Console.WriteLine("First Error Message: " + sqlex.Message); Console.WriteLine("Error Count: {0}", ➥sqlex.Errors.Count.ToString()); } } static void conn_ShortMessage(object sender, SqlInfoMessageEventArgs e) { Console.WriteLine("[{0}] SQL Server Message: {1}", System.DateTime.Now.ToLongTimeString(), e.Message); } } This returns the following result: [3:39:26 [3:39:26 [3:39:29 [3:39:32 No error PM] SQL Server PM] SQL Server PM] SQL Server PM] SQL Server returned Message: Message: Message: Message: Printed at buffer flush Starting Status Done Normally, when you a series of PRINT statements inside a SQL Server batch or stored procedure, the results are all returned at the end A RAISERROR WITH NOWAIT is sent immediately to the client, as is any previous PRINT statement If you remove the WITH NOWAIT from the first RAISERROR, the first three lines are all printed at the same time when the RAISERROR WITH NOWAIT pushes them all to the client This approach can provide a convenient way to return status information for long-running tasks that contain multiple SQL statements Summary SQL Server error handling doesn’t need to be an afterthought SQL Server 2005 pro- vides powerful tools that allow developers to selectively handle, capture, and consume errors inside SQL Server Errors that can’t be handled on the server can be passed back to the application .NET has specialized classes that allow applications to capture detailed information about SQL Server exceptions Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark Licensed to Kerri Ross Summary 85 About the author Bill Graziano has been a SQL Server consultant for 10 years, doing production support, performance tuning, and application development He serves on the board of directors for the Professional Association for SQL Server (PASS), where he serves as the vice president of marketing and sits on the executive committee He’s a regular speaker at conferences and user groups across the country Bill runs the popular web site http:/ /SQLTeam.com and is currently a SQL Server MVP Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark Licensed to Kerri Ross Summary 101 It is useful to be able to write queries that can be simplified by the optimizer, but if you can also make your queries simpler to understand, they're probably quicker to write and easier to maintain Why not consider using views that contain lookup information? Views that can be simplified can still wrap up useful functionality to make life easier for all the query writers, providing a much richer database to everyone Summary We all need to be able to read and understand the FROM clause In this chapter we’ve looked at the basics of JOINs, but also considered a methodology for reading more complex FROM clauses, and considered the ways that JOINs can be made redundant in queries I hope that after reading this chapter, you reexamine some of your FROM clauses, and have a new appreciation for the power of JOINs In particular, I hope that part of your standard query writing process will include considering ways to allow JOINs to become redundant, using LEFT JOINs and uniqueness as demonstrated in the second half of this chapter About the author Rob Farley is a Microsoft MVP (SQL) based in Adelaide, Australia, where he runs a SQL and BI consultancy called LobsterPot Solutions He also runs the Adelaide SQL Server User Group, and regularly trains and presents at user groups around Australia He holds many certifications and has made several trips to Microsoft in Redmond to help create exams for Microsoft Learning in SQL and NET His passions include Arsenal Football Club, his church, and his wife and three amazing children The address of his blog is http:/ /msmvps.com/blogs/robfarley, and his company website is at http:/ /www.lobsterpot.com.au Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark Licensed to Kerri Ross What makes a bulk insert a minimally logged operation? Denis Gobo By using a minimally logged operation, SQL Server doesn’t have to work as hard as if the operation was fully logged The result is that your import will be much faster, and also that your log file could be a fraction of the size of a fully logged operation Before we start, we first need to understand what a minimally logged operation is A minimally logged operation logs only the pages and extents that are affected; a fully logged operation will log all the individual insert, update, and delete statements If you have 100 insert statements in a fully logged operation, you’d have 100 entries in the log file, whereas if it were minimally logged, you’d only have one entry A minimally logged bulk copy can be performed if all of these conditions are met: The recovery model is simple or bulk-logged The target table isn’t being replicated The target table doesn’t have any triggers The target table either has zero rows or doesn’t have indexes The TABLOCK hint is specified In this chapter, I’m going to concentrate on the TABLOCK hint (used to obtain a shared lock on a table which is held until the end of a T-SQL statement) and how it can be used to increase performance I’ll show you that performance will greatly increase when you specify the TABLOCK hint Recovery and locking Three kinds of locks can be acquired on a table The lowest-level lock is a row lock: this will only lock one row A coarser-grain lock is the page lock, which will lock all the data on a page Finally, a table lock will lock the whole table Only when you 102 Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark Licensed to Kerri Ross Recovery and locking 103 specify a table lock in your bulk insert statement can you have a minimally logged operation SQL Server has three recovery models: Full, Simple, and Bulk-Logged Simple Recovery—Simple Recovery uses the least amount of log space, and you can only restore to the most recent full database or differential backup Full Recovery—Full Recovery uses the most log space and gives you full recoverability of data, because you can use backups as well as transaction logs to restore to a point in time Bulk-Logged Recovery—Bulk Logged Recovery is similar to the Full Recovery model, but offers no point-in-time recovery for bulk operations Bulk operations would have to be redone To show you how the recovery model and the TABLOCK hint affect the size of the log, we will create six databases: two databases per recovery level For each recovery level, we will run the bulk insert with and without the TABLOCK hint Start by executing the scripts in listing 1, which will create our six databases Listing SQL scripts to create databases CREATE DATABASE TestRecoverySimple GO ALTER DATABASE TestRecoverySimple SET RECOVERY SIMPLE GO CREATE DATABASE TestRecoverySimpleLock GO ALTER DATABASE TestRecoverySimpleLock SET RECOVERY SIMPLE GO CREATE DATABASE TestRecoveryBulk GO ALTER DATABASE TestRecoveryBulk SET RECOVERY BULK_LOGGED GO CREATE DATABASE TestRecoveryBulkLock GO ALTER DATABASE TestRecoveryBulkLock SET RECOVERY BULK_LOGGED GO CREATE DATABASE TestRecoveryFull GO ALTER DATABASE TestRecoveryFull SET RECOVERY FULL GO Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark Licensed to Kerri Ross 104 CHAPTER What makes a bulk insert a minimally logged operation? CREATE DATABASE TestRecoveryFullLock GO ALTER DATABASE TestRecoveryFullLock SET RECOVERY FULL GO Now that the databases have been created, we can continue by creating our bulk import file Creating the file to import Next, we need to create a file to use for our bulk import; the easiest way to create a file is to use the bcp utility The bcp utility is a program that accepts a variety of arguments, making it ideal for importing and exporting a wide range of file formats You can run the bcp utility in two ways: from the command line or from a query window by using xp_cmdshell In order to create our import file, we need some data; we will use the sysobjects table to generate this data The sysobjects table is a system table that exists in each database; this table contains one row for each object created in the database We will create a file with 500,000 rows by cross-joining the sysobjects table with itself Here’s what the query looks like: SELECT top 500000 s.name,s.id,s.userstat,s2.name,newid() FROM master sysobjects s CROSS JOIN master sysobjects s2 To use bcp from the command line, copy the following line and paste it into a command window: bcp "select top 500000 s.name,s.id,s.userstat,s2.name,newid() from ➥ master sysobjects s cross join master sysobjects s2" queryout ➥ c:\BulkTestData.txt -c -S(local)\sql2008 –T To use bcp from a query window, you need to the following: master xp_cmdshell 'bcp "select top 500000 ➥ s.name,s.id,s.userstat,s2.name,newid() from master sysobjects s cross ➥ join master sysobjects s2" queryout c:\BulkTestData.txt -c ➥ S(local)\sql2008 -T' NOTE I have a named instance on my machine named sql2008; if you don’t have a named instance, just use (local), or if you have a named instance, use (local)\InstanceName You might get the error in listing 2, because SQL Server ships with xp_cmdshell disabled by default as of SQL Server 2005 Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark Licensed to Kerri Ross Creating the tables to store the data Listing 105 Error message on running bcp utility from a query window Server: Msg 15281, Level 16, State 1, Procedure xp_cmdshell, Line SQL Server blocked access to procedure 'sys.xp_cmdshell' of component 'xp_cmdshell' because this component is turned off as part of the security configuration for this server A system administrator can enable the use of 'xp_cmdshell' by using sp_configure For more information about enabling 'xp_cmdshell', see "Surface Area Configuration" in SQL Server Books Online To enable xp_cmdshell, execute the code in listing Listing Script to enable xp_cmdshell EXECUTE SP_CONFIGURE 'show advanced options', RECONFIGURE WITH OVERRIDE GO EXECUTE SP_CONFIGURE 'xp_cmdshell', '1' RECONFIGURE WITH OVERRIDE GO EXECUTE SP_CONFIGURE 'show advanced options', RECONFIGURE WITH OVERRIDE GO Creating the tables to store the data After we create the file, we need to create tables to store the data in We will create the same table in all six databases with the code in listing Listing Script to create a database table in six different databases USE TestRecoveryBulk GO CREATE TABLE BulkImport ( name1 varchar(500) not null, id int not null, userstat int not null, name2 varchar(500) not null, SomeVal uniqueidentifier not null ) USE TestRecoverySimple GO CREATE TABLE BulkImport ( name1 varchar(500) not null, id int not null, userstat int not null, name2 varchar(500) not null, SomeVal uniqueidentifier not null ) USE TestRecoveryFull GO Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark Licensed to Kerri Ross 106 CHAPTER What makes a bulk insert a minimally logged operation? CREATE TABLE BulkImport ( name1 varchar(500) not null, id int not null, userstat int not null, name2 varchar(500) not null, SomeVal uniqueidentifier not null ) USE TestRecoveryBulkLock GO CREATE TABLE BulkImport ( name1 varchar(500) not null, id int not null, userstat int not null, name2 varchar(500) not null, SomeVal uniqueidentifier not null ) USE TestRecoverySimpleLock GO CREATE TABLE BulkImport ( name1 varchar(500) not null, id int not null, userstat int not null, name2 varchar(500) not null, SomeVal uniqueidentifier not null ) USE TestRecoveryFullLock GO CREATE TABLE BulkImport ( name1 varchar(500) not null, id int not null, userstat int not null, name2 varchar(500) not null, SomeVal uniqueidentifier not null ) Importing the data Here’s where the import starts First we will the imports without the TABLOCK hint by executing the three BULK INSERT statements in listing Listing BULK INSERT statements to import data without the TABLOCK hint BULK INSERT TestRecoveryFull BulkImport FROM 'c:\BulkTestData.txt' WITH (DATAFILETYPE = 'char', FIELDTERMINATOR = '\t', ROWTERMINATOR = '\n') BULK INSERT TestRecoverySimple BulkImport FROM 'c:\BulkTestData.txt' WITH (DATAFILETYPE = 'char', Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark Licensed to Kerri Ross Importing the data 107 FIELDTERMINATOR = '\t', ROWTERMINATOR = '\n') BULK INSERT TestRecoveryBulk BulkImport FROM 'c:\BulkTestData.txt' WITH (DATAFILETYPE = 'char', FIELDTERMINATOR = '\t', ROWTERMINATOR = '\n') Next we will the import with the TABLOCK hint by executing the block of code in listing Listing BULK INSERT statement to import data with TABLOCK hint BULK INSERT TestRecoveryFullLock BulkImport FROM 'c:\BulkTestData.txt' WITH (DATAFILETYPE = 'char', FIELDTERMINATOR = '\t', ROWTERMINATOR = '\n', TABLOCK) BULK INSERT TestRecoverySimpleLock BulkImport FROM 'c:\BulkTestData.txt' WITH (DATAFILETYPE = 'char', FIELDTERMINATOR = '\t', ROWTERMINATOR = '\n', TABLOCK) BULK INSERT TestRecoveryBulkLock BulkImport FROM 'c:\BulkTestData.txt' WITH (DATAFILETYPE = 'char', FIELDTERMINATOR = '\t', ROWTERMINATOR = '\n', TABLOCK) Now that we’ve imported all the files, let’s find out how big the log files are for all six databases Execute the query in listing Listing Query to determine the size of log files SELECT size,name FROM TestRecoveryBulk.sys.sysfiles WHERE name LIKE '%log' UNION ALL SELECT size,name FROM TestRecoveryBulkLock.sys.sysfiles WHERE name LIKE '%log' UNION ALL SELECT size,name FROM TestRecoverySimple.sys.sysfiles WHERE name LIKE '%log' UNION ALL Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark Licensed to Kerri Ross 108 CHAPTER What makes a bulk insert a minimally logged operation? SELECT size,name FROM TestRecoverySimpleLock.sys.sysfiles WHERE name LIKE '%log' UNION ALL SELECT size,name FROM TestRecoveryFull.sys.sysfiles WHERE name LIKE '%log' UNION ALL SELECT size,name FROM TestRecoveryFullLock.sys.sysfiles WHERE name LIKE '%log' The result set should look like table Table Resultant log file sizes Size in KB Database name 24912 TestRecoveryBulk_log 160 TestRecoveryBulkLock_log 24912 TestRecoverySimple_log 160 TestRecoverySimpleLock_log 24912 TestRecoveryFull_log 5408 TestRecoveryFullLock_log As you can see, when using simple or bulk-logged recovery with the TABLOCK hint, the size of the log file is much smaller than when you aren’t using the TABLOCK hint If you are using SQL Server Integration Services and the BULK INSERT task, you must also lock the table for minimal logging You can enable this in two ways The first way is to right-click on the Bulk Insert Task icon and select Properties, or double-click on the Bulk Insert Task icon This will bring up the dialog box shown in figure 1; click on the drop-down box next to Options and select Table Lock Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark Licensed to Kerri Ross Summary Figure 109 Bulk Insert Task Editor Another way is to scroll down to Table Lock in the Properties pane on the right side of Business Intelligence Development Studio and set TableLock to True, as shown in figure Summary As you’ve seen in this chapter, choosing the correct recovery model isn’t enough to make a bulk insert a minimally logged operation; you also need to make sure that you lock the table Figure Properties window Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark Licensed to Kerri Ross 110 CHAPTER What makes a bulk insert a minimally logged operation? About the author Denis Gobo resides in Princeton, New Jersey, with his wife and three kids For the last four years, Denis has worked for Dow Jones, where his task is to optimize the storage and retrieval of a good amount of data; most of this data is stored in SQL Server Denis is a cofounder of http://www.lessthandot.com, a community site for tech professionals, where he also blogs and answers questions in the forums In his free time, Denis likes to read, watch horror movies, and spend time with his family Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark Licensed to Kerri Ross Avoiding three common query mistakes Kathi Kellenberger Writing correct and well-performing queries is both an art and a science The query must return the expected results and execute in a reasonable time Many blogs and articles have been written about improving query performance, but this chapter will focus on common mistakes that ultimately cause incorrect or incomplete data to be returned These are problems I have frequently been asked to help solve or have encountered myself The examples in this chapter use SQL Server 2008 and the AdventureWorks2008 database that is available for download at http://www.codeplex.com Search for “SQL Server 2008 code samples” on the site to find the latest release of the database The queries will also work with the SQL Server 2005 version of the AdventureWorks database NULL comparisons The NULL value means unknown; no value has been assigned This is not the same as an empty string or zero As long as the ANSI_NULLS setting is turned on, which is the default, comparing a value to NULL returns unknown One usually expects a value of TRUE or FALSE when making comparisons, but unknown complicates matters under certain conditions When comparing a known value to NULL and unknown is returned, it effectively works the same as FALSE, and no results are returned The AdventureWorks2008 database has a Production.Product table with a Color column that can contain NULLs If you are trying to find all the products with the color red, you most likely not want to see the products with no color assigned; therefore, this is not a problem But if you would like a list of the products where the color does not equal red, you must decide whether or not the values with no assigned color belong in the results If you intend for the NULL rows to be included, the criteria WHERE color 'red' will be incorrect 111 Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark Licensed to Kerri Ross 112 CHAPTER Avoiding three common query mistakes Why is this the case? The expression value 'red' is the same as NOT(value = 'red') If the value happens to be NULL, then unknown is the result of comparing the value to red within the parentheses When applying NOT to unknown, the expression still returns unknown Not FALSE is equal to TRUE, but not unknown is still unknown Listing shows three ways to write a query to produce the partial results shown in figure The second and third queries each use a function, ISNULL or COALESCE, to replace NULL with an empty string This allows the color to be compared to the empty string instead of NULL Listing Figure NULL values are included along with the colors that have data Three queries to include NULL SELECT ProductID, Color FROM Production.Product WHERE Color 'red' OR Color IS NULL SELECT ProductID, Color FROM Production.Product WHERE ISNULL(Color,'') 'red' SELECT ProductID, Color FROM Production.Product WHERE COALESCE(Color,'') 'red' You also need to be careful when your expression contains the less-than operator (