<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;Ak4AQ3c-cSp7ImA9WhRRFE4.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723</id><updated>2011-11-27T16:09:02.959-08:00</updated><category term="Aggregate Functions" /><category term="'two-bites' approach" /><category term="CASE statements" /><category term="IDENTITY columns in SQL Server" /><category term="Join" /><category term="Schedulier in SQL" /><category term="Isolation Levels" /><category term="Holy Grail" /><category term="Schedulier in T-SQL" /><category term="SQL Aggregate Functions and NULL" /><category term="Durable" /><category term="Conditional Statements" /><category term="common table expressions" /><category term="NULL Variables" /><category term="Upper Case" /><category term="OUTPUT Clause" /><category term="UPPER and LOWER Function" /><category term="Lower Case" /><category term="JOIN TYPE IN SQL" /><category term="COUNT(*) OVER() Approach" /><category term="Read Only Tables in T-SQL" /><category term="Schedulier" /><category term="How to Use GROUP BY in SQL Server" /><category term="Execution Plans" /><category term="Non-Clustered INdex" /><category term="Show Execution Plan" /><category term="Clustered Index How to Schedule Job in SQL" /><category term="uniqueidentifier" /><category term="Indexed Columns" /><category term="Comparing Columns with Different Case" /><category term="parent/child relationship" /><category term="How To Use JOIN in T-SQL" /><category term="web.config" /><category term="GROUPING SETS" /><category term="ROW_NUMBER OVER()" /><category term="Sql Server 2008" /><category term="How to use Index in SQL" /><category term="Scheduling" /><category term="conditional WHERE clauses" /><category term="IDENTITY  in SQL" /><category term="Encrypting Connection Strings" /><category term="CASE" /><category term="How to get the BOTTOM records" /><category term="MERGE keyword" /><category term="How to use Index" /><category term="T-SQL query" /><category term="FORCESEEK" /><category term="IP Address" /><category term="COMMIT" /><category term="Date Time DataType" /><category term="BEGIN TRAN" /><category term="Datetime" /><category term="NULL Versus NULL" /><category term="GRANT permissions" /><category term="one-to-many" /><category term="Index in T-SQL" /><category term="Consistent" /><category term="SQL WHERE clause" /><category term="Optimized Stored Procedures" /><category term="Function in the WHERE clause" /><category term="Read Only Tables in Sql" /><category term="Working with Datetime" /><category term="Auto generated keys" /><category term="Optimizer Join Methods" /><category term="Stored Procedure vs Triggers" /><category term="SQL Server" /><category term="INDEX" /><category term="Clustered Index" /><category term="Upper and Lower Case" /><category term="AUTONUMBER" /><category term="Sql  New Version 2008" /><category term="What is Null" /><category term="IDENTITY columns" /><category term="Exclusive locks" /><category term="ROLLBACK" /><category term="Not Equal To" /><category term="GROUP BY in SQL Server" /><category term="COALESCE()" /><category term="The Pitfall of &quot;Not Equal To&quot;" /><category term="smalldatetime" /><category term="Where Clause in T-SQL Query" /><category term="How To Use JOIN in  T-SQL" /><category term="“WITH TIES” options" /><category term="Index in SQL" /><category term="Index Seek" /><category term="Merge in SQL" /><category term="Read Only Tables" /><category term="UDF For DateTime" /><category term="transaction" /><category term="Clustered Index Seek" /><category term="Multi-Row Inserts" /><category term="DENY permissions" /><category term="Arithmetic overflow" /><category term="IDENT_CURRENT" /><category term="Security Using Triggers" /><category term="How to Schedule Job in SQL" /><category term="New in Sql Server 2008" /><category term="SQL transaction" /><category term="Stored Procedures" /><category term="Triggers" /><category term="Identity Values" /><category term="Null" /><category term="Transactions" /><category term="temp table approach" /><category term="Locking" /><category term="INNER JOIN" /><category term="TOP Clause" /><category term="Design Stored Procedures" /><category term="CTE" /><category term="SQL Server 2005 Paging" /><category term="Storing the IP address in an efficient format" /><category term="SCPOPE_IDENTITY" /><category term="SQL Server 2008 introduces MERGE" /><category term="GROUP BY" /><category term="IP Address in SQL" /><category term="Dealing With Upper and Lower Case Data" /><category term="examples of working with datetime" /><category term="SQL JOIN" /><category term="Schedulier Job in SQL" /><category term="Using COUNT(Distinct)" /><category term="High Performance Stored Procedures" /><category term="auto number" /><category term="Index Scan" /><category term="WHERE Clauses" /><title>SQL SERVER Encyclopedia- SQL SERVER Tips &amp; Sugegstions- BuggFixing &amp; Trouble Shooting</title><subtitle type="html">This blog is basically contains the articles Regarding the Sql Server.
You can find here performance &amp;amp; Tuning and also Optimization help BuggFixing &amp;amp; Trouble Shooting</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://piyushagarwal.blogspot.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>40</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting" /><feedburner:info uri="sqlserverencyclopedia-sqlservertipssugegstions-buggfixingtroubleshooting" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry gd:etag="W/&quot;AkYDSX85fip7ImA9WxFRE0s.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-7479253815166452611</id><published>2010-04-27T04:36:00.000-07:00</published><updated>2010-04-27T04:36:18.126-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-04-27T04:36:18.126-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Security Using Triggers" /><category scheme="http://www.blogger.com/atom/ns#" term="Triggers" /><title>Row Oriented Security Using Triggers</title><content type="html">&lt;b&gt;Overview&lt;/b&gt;&lt;br /&gt;
This article introduces a simple model for row-oriented security. It is based on a small number of security tables and a few triggers. The model is independent of SQL Server's own security, and may be used in other database products with minimal change. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The basic premise of this model is that some user table acts as a gateway to all other tables. Row-oriented security uses a single table to list which rows of the gateway table may be accessed by a given user. Such users may access related rows in other tables if access has been granted to the corresponding gateway rows. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
It also assumes that users will access the database through approved applications only. That's because the model puts a tiny part of the security burden on the front-end applications instead of the database server (where most of the security processing occurs). In particular, those apps can implement additional row-oriented and column-oriented security without additional programming. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This model has been successfully used by a large Medical faculty for several years. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example&lt;br /&gt;
&lt;br /&gt;
To make things really simple, suppose our database contains exactly one table Employee for which, say, an HR application has been written: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Fig. 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
In that application, we would like each user to be restricted to various rows depending on the roles that user has been assigned. We would like those restrictions to be defined in a set of security tables that the application reads at run-time, and which may be modified in real-time by an administrator. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The rows in Employee that each role may access will be defined by filters on that table, such as: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SELECT EmployeeId AS SelectId FROM Employee WHERE City = 'Vancouver'Such filters simply list key values of the gateway table with a fixed name SelectId. Each role may have any number of filters assigned to it and each user may have multiple roles. For convenience, the gateway table's key and SelectId are INT columns (although that can be easily changed). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
We also want the rows that any role (and user) can see to accurately reflect the current state of the data (an employee may move at any time, for example) or the filters currently assigned to it (they may be inserted or deleted at any time). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Furthermore, the app itself should automatically impose additional row- and column-oriented constraints, such as preventing record deletions or hiding certain fields, depending on the restrictions entered into the security tables by the administrator. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The model presented here does that, using special security tables that designate what roles users belong to, what rows each role may access in the gateway table, what kind of editing may be done on those rows, and what columns will be hidden by the apps. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Functionality&lt;br /&gt;
&lt;br /&gt;
Users, roles and restrictions are defined by an organization using special security tables added to the database. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
A row-oriented restriction is nothing more than a filter defining what rows of the gateway table may be accessed by a given role. Such filters may also be defined using additional tables, such as lookup tables. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Users may be assigned any number of roles. They automatically inherit the "sum" of all security restrictions on those roles. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The filtering mechanism for invoking security works dynamically, using triggers that fire whenever the gateway table is modified: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
If any row in the gateway table becomes visible to a security filter (because some underlying data has suddenly changed) then that row is immediately available to any role whose security includes that filter. &lt;br /&gt;
&lt;br /&gt;
Correspondingly, if any row in the gateway table becomes invisible to a security filter then that row is no longer available to any role whose security included that filter (unless some other security filter for that role still allows it). &lt;br /&gt;
&lt;br /&gt;
Furthermore, any changes to the set of security filters is immediately witnessed by all users, using triggers that fire whenever one of them is added or deleted: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
If any security filter is added, then each row in the gateway table defined by it is immediately available to any role whose security includes that filter. &lt;br /&gt;
&lt;br /&gt;
Correspondingly, if any security filter is deleted then each row of the gateway table defined by it is no longer available to any role whose security included that filter (unless some other security filter for that role still allows it). &lt;br /&gt;
&lt;br /&gt;
Implementation&lt;br /&gt;
&lt;br /&gt;
The entire system is written using triggers and stored procedures. So it is basically independent of any desktop or web application, which need only include some standard, front-end security code to use it. That's why only "approved" applications are allowed to access the database. Of course, one may bypass the security model by simply ignoring the security tables. That allows developers to test applications that are security-free, before imposing user restrictions prior to deployment. Because a single relationship connects the security tables to the gateway table, adding security to any approved app simply involves "fooling" the app by replacing the gateway table with a recordset filtered by security. In that way, legacy apps may have row-oriented security bolted onto them fairly quickly. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For convenience, a VB.NET security manager interactively defines users, roles and restrictions. Any changes to security are witnessed by users when they log in through an approved app. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This system is ideal for a Citrix server farm (where it was originally developed), where only approved applications access data and the underlying recordsets and code are hidden from the user. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Security Model&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Zoom in &lt;br /&gt;
Open in new window&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Fig. 2&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The lkpUser and lkpRole tables define users and roles, connected by the tblUserRole table. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The lkpView table defines various "views" (not SQL views) a role might use to access data from the same table under various contexts. To keep things simple for this article, there's only one view and everybody has it. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The key security table is tblRoleAccessSelect. For each record, the foreign key SelectId points to a record in the gateway table Employee for a given Role and View. Approved applications no longer see Employee (which has been renamed and hidden), but rather a subset of it silently filtered by the security table tblRoleAccessSelect. Users will not be aware of this restriction except that rows in the filtered table will appear/disappear whenever changes are made to user data or the tblRoleAccessSelectFilter table. That's because the key security table is dynamically populated by whatever filters have been added or deleted in the tblRoleAccessSelectFilter table. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The tblRoleAccessSelectFilter table assigns any number of filters to each role and view. Any such filter is simply a query with one column SelectId that lists what rows are available in the Employee table (which is our gateway table for his article). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The main problem is to dynamically change tblRoleAccessSelect whenever user data changes (which will affect the output of those filters), or when changes are made to the tblRoleAccessSelectFilter table. Triggers and stored procedures do this automatically. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The lkpAccessRow and lkpAccessColumn tables define additional row- and column- oriented restrictions that each app could automatically impose using front-end code provided by the model: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Fig. 3&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For rows, you may allow/disallow editing, adding, or deleting. For columns, you may allow hiding or locking (or specify full control). A given user may have several roles and views, so the highest priority wins in each case. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The tblRoleAccessRow table assigns available row-level restrictions to various roles and views. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The tblRoleAccessColumn table assigns available column-level restrictions to various roles and views. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
It's these tables that the HR application's front-end code uses to decide how to edit rows that have been made available to it, and what columns it can see. This code may be further customized if you define more restrictions in the lkpAccessRow and lkpAccessColumn tables. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
How The Model Works (server side)&lt;br /&gt;
&lt;br /&gt;
The key security table tblRoleAccessSelect points to those records in the gateway table Employee that may be seen by any given role and view. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
If a row is updated/inserted into the gateway table, the following trigger passes its key to the stored proc sqRoleAccessSelectSync. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This proc insures that the correct rows in the key security table are inserted or deleted, to re-synchronize with all of the filters assigned to each role and view: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
ALTER TRIGGER [dbo].[TR_Employee_Update_Insert] ON [dbo].[Employee] &lt;br /&gt;
&lt;br /&gt;
AFTER UPDATE, INSERT&lt;br /&gt;
&lt;br /&gt;
AS&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- No counting&lt;br /&gt;
&lt;br /&gt;
SET NOCOUNT ON&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Declarations&lt;br /&gt;
&lt;br /&gt;
DECLARE @MyId INT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- If exactly one record updated/inserted&lt;br /&gt;
&lt;br /&gt;
IF (SELECT COUNT(*) FROM INSERTED) = 1&lt;br /&gt;
&lt;br /&gt;
BEGIN&lt;br /&gt;
&lt;br /&gt;
-- Determine key of record&lt;br /&gt;
&lt;br /&gt;
SELECT @MyId = (SELECT EmployeeId FROM INSERTED)&lt;br /&gt;
&lt;br /&gt;
-- Synchronize security table for record updated/inserted&lt;br /&gt;
&lt;br /&gt;
EXECUTE sqRoleAccessSelectSync @MyId&lt;br /&gt;
&lt;br /&gt;
END&lt;br /&gt;
&lt;br /&gt;
In other words, when user data changes, the key security table will always be in sync with the filters initially used to populate it. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The sole task of the stored proc sqRoleAccessSelectSync is to insure that for each role, view and filter in the table tblRoleAccessSelectFilter, the row (role, view, Id) belongs to the table tblRoleAccessSelect if and only if Id is selected by at least one of the filters in that table with the same role and view: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
ALTER PROCEDURE [dbo].[sqRoleAccessSelectSync]&lt;br /&gt;
&lt;br /&gt;
(&lt;br /&gt;
&lt;br /&gt;
@Id INT&lt;br /&gt;
&lt;br /&gt;
)&lt;br /&gt;
&lt;br /&gt;
AS&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
&lt;br /&gt;
For each Role, View, and Filter in tblRoleAccessSelectFilter, &lt;br /&gt;
&lt;br /&gt;
check if @Id belongs to that filter's result set.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Insure that (Role, View, @Id) belongs to RoleAccessSelect if, and only if, &lt;br /&gt;
&lt;br /&gt;
@Id belongs to at least one of the result sets for that Role and View.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Note that each Role, View combination may be associated with multiple Filters.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
So if @Id belongs to the result set of one filter, care must be taken to avoid &lt;br /&gt;
&lt;br /&gt;
deleting the corresponding row in RoleAccessSelect if @Id does not belong to the &lt;br /&gt;
&lt;br /&gt;
result set of another filter with the same Role and View.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Avoid this by ordering the Filters by Role and View and using a flag to check &lt;br /&gt;
&lt;br /&gt;
for an inserted @Id for all Filters of a given Role and View.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Reset that flag when a new Role and View combination appears. &lt;br /&gt;
&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Declarations&lt;br /&gt;
&lt;br /&gt;
DECLARE @RoleIdCurrent INT -- current Role for all Filters being checked&lt;br /&gt;
&lt;br /&gt;
DECLARE @ViewIdCurrent INT -- current View for all Filters being checked&lt;br /&gt;
&lt;br /&gt;
DECLARE @ynInserted INT -- inserted flag for all Filters being checked with given Role, View&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Declarations for cursor&lt;br /&gt;
&lt;br /&gt;
DECLARE @RoleId INT&lt;br /&gt;
&lt;br /&gt;
DECLARE @ViewId INT&lt;br /&gt;
&lt;br /&gt;
DECLARE @FilterName NVARCHAR(256)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Cursor for tblRoleAccessSelectFilter listing Roles, Views, and Filters (ordered by Role, View)&lt;br /&gt;
&lt;br /&gt;
DECLARE curTable CURSOR&lt;br /&gt;
&lt;br /&gt;
FOR &lt;br /&gt;
&lt;br /&gt;
SELECT RoleId, ViewId, FilterName FROM dbo.tblRoleAccessSelectFilter ORDER BY RoleId, ViewId&lt;br /&gt;
&lt;br /&gt;
FOR READ ONLY&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Set inserted flag to off&lt;br /&gt;
&lt;br /&gt;
SET @ynInserted = 0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Open cursor&lt;br /&gt;
&lt;br /&gt;
OPEN curTable &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Fetch first row&lt;br /&gt;
&lt;br /&gt;
FETCH NEXT FROM curTable INTO @RoleId, @ViewId, @FilterName&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Save current Role, View combination for which inserted check will be monitored&lt;br /&gt;
&lt;br /&gt;
SET @RoleIdCurrent = @RoleId&lt;br /&gt;
&lt;br /&gt;
SET @ViewIdCurrent = @ViewId&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Do this while rows are fetched and inserted @Id does not exist for current Role, View&lt;br /&gt;
&lt;br /&gt;
-- If inserted @Id exists, reset inserted flag to prevent possible deletion later on for current Role, View&lt;br /&gt;
&lt;br /&gt;
-- Reset inserted flag to off if new Role, View combination appear&lt;br /&gt;
&lt;br /&gt;
While @@FETCH_STATUS = 0 &lt;br /&gt;
&lt;br /&gt;
BEGIN&lt;br /&gt;
&lt;br /&gt;
-- Check if @ID is in filter and insert/delete corresponding row into tblRoleAccessSelect if it is/is not&lt;br /&gt;
&lt;br /&gt;
-- But only do this if inserted flag is off (otherwise there's nothing more to do for current Row, View)&lt;br /&gt;
&lt;br /&gt;
IF @ynInserted = 0&lt;br /&gt;
&lt;br /&gt;
EXECUTE sqRoleAccessSelectSync2&lt;br /&gt;
&lt;br /&gt;
@Id = @Id,&lt;br /&gt;
&lt;br /&gt;
@RoleId = @RoleId, &lt;br /&gt;
&lt;br /&gt;
@ViewId = @ViewId, &lt;br /&gt;
&lt;br /&gt;
@FilterName = @FilterName,&lt;br /&gt;
&lt;br /&gt;
@ynInserted = @ynInserted OUTPUT&lt;br /&gt;
&lt;br /&gt;
FETCH NEXT FROM curTable INTO @RoleId, @ViewId, @FilterName&lt;br /&gt;
&lt;br /&gt;
-- If Row, View combination changes, reset inserted flag to off and continue&lt;br /&gt;
&lt;br /&gt;
IF (@RoleId &amp;lt;&amp;gt; @RoleIdCurrent) OR (@ViewId &amp;lt;&amp;gt; @ViewIdCurrent)&lt;br /&gt;
&lt;br /&gt;
BEGIN&lt;br /&gt;
&lt;br /&gt;
SET @ynInserted = 0&lt;br /&gt;
&lt;br /&gt;
SET @RoleIdCurrent = @RoleId&lt;br /&gt;
&lt;br /&gt;
SET @ViewIdCurrent = @ViewId&lt;br /&gt;
&lt;br /&gt;
END&lt;br /&gt;
&lt;br /&gt;
END&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Close cursor&lt;br /&gt;
&lt;br /&gt;
CLOSE curTable&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Deallocate cursor&lt;br /&gt;
&lt;br /&gt;
DEALLOCATE curTable&lt;br /&gt;
&lt;br /&gt;
It does this by calling the stored proc sqRoleAccessSelectSync2 which insures that the row (role, view, Id) belongs to the table tblRoleAccessSelect if and only if Id is selected by the passed filter. However, this proc passes back to its caller a flag indicating whether an insertion occurred (or the row (role, view, Id) was already found to exist) so that the calling program knows when to avoid deleting rows unnecessarily: ALTER PROCEDURE [dbo].[sqRoleAccessSelectSync2]&lt;br /&gt;
&lt;br /&gt;
(&lt;br /&gt;
&lt;br /&gt;
@Id INT,&lt;br /&gt;
&lt;br /&gt;
@RoleId INT,&lt;br /&gt;
&lt;br /&gt;
@ViewId INT,&lt;br /&gt;
&lt;br /&gt;
@FilterName NVARCHAR(256),&lt;br /&gt;
&lt;br /&gt;
@ynInserted INT OUTPUT&lt;br /&gt;
&lt;br /&gt;
)&lt;br /&gt;
&lt;br /&gt;
AS&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
&lt;br /&gt;
Insure that (RoleId, ViewId, @Id) belongs to RoleAccessSelect &lt;br /&gt;
&lt;br /&gt;
if @Id belongs to the result set of filter @FilterName (otherwise delete it).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Note that this call could delete (RoleId, ViewId, @Id) in tblRoleAccessSelect after&lt;br /&gt;
&lt;br /&gt;
an earlier call inserted it, or discovered it was already inserted, for the same &lt;br /&gt;
&lt;br /&gt;
RoleId, ViewId.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
So the calling script must insure that this doesn't happen by monitoring the value &lt;br /&gt;
&lt;br /&gt;
of @ynInserted set by this call. &lt;br /&gt;
&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
BEGIN&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SET NOCOUNT ON&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Declarations&lt;br /&gt;
&lt;br /&gt;
DECLARE @SQLString NVARCHAR(1028)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Set inserted flag off&lt;br /&gt;
&lt;br /&gt;
SET @ynInserted = 0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Check if @Id belongs to the result set of filter @FilterName&lt;br /&gt;
&lt;br /&gt;
-- Use temporary table to avoid result sets in caller&lt;br /&gt;
&lt;br /&gt;
-- Note that SelectId is always a single column in @FilterName, by convention&lt;br /&gt;
&lt;br /&gt;
SET @SQLString = N'SELECT TOP 1 * INTO #t FROM dbo.' + @FilterName + ' WHERE (SelectId = ' + CAST(@Id AS NVARCHAR(10)) + ')'&lt;br /&gt;
&lt;br /&gt;
EXEC sp_executesql @SQLString&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- If it is&lt;br /&gt;
&lt;br /&gt;
IF (@@ROWCOUNT = 1) &lt;br /&gt;
&lt;br /&gt;
BEGIN&lt;br /&gt;
&lt;br /&gt;
-- Check if it is in tblRoleAccessSelect with given Role and View&lt;br /&gt;
&lt;br /&gt;
SET @SQLString = N'SELECT SelectId FROM tblRoleAccessSelect WHERE SelectId = ' + CAST(@Id AS NVARCHAR(10)) + ' AND RoleId = ' + CAST(@RoleId AS NVARCHAR(10)) + ' AND ViewId = ' + CAST(@ViewId AS NVARCHAR(10))&lt;br /&gt;
&lt;br /&gt;
EXEC sp_executesql @SQLString&lt;br /&gt;
&lt;br /&gt;
-- If it isn't&lt;br /&gt;
&lt;br /&gt;
IF (@@ROWCOUNT = 0) &lt;br /&gt;
&lt;br /&gt;
BEGIN&lt;br /&gt;
&lt;br /&gt;
-- Insert it &lt;br /&gt;
&lt;br /&gt;
SET @SQLString = N'INSERT INTO tblRoleAccessSelect VALUES (' + CAST(@RoleId AS NVARCHAR(10)) + ',' + CAST(@ViewId AS NVARCHAR(10)) + ',' + CAST(@Id AS NVARCHAR(10)) + ')'&lt;br /&gt;
&lt;br /&gt;
EXEC sp_executesql @SQLString&lt;br /&gt;
&lt;br /&gt;
-- Set inserted flag on (because it has just been inserted)&lt;br /&gt;
&lt;br /&gt;
SET @ynInserted = 1&lt;br /&gt;
&lt;br /&gt;
END&lt;br /&gt;
&lt;br /&gt;
-- If it is&lt;br /&gt;
&lt;br /&gt;
ELSE&lt;br /&gt;
&lt;br /&gt;
-- Set inserted flag on (because it was already inserted)&lt;br /&gt;
&lt;br /&gt;
SET @ynInserted = 1&lt;br /&gt;
&lt;br /&gt;
END&lt;br /&gt;
&lt;br /&gt;
ELSE&lt;br /&gt;
&lt;br /&gt;
-- If it isn't, then it can't be for this (or any other) Role, View combination&lt;br /&gt;
&lt;br /&gt;
BEGIN&lt;br /&gt;
&lt;br /&gt;
-- So delete it for current Role, View combination&lt;br /&gt;
&lt;br /&gt;
SET @SQLString = N'DELETE FROM tblRoleAccessSelect WHERE RoleId = ' + CAST(@RoleId AS NVARCHAR(10)) + ' AND ViewId = ' + CAST(@ViewId AS NVARCHAR(10)) + ' AND SelectId = ' + CAST(@Id AS NVARCHAR(10)) &lt;br /&gt;
&lt;br /&gt;
EXEC sp_executesql @SQLString&lt;br /&gt;
&lt;br /&gt;
END&lt;br /&gt;
&lt;br /&gt;
END&lt;br /&gt;
&lt;br /&gt;
It should be noted that if a row is deleted from the Employee table, the (cascading delete) relationship connecting it to the key security table takes affect. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
A subtle condition on the model is that if a row can/cannot be seen in the gateway table by a given filter, then it remains that way no matter what other other rows are modified. For example, if a filter sees an Employee because they live in Vancouver, that wouldn't change if someone else moves to/from that city. For a bizarre example, a filter that sees everything if and only if there are exactly 100 rows would be invalid. This condition is always satisfied by ordinary filtering for our purposes. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Of course, we must also handle the event where rows have been inserted/deleted in the tblRoleAccessSelectFilter table (updates are not allowed). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For insertions, the following trigger TR_tblRoleAccessSelectFilter_Insert on that table is used to populate the key security table with key values for the inserted role and view that aren't already present (to avoid duplicates): &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
ALTER TRIGGER [dbo].[TR_tblRoleAccessSelectFilter_Insert] ON [dbo].[tblRoleAccessSelectFilter] &lt;br /&gt;
&lt;br /&gt;
FOR INSERT&lt;br /&gt;
&lt;br /&gt;
AS&lt;br /&gt;
&lt;br /&gt;
BEGIN&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Declarations&lt;br /&gt;
&lt;br /&gt;
DECLARE @SQLString NVARCHAR(1028)&lt;br /&gt;
&lt;br /&gt;
DECLARE @RoleId INT&lt;br /&gt;
&lt;br /&gt;
DECLARE @ViewId INT&lt;br /&gt;
&lt;br /&gt;
DECLARE @FilterName NVARCHAR(256) &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Do nothing unless INSERTED contains exactly one row&lt;br /&gt;
&lt;br /&gt;
IF @@ROWCOUNT &amp;lt;&amp;gt; 1 &lt;br /&gt;
&lt;br /&gt;
RETURN&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Avoid extra result sets&lt;br /&gt;
&lt;br /&gt;
SET NOCOUNT ON&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Get RoleId, ViewId, Filter from record that's been INSERTED&lt;br /&gt;
&lt;br /&gt;
SELECT @RoleId = (SELECT RoleId FROM INSERTED)&lt;br /&gt;
&lt;br /&gt;
SELECT @ViewId = (SELECT ViewId FROM INSERTED)&lt;br /&gt;
&lt;br /&gt;
SELECT @FilterName = (SELECT FilterName FROM INSERTED)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Insert into tblRoleAccessSelect&lt;br /&gt;
&lt;br /&gt;
-- Note that @FilterName must name an existing filter (otherwise insertion will fail)&lt;br /&gt;
&lt;br /&gt;
SET @SQLString =&lt;br /&gt;
&lt;br /&gt;
N'INSERT INTO tblRoleAccessSelect(RoleId,ViewId,SelectId) SELECT D1.RoleId,D1.ViewId,D1.SelectId FROM&lt;br /&gt;
&lt;br /&gt;
(SELECT ' + CAST(@RoleId as NVARCHAR(10)) + ' AS RoleId,' + CAST(@ViewId as NVARCHAR(10)) + ' AS ViewId,' + @FilterName + '.SelectId FROM ' + @FilterName + ') D1 LEFT OUTER JOIN tblRoleAccessSelect ON ' + 'D1.RoleId = tblRoleAccessSelect.RoleId AND ' + 'D1.ViewId = tblRoleAccessSelect.ViewId AND D1.SelectId = tblRoleAccessSelect.SelectId WHERE tblRoleAccessSelect.SelectId IS NULL' &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
EXEC sp_executesql @SQLString&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
END&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Finalization&lt;br /&gt;
&lt;br /&gt;
SET NOCOUNT OFF&lt;br /&gt;
&lt;br /&gt;
Deletions are a little more complicated: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
ALTER TRIGGER [dbo].[TR_tblRoleAccessSelectFilter_Delete] ON [dbo].[tblRoleAccessSelectFilter] &lt;br /&gt;
&lt;br /&gt;
FOR DELETE&lt;br /&gt;
&lt;br /&gt;
AS&lt;br /&gt;
&lt;br /&gt;
BEGIN&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Declarations&lt;br /&gt;
&lt;br /&gt;
DECLARE @SQLString NVARCHAR(2048) -- outer SQL string&lt;br /&gt;
&lt;br /&gt;
DECLARE @SQLString1 NVARCHAR(2048) -- inner SQL string consisting of UNIONs (variable number)&lt;br /&gt;
&lt;br /&gt;
DECLARE @RoleId INT&lt;br /&gt;
&lt;br /&gt;
DECLARE @ViewId INT&lt;br /&gt;
&lt;br /&gt;
DECLARE @FilterName NVARCHAR(256) &lt;br /&gt;
&lt;br /&gt;
DECLARE @RoleIdSave INT&lt;br /&gt;
&lt;br /&gt;
DECLARE @ViewIdSave INT&lt;br /&gt;
&lt;br /&gt;
DECLARE @FilterNameSave NVARCHAR(256)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Do nothing unless DELETED contains exactly one row&lt;br /&gt;
&lt;br /&gt;
IF (SELECT COUNT(*) FROM DELETED) &amp;lt;&amp;gt; 1 &lt;br /&gt;
&lt;br /&gt;
RETURN&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Avoid extra result sets&lt;br /&gt;
&lt;br /&gt;
SET NOCOUNT ON&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Get @RoleId, @ViewId, @Filter from DELETED record&lt;br /&gt;
&lt;br /&gt;
SELECT @RoleId = (SELECT RoleId FROM DELETED)&lt;br /&gt;
&lt;br /&gt;
SELECT @ViewId = (SELECT ViewId FROM DELETED)&lt;br /&gt;
&lt;br /&gt;
SELECT @FilterName = (SELECT FilterName FROM DELETED)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Save Role, View and Filter for use in building SQL outer string&lt;br /&gt;
&lt;br /&gt;
SET @RoleIdSave = @RoleId&lt;br /&gt;
&lt;br /&gt;
SET @ViewIdSave = @ViewId&lt;br /&gt;
&lt;br /&gt;
SET @FilterNameSave = @FilterName&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Start building inner SQL string&lt;br /&gt;
&lt;br /&gt;
SET @SQLString1 = 'SELECT SelectId FROM ' + @FilterName &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Cursor for tblRoleAccessSelectFilter listing Filters &lt;br /&gt;
&lt;br /&gt;
-- with the same Roles, Views as DELETED record&lt;br /&gt;
&lt;br /&gt;
DECLARE curTable CURSOR&lt;br /&gt;
&lt;br /&gt;
FOR &lt;br /&gt;
&lt;br /&gt;
SELECT FilterName FROM dbo.tblRoleAccessSelectFilter &lt;br /&gt;
&lt;br /&gt;
WHERE&lt;br /&gt;
&lt;br /&gt;
RoleId = @RoleId AND&lt;br /&gt;
&lt;br /&gt;
ViewId = @ViewId &lt;br /&gt;
&lt;br /&gt;
FOR READ ONLY&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Open cursor&lt;br /&gt;
&lt;br /&gt;
OPEN curTable &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Fetch next row (note that deleted row does not belong to cursor)&lt;br /&gt;
&lt;br /&gt;
FETCH NEXT FROM curTable INTO @FilterName&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Continue building SQL inner string&lt;br /&gt;
&lt;br /&gt;
While @@FETCH_STATUS = 0 &lt;br /&gt;
&lt;br /&gt;
BEGIN&lt;br /&gt;
&lt;br /&gt;
SET @SQLString1 = @SQLString1 + ' UNION ALL SELECT SelectId FROM ' + @FilterName &lt;br /&gt;
&lt;br /&gt;
FETCH NEXT FROM curTable INTO @FilterName&lt;br /&gt;
&lt;br /&gt;
END&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Delete from tblRoleAccessSelect (explanation below)&lt;br /&gt;
&lt;br /&gt;
SET @SQLString = &lt;br /&gt;
&lt;br /&gt;
N'DELETE FROM tblRoleAccessSelect WHERE &lt;br /&gt;
&lt;br /&gt;
RoleId = ' + CAST(@RoleIdSave AS NVARCHAR(10)) + ' AND&lt;br /&gt;
&lt;br /&gt;
ViewId = ' + CAST(@ViewIdSave AS NVARCHAR(10)) + ' AND&lt;br /&gt;
&lt;br /&gt;
SelectId IN&lt;br /&gt;
&lt;br /&gt;
(&lt;br /&gt;
&lt;br /&gt;
SELECT tblRoleAccessSelect.SelectId FROM tblRoleAccessSelect&lt;br /&gt;
&lt;br /&gt;
INNER JOIN ' +&lt;br /&gt;
&lt;br /&gt;
@FilterNameSave +&lt;br /&gt;
&lt;br /&gt;
' ON&lt;br /&gt;
&lt;br /&gt;
tblRoleAccessSelect.SelectId = ' + @FilterNameSave + '.SelectId&lt;br /&gt;
&lt;br /&gt;
INNER JOIN&lt;br /&gt;
&lt;br /&gt;
(&lt;br /&gt;
&lt;br /&gt;
SELECT SelectId FROM &lt;br /&gt;
&lt;br /&gt;
(' +&lt;br /&gt;
&lt;br /&gt;
@SQLString1 + &lt;br /&gt;
&lt;br /&gt;
') d1&lt;br /&gt;
&lt;br /&gt;
GROUP BY SelectId&lt;br /&gt;
&lt;br /&gt;
HAVING COUNT(*) = 1&lt;br /&gt;
&lt;br /&gt;
) d2&lt;br /&gt;
&lt;br /&gt;
ON&lt;br /&gt;
&lt;br /&gt;
tblRoleAccessSelect.SelectId = d2.SelectId&lt;br /&gt;
&lt;br /&gt;
)'&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
&lt;br /&gt;
Sample deletion for filter ftr1 but where ftr2 has same Role, View.&lt;br /&gt;
&lt;br /&gt;
Avoid deleting any SelectId belonging to ftr1 if it also belongs to ftr2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
DELETE &lt;br /&gt;
&lt;br /&gt;
FROM tblRoleAccessSelect WHERE&lt;br /&gt;
&lt;br /&gt;
RoleId = 1 AND&lt;br /&gt;
&lt;br /&gt;
ViewId = 1 AND&lt;br /&gt;
&lt;br /&gt;
SelectId IN&lt;br /&gt;
&lt;br /&gt;
(&lt;br /&gt;
&lt;br /&gt;
-- Find the SelectId values in tblRoleAccessSelect belonging to ftr1&lt;br /&gt;
&lt;br /&gt;
SELECT tblRoleAccessSelect.SelectId FROM tblRoleAccessSelect&lt;br /&gt;
&lt;br /&gt;
INNER JOIN&lt;br /&gt;
&lt;br /&gt;
ftr1&lt;br /&gt;
&lt;br /&gt;
ON&lt;br /&gt;
&lt;br /&gt;
tblRoleAccessSelect.SelectId = ftr1.SelectId&lt;br /&gt;
&lt;br /&gt;
-- But make sure they don't also belong to ftr2 &lt;br /&gt;
&lt;br /&gt;
INNER JOIN&lt;br /&gt;
&lt;br /&gt;
(&lt;br /&gt;
&lt;br /&gt;
-- UNION of SelectId for two filters without duplication (but where COUNT = 1).&lt;br /&gt;
&lt;br /&gt;
-- This guarantees that each SelectId belongs to exactly one filter. &lt;br /&gt;
&lt;br /&gt;
SELECT SelectId FROM&lt;br /&gt;
&lt;br /&gt;
(&lt;br /&gt;
&lt;br /&gt;
-- UNION of SelectId for two filters (with duplication)&lt;br /&gt;
&lt;br /&gt;
-- This is the inner SQL string&lt;br /&gt;
&lt;br /&gt;
SELECT SelectId FROM ftr1&lt;br /&gt;
&lt;br /&gt;
UNION ALL&lt;br /&gt;
&lt;br /&gt;
SELECT SelectId FROM ftr2&lt;br /&gt;
&lt;br /&gt;
) d1&lt;br /&gt;
&lt;br /&gt;
GROUP BY SelectId&lt;br /&gt;
&lt;br /&gt;
HAVING COUNT(*) = 1&lt;br /&gt;
&lt;br /&gt;
) d2&lt;br /&gt;
&lt;br /&gt;
ON&lt;br /&gt;
&lt;br /&gt;
tblRoleAccessSelect.SelectId = d2.SelectId&lt;br /&gt;
&lt;br /&gt;
)&lt;br /&gt;
&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
EXEC sp_executesql @SQLString&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Close cursor&lt;br /&gt;
&lt;br /&gt;
CLOSE curTable&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Deallocate cursor&lt;br /&gt;
&lt;br /&gt;
DEALLOCATE curTable&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Finalization&lt;br /&gt;
&lt;br /&gt;
SET NOCOUNT OFF&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
END&lt;br /&gt;
&lt;br /&gt;
Here we have to be careful not to delete a row in the key security table if some other filter with the same role and view still wants it present. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
How The Model Works (client side)&lt;br /&gt;
&lt;br /&gt;
Each client application runs a hidden piece of security code when any form is opened, which automatically provides additional row- or column- oriented security. For example, the following snippet locks or hides any control referenced by the tblRoleAccessColumn table: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Dim reader As SqlDataReader = cmd.ExecuteReader()&lt;br /&gt;
&lt;br /&gt;
While reader.Read() ' Loop through constraints for current test&lt;br /&gt;
&lt;br /&gt;
MySecurity(0) = reader.Item(0).ToString ' ColumnName&lt;br /&gt;
&lt;br /&gt;
MySecurity(1) = reader.Item(1).ToString ' AccessColumnName&lt;br /&gt;
&lt;br /&gt;
For Each ctrl As Control In Me.Controls ' Loop through all controls&lt;br /&gt;
&lt;br /&gt;
' If this is one of the following controls&lt;br /&gt;
&lt;br /&gt;
If (TypeOf ctrl Is TextBox) Or _&lt;br /&gt;
&lt;br /&gt;
(TypeOf ctrl Is Label) Or _&lt;br /&gt;
&lt;br /&gt;
(TypeOf ctrl Is Button) Or _&lt;br /&gt;
&lt;br /&gt;
(TypeOf ctrl Is ListBox) Or _&lt;br /&gt;
&lt;br /&gt;
(TypeOf ctrl Is ListView) Then&lt;br /&gt;
&lt;br /&gt;
If MySecurity(0) = ctrl.Name Then ' Referenced by current constraint&lt;br /&gt;
&lt;br /&gt;
ctrl.Enabled = True ' Enable it and make it visible&lt;br /&gt;
&lt;br /&gt;
ctrl.Visible = True&lt;br /&gt;
&lt;br /&gt;
If MySecurity(1) = "Lock" Then ' Lock it if required&lt;br /&gt;
&lt;br /&gt;
ctrl.Enabled = False&lt;br /&gt;
&lt;br /&gt;
End If&lt;br /&gt;
&lt;br /&gt;
If MySecurity(1) = "Hide" Then ' Hide it if required&lt;br /&gt;
&lt;br /&gt;
ctrl.Visible = False&lt;br /&gt;
&lt;br /&gt;
End If&lt;br /&gt;
&lt;br /&gt;
If MySecurity(1) = "Full" Then ' Full access if required&lt;br /&gt;
&lt;br /&gt;
ctrl.Enabled = True&lt;br /&gt;
&lt;br /&gt;
ctrl.Visible = True&lt;br /&gt;
&lt;br /&gt;
End If&lt;br /&gt;
&lt;br /&gt;
End If&lt;br /&gt;
&lt;br /&gt;
End If&lt;br /&gt;
&lt;br /&gt;
Next&lt;br /&gt;
&lt;br /&gt;
End While&lt;br /&gt;
&lt;br /&gt;
reader.Close()&lt;br /&gt;
&lt;br /&gt;
Strictly speaking, the ColumnName column in the tblRoleAccessColumn table is the form's control name, not the column name of the underlying table. Of course, an app and form name should also be part of the tblRoleAccessColumn table's definition (for greater flexibility) but this is omitted for simplicity. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
A Visual Basic 2005 Express source and executable package for maintaining the model (along with a front-end application and sample database) may be found in the Resource Section below. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example&lt;br /&gt;
&lt;br /&gt;
In this example there are two Users, two Roles, and one View. The Employee table has 100 randomly generated records. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
User1 has Role1 while User2 has Role1 and Role2 (all views are View1 so it will be ignored in what follows). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Role1 has filter ftr1, which lists those Employees whose names start with A or B: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
CREATE VIEW dbo.ftr1&lt;br /&gt;
&lt;br /&gt;
AS&lt;br /&gt;
&lt;br /&gt;
SELECT EmployeeId AS SelectId&lt;br /&gt;
&lt;br /&gt;
FROM dbo.Employee&lt;br /&gt;
&lt;br /&gt;
WHERE (SUBSTRING(LastName, 1, 1) IN ('A', 'B'))&lt;br /&gt;
&lt;br /&gt;
It also has ftr2 (Employees whose names start with B or C). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Role2 has filters ftr3 (Employees whose names start with C or D) and ftr4 (Employees whose names start with E). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When User2 and View1 are selected, clicking RoleAccessSelect yields 42 (Role, View, Select) combinations: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Zoom in &lt;br /&gt;
Open in new window&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Fig. 4&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
A given Employee may appear multiple times if the selected User belongs to multiple Roles (in this case 28 Employees appear 42 times with different Roles and Views). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The SQL query generated for this choice of User, Role and View is: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SELECT DISTINCT &lt;br /&gt;
&lt;br /&gt;
lkpRole.RoleName, &lt;br /&gt;
&lt;br /&gt;
lkpView.ViewName, &lt;br /&gt;
&lt;br /&gt;
Employee.LastName, &lt;br /&gt;
&lt;br /&gt;
tblRoleAccessSelect.SelectId FROM &lt;br /&gt;
&lt;br /&gt;
Employee INNER JOIN &lt;br /&gt;
&lt;br /&gt;
tblRoleAccessSelect ON &lt;br /&gt;
&lt;br /&gt;
Employee.EmployeeId = tblRoleAccessSelect.SelectId INNER JOIN &lt;br /&gt;
&lt;br /&gt;
lkpRole ON &lt;br /&gt;
&lt;br /&gt;
tblRoleAccessSelect.RoleId = lkpRole.RoleId INNER JOIN &lt;br /&gt;
&lt;br /&gt;
tblUserRole ON &lt;br /&gt;
&lt;br /&gt;
lkpRole.RoleId = tblUserRole.RoleId INNER JOIN &lt;br /&gt;
&lt;br /&gt;
lkpView ON &lt;br /&gt;
&lt;br /&gt;
tblRoleAccessSelect.ViewId = lkpView.ViewId INNER JOIN &lt;br /&gt;
&lt;br /&gt;
lkpUser ON &lt;br /&gt;
&lt;br /&gt;
tblUserRole.UserId = lkpUser.UserId &lt;br /&gt;
&lt;br /&gt;
WHERE &lt;br /&gt;
&lt;br /&gt;
(lkpUser.UserName = 'User2') AND &lt;br /&gt;
&lt;br /&gt;
(1=1) AND &lt;br /&gt;
&lt;br /&gt;
(lkpView.ViewName = 'View1') &lt;br /&gt;
&lt;br /&gt;
ORDER BY &lt;br /&gt;
&lt;br /&gt;
lkpRole.RoleName, &lt;br /&gt;
&lt;br /&gt;
lkpView.ViewName, &lt;br /&gt;
&lt;br /&gt;
Employee.LastName&lt;br /&gt;
&lt;br /&gt;
Meanwhile, on any apps using the standard security snap-in: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Role1 hides the control tbEmail &lt;br /&gt;
&lt;br /&gt;
Role2 hides the controls tbBirthDate and tbSIN and locks the control tbEmail &lt;br /&gt;
&lt;br /&gt;
Any controls that don't exist are simply ignored. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When the application opens, 28 unique records may be seen by User2 and View1 when Employees is clicked: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Zoom in &lt;br /&gt;
Open in new window&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Fig. 5&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The security stored proc sqMain generates the SQL that drives this listing: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SELECT &lt;br /&gt;
&lt;br /&gt;
EmployeeId, &lt;br /&gt;
&lt;br /&gt;
LastName, &lt;br /&gt;
&lt;br /&gt;
FirstName, &lt;br /&gt;
&lt;br /&gt;
Address, &lt;br /&gt;
&lt;br /&gt;
City, &lt;br /&gt;
&lt;br /&gt;
Postal, &lt;br /&gt;
&lt;br /&gt;
Prov,&lt;br /&gt;
&lt;br /&gt;
Phone, &lt;br /&gt;
&lt;br /&gt;
Email, &lt;br /&gt;
&lt;br /&gt;
BirthDate, &lt;br /&gt;
&lt;br /&gt;
[SIN]&lt;br /&gt;
&lt;br /&gt;
FROM &lt;br /&gt;
&lt;br /&gt;
Employee &lt;br /&gt;
&lt;br /&gt;
WHERE EmployeeId IN&lt;br /&gt;
&lt;br /&gt;
(&lt;br /&gt;
&lt;br /&gt;
SELECT DISTINCT &lt;br /&gt;
&lt;br /&gt;
tblRoleAccessSelect.SelectId&lt;br /&gt;
&lt;br /&gt;
FROM &lt;br /&gt;
&lt;br /&gt;
tblRoleAccessSelect INNER JOIN&lt;br /&gt;
&lt;br /&gt;
lkpView ON &lt;br /&gt;
&lt;br /&gt;
tblRoleAccessSelect.ViewId = lkpView.ViewId INNER JOIN&lt;br /&gt;
&lt;br /&gt;
tblUserRole ON &lt;br /&gt;
&lt;br /&gt;
tblRoleAccessSelect.RoleId = tblUserRole.RoleId&lt;br /&gt;
&lt;br /&gt;
WHERE &lt;br /&gt;
&lt;br /&gt;
UserId = 2 AND &lt;br /&gt;
&lt;br /&gt;
ViewName = 'View1'&lt;br /&gt;
&lt;br /&gt;
) &lt;br /&gt;
&lt;br /&gt;
ORDER BY &lt;br /&gt;
&lt;br /&gt;
LastName, &lt;br /&gt;
&lt;br /&gt;
FirstName&lt;br /&gt;
&lt;br /&gt;
After selecting an Employee, clicking the Test Security button will lock the Email control and hide the BirthDate and SIN controls (locking has greater priority than hiding, although this priority can be changed in the tblRoleAccessColumn table). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Summary&lt;br /&gt;
This article has presented a simple model for implementing row-oriented security in SQL Server. Although it puts some of the security burden on the client application (so it's more vulnerable) it has proven useful in situations where access to front-end applications is tightly controlled. Also, it's easy to deploy and surprisingly efficient (even when the key security table is large). Furthermore, legacy applications may use it with a simple addition to its front-end code.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-7479253815166452611?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/R-GoN4XuRCI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/7479253815166452611/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2010/04/row-oriented-security-using-triggers.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/7479253815166452611?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/7479253815166452611?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/R-GoN4XuRCI/row-oriented-security-using-triggers.html" title="Row Oriented Security Using Triggers" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2010/04/row-oriented-security-using-triggers.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0QDQ34yfSp7ImA9WxBWEUo.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-1969950306806699563</id><published>2010-02-02T21:36:00.000-08:00</published><updated>2010-02-02T21:36:12.095-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-02-02T21:36:12.095-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="web.config" /><category scheme="http://www.blogger.com/atom/ns#" term="Encrypting Connection Strings" /><title>Encrypting Connection Strings in Web.config</title><content type="html">One of the best practices in ASP.NET is to save your database connection strings in the Web.config file instead of hard-coding it in your code. This allows you to change database servers easily, without needing to modify your code. As an additional protection, it is always better to use integrated Windows security to access your database, rather than using SQL Server authentication, and thus including your SQL server credentials in the connection string. Either way, it's not such a good idea to save your connection strings as plain text in Web.config -- you should ideally encrypt the connection strings so that it leaves no chance for a potential hacker to easily get more information about your database server. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
In ASP.NET 2.0, Microsoft has taken this further by allowing you to encrypt the connection strings in Web.config, all without much plumbing on your part. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Using the DataProtectionConfigurationProvider and RSAProtectedConfigurationProvider for Encryption&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
To see how you can encrypt the connection strings stored in the Web.config file, you will configure a GridView control to bind to an SqlDataSource control. The connection string used by the SqlDataSource control is saved in the Web.config file. You then encrypt the connection strings, using the two Protection Configuration Providers available in .NET 2.0. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
1. Launch Visual Studio 2005 and create a new Website project. Name the project as C:\EncryptConfig. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. Populate the default form with a GridView control and configure it to use an SqlDataSource control. Configure the SqlDataSource control using the Choose Data Source drop-down list in the GridView Tasks menu (see Figure 1) to connect to the Authors table in the pubs database in SQL Server 2000. In particular, ensure that the connection string to the database is stored in Web.config. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Figure 1: Configuring the GridView control.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The Source View of the GridView and SqlDataSource controls look like this: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
DataSourceID="SqlDataSource1" AutoGenerateColumns="False"&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SortExpression="au_id"&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SortExpression="au_lname"&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SortExpression="au_fname"&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SortExpression="phone"&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SortExpression="address"&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SortExpression="city"&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SortExpression="state"&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SortExpression="zip"&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
DataField="contract"&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SelectCommand="SELECT * FROM [authors]"&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
ConnectionString=""&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. The default form should now look very much like Figure 2. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Figure 2: The GridView and SqlDataSource control.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. The Web.config file will now contain the connection string: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
connectionString="Data Source="(local)";&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Initial Catalog=pubs;Integrated Security=True"&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
providerName="System.Data.SqlClient" /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
... &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
5. Switch to the code-behind of the default form and add in the EncryptConnStr() method. The EncryptConnStr() method first retrieves the Web.config file and then applies encryption to the specified section (, in this case) of the file using the Protection Configuration Provider indicated (passed in via the protectionProvider parameter). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Imports System.Configuration&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Imports System.Web.Security&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Public Sub EncryptConnStr(ByVal protectionProvider As String)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'---open the web.config file&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Dim config As Configuration = _&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
ConfigurationManager.OpenWebConfiguration( _&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Request.ApplicationPath)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'---indicate the section to protect&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Dim section As ConfigurationSection = _&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
config.Sections("connectionStrings")&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'---specify the protection provider&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
section.SectionInformation.ProtectSection(protectionProvider)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'---Apple the protection and update&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
config.Save()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
End Sub&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
6. Also, add in the DecryptConnStr() method. The DecryptConnStr() method will decrypt the encrypted connection strings in web.config: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Public Sub DecryptConnStr()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Dim config As Configuration = _&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
ConfigurationManager.OpenWebConfiguration( _&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Request.ApplicationPath)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Dim section As ConfigurationSection = _&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
config.Sections("connectionStrings")&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
section.SectionInformation.UnProtectSection()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
config.Save()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
End Sub&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
7. Note that the UnProtectSection() method, unlike ProtectSection(), does not require a provider name. When a section is encrypted, information regarding the provider that performed the encryption is stored in the Web.config file. UnProtectSection will use that information to determine which provider to use to decrypt the data. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
8. There are two protection configuration providers available for your use -- DataProtectionConfigurationProvider and RSAProtectedConfigurationProvider. The DataProtectionConfigurationProvider uses the Windows DPAPI to perform encryption. The RSAProtectedConfigurationProvider uses the public-key algorithm available in the .NET Framework's RSACryptoServiceProvider class to perform encryption. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
9. To test the EncryptConnStr() method, call it in the Form_Load event (you should only call the Encypt() method once) with the indicated Protection Configuration Provider: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Protected Sub Page_Load(ByVal sender As Object, _&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
ByVal e As System.EventArgs) _&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Handles Me.Load&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Encrypt("DataProtectionConfigurationProvider")&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'--or--&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
' Encrypt("RSAProtectedConfigurationProvider")&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
End Sub&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
10. If you use the DataProtectionConfigurationProvider provider, your connection string will now look like this: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
inheritedByChildren="False" /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
AQAAANCMnd8.............p4nZCszebgXs=&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
11. If you use the RSAProtectedConfigurationProvider provider, your connection string will now look like this: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
inheritedByChildren="False" /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
xmlns="http://www.w3.org/2001/04/xmlenc#"&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
"http://www.w3.org/2001/04/xmlenc#tripledes-cbc" /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
"http://www.w3.org/2001/04/xmlenc#rsa-1_5" /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
RSA Key&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
J7HkJ/9i...c2usLA=&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
k6w9KUZ...5p2AP5gQ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
12. Notice that the section added to Web.config contains information needed to decrypt the connection strings. More importantly, doesn't contain the decryption key. For example, if you use the Windows DataProtectionConfigurationProvider, the decryption key is auto-generated and saved in the Windows Local Security Authority (LSA). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
13. The really cool thing about encrypting the Web.config file is that the process of decrypting the required connection string is totally transparent to the developer. Controls and code that need to access the connection string will automatically know how to encrypt the encrypted information. However, if you want to decrypt the Web.config file so that you can make modifications to it, simply call the DecryptConnStr() method. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can check if a section is protected by using the IsProtected property, like this: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
If Not section.SectionInformation.IsProtected Then&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
section.SectionInformation.ProtectSection(protectionProvider)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
config.Save()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
End If&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can encrypt almost all the sections in Web.config, with the exceptions of some sections such as and . This is because these sections are accessed by parts of the unmanaged code in ASP.NET. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Programmatically Add a New Connection String&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can programmatically add a new connection string to the Web.config file, even though it is encrypted. The following AddConnString() method adds a new connection string named NorthWindConnectionString to an encrypted Web.config: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Public Sub AddConnString()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'---add a connection string to Web.config&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Dim config As Configuration = _&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
ConfigurationManager.OpenWebConfiguration( _&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Request.ApplicationPath)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
config.ConnectionStrings.ConnectionStrings.Add _&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
(New ConnectionStringSettings("NorthwindConnectionString", _&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
"server=localhost;database=northwind;integrated security=true"))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
config.Save()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
End Sub&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
If you were to decrypt the Web.config file, you will find the newly added connection string: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
(local)";Initial Catalog=pubs;Integrated Security=True"&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
providerName="System.Data.SqlClient" /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Summary&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
In this article, I have discussed how easy it is to encrypt connection strings in the Web.config file using the two Protection Configuration Providers -- DataProtectionConfigurationProvider and RSAProtectedConfigurationProvider -- for encryption. Since it is now so easy to perform encryption in Web.config, there is really no reason for not doing so.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-1969950306806699563?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/v7-t4zbCCq4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/1969950306806699563/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2010/02/encrypting-connection-strings-in.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/1969950306806699563?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/1969950306806699563?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/v7-t4zbCCq4/encrypting-connection-strings-in.html" title="Encrypting Connection Strings in Web.config" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2010/02/encrypting-connection-strings-in.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CU4EQXw6eSp7ImA9WxBWEUo.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-900701848679908863</id><published>2010-02-02T21:11:00.000-08:00</published><updated>2010-02-02T21:11:40.211-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-02-02T21:11:40.211-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="temp table approach" /><category scheme="http://www.blogger.com/atom/ns#" term="COUNT(*) OVER() Approach" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL Server 2005 Paging" /><category scheme="http://www.blogger.com/atom/ns#" term="ROW_NUMBER OVER()" /><category scheme="http://www.blogger.com/atom/ns#" term="'two-bites' approach" /><category scheme="http://www.blogger.com/atom/ns#" term="Holy Grail" /><title>SQL Server 2005 Paging – The Holy Grail</title><content type="html">Introduction&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The paging and ranking functions introduced in 2005 are old news by now, but the typical ROW_NUMBER OVER() implementation only solves part of the problem.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Nearly every application that uses paging gives some indication of how many pages (or total records) are in the total result set. The challenge is to query the total number of rows, and return only the desired records with a minimum of overhead? The holy grail solution would allow you to return one page of the results and the total number of rows with no additional I/O overhead.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
In this article, we're going to explore four approaches to this problem and discuss their relative strengths and weaknesses. For the purposes of comparison, we'll be using I/O as a relative benchmark.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The 'two-bites' approach&lt;br /&gt;
&lt;br /&gt;
The most basic approach is the 'two-bites' approach. In this approach you, effectively, run your query twice; querying the total rows in one pass, and querying your result set in the second. The code is pretty straightforward:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
DECLARE @startRow INT ; SET @startrow = 50&lt;br /&gt;
&lt;br /&gt;
SELECTCOUNT(*) AS TotRows&lt;br /&gt;
&lt;br /&gt;
FROM [INFORMATION_SCHEMA].columns&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
;WITH cols&lt;br /&gt;
&lt;br /&gt;
AS&lt;br /&gt;
&lt;br /&gt;
(&lt;br /&gt;
&lt;br /&gt;
SELECT table_name, column_name,&lt;br /&gt;
&lt;br /&gt;
ROW_NUMBER() OVER(ORDER BY table_name, column_name) AS seq&lt;br /&gt;
&lt;br /&gt;
FROM [INFORMATION_SCHEMA].columns&lt;br /&gt;
&lt;br /&gt;
)&lt;br /&gt;
&lt;br /&gt;
SELECT table_name, column_name&lt;br /&gt;
&lt;br /&gt;
FROM cols&lt;br /&gt;
&lt;br /&gt;
WHERE seq BETWEEN @startRow AND @startRow + 49&lt;br /&gt;
&lt;br /&gt;
ORDERBY seq&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
It gives the desired results, but this approach doubles the cost of the query because you query your underlying tables twice:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
(1 row(s) affected)&lt;br /&gt;
&lt;br /&gt;
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;
&lt;br /&gt;
Table 'syscolpars'. Scan count 1, logical reads 46, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;
&lt;br /&gt;
Table 'sysschobjs'. Scan count 1, logical reads 34, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
(50 row(s) affected)&lt;br /&gt;
&lt;br /&gt;
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;
&lt;br /&gt;
Table 'syscolpars'. Scan count 1, logical reads 46, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;
&lt;br /&gt;
Table 'sysschobjs'. Scan count 1, logical reads 34, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The temp table approach&lt;br /&gt;
&lt;br /&gt;
The 'two-bites' approach is especially undesirable if your paged query is very expensive and complex. A common workaround is to write the superset into a temporary table, then query out the subset. This is also the most common way to implement paging pre-2005 (in this case, ROW_NUMBER is superfluous).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
DECLARE @startRow INT ; SET @startrow = 50&lt;br /&gt;
&lt;br /&gt;
CREATETABLE #pgeResults(&lt;br /&gt;
&lt;br /&gt;
id INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,&lt;br /&gt;
&lt;br /&gt;
table_name VARCHAR(255),&lt;br /&gt;
&lt;br /&gt;
column_name VARCHAR(255)&lt;br /&gt;
&lt;br /&gt;
)&lt;br /&gt;
&lt;br /&gt;
INSERTINTO #pgeResults(Table_name, column_name)&lt;br /&gt;
&lt;br /&gt;
SELECT table_name, column_name&lt;br /&gt;
&lt;br /&gt;
FROM [INFORMATION_SCHEMA].columns&lt;br /&gt;
&lt;br /&gt;
ORDERBY [table_name], [column_name]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SELECT@@ROWCOUNT AS TotRows&lt;br /&gt;
&lt;br /&gt;
SELECT Table_Name, Column_Name&lt;br /&gt;
&lt;br /&gt;
FROM #pgeResults&lt;br /&gt;
&lt;br /&gt;
WHERE id between @startrow and @startrow + 49&lt;br /&gt;
&lt;br /&gt;
ORDERBY id&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
DROPTABLE #pgeResults&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Looking at the query plan, you can see that your underlying tables are queried only once but the I/O stats show us that you take an even bigger hit populating the temporary table.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Table '#pgeResults_________________________________________________________________________________________________________000000001A9F'. Scan count 0, logical reads 5599, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;
&lt;br /&gt;
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;
&lt;br /&gt;
Table 'syscolpars'. Scan count 1, logical reads 46, physical reads 0, read-ahead reads 14, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;
&lt;br /&gt;
Table 'sysschobjs'. Scan count 1, logical reads 34, physical reads 0, read-ahead reads 39, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;
&lt;br /&gt;
(2762 row(s) affected)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
(1 row(s) affected)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
(50 row(s) affected)&lt;br /&gt;
&lt;br /&gt;
Table '#pgeResults_________________________________________________________________________________________________________000000001A9F'. Scan count 1, logical reads 3, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
In this case, it would be better to query the tables twice. Maybe some new 2005 functionality can yield a better solution.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The COUNT(*) OVER() Approach&lt;br /&gt;
&lt;br /&gt;
OVER() can also be used with Aggregate Window Functions. For our purposes this means we can do a COUNT(*) without the need for a GROUP BY clause, returning the total count in our result set. The code definitely looks much cleaner and, if your application permits it, you can simply return one dataset (eliminating the overhead of writing to a temp table).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
DECLARE @startRow INT ; SET @startrow = 50&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
;WITH cols&lt;br /&gt;
&lt;br /&gt;
AS&lt;br /&gt;
&lt;br /&gt;
(&lt;br /&gt;
&lt;br /&gt;
SELECT table_name, column_name, &lt;br /&gt;
&lt;br /&gt;
ROW_NUMBER() OVER(ORDER BY table_name, column_name) AS seq, &lt;br /&gt;
&lt;br /&gt;
COUNT(*) OVER() AS totrows&lt;br /&gt;
&lt;br /&gt;
FROM [INFORMATION_SCHEMA].columns&lt;br /&gt;
&lt;br /&gt;
)&lt;br /&gt;
&lt;br /&gt;
SELECT table_name, column_name, totrows&lt;br /&gt;
&lt;br /&gt;
FROM cols&lt;br /&gt;
&lt;br /&gt;
WHERE seq BETWEEN @startRow AND @startRow + 49&lt;br /&gt;
&lt;br /&gt;
ORDERBY seq&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Unfortunately this approach has it's own hidden overhead:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Table 'Worktable'. Scan count 3, logical reads 5724, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;
&lt;br /&gt;
Table 'syscolpars'. Scan count 1, logical reads 46, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;
&lt;br /&gt;
Table 'sysschobjs'. Scan count 1, logical reads 34, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where did that come from? In this case, SQL Server implements the COUNT(*) OVER() by dumping all the data into a hidden spool table, which it then aggregates and joins back to your main output. It does this to avoid re scanning the underlying tables. Although this approach looks the cleanest, it introduces the most overhead. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I've spent most of today cleaning up and old data-paging proc that is both very inefficient and frequently called enough for me to notice it. I've explored probably a dozen other approaches to solving this problem before I came up with the solution below. For the sake of brevity—and because they rest are pretty obscure and equally inefficient­—we'll now skip to the best solution.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The Holy Grail&lt;br /&gt;
&lt;br /&gt;
In theory, ROW_NUMBER() gives you all the information you need because it assigns a sequential number to every single row in your result set. It all falls down, of course, when you only return a subset of your results that don't include the highest sequential number. The solution is to return a 2nd column of sequential numbers, in the reverse order. The total number of the records will always be the sum of the two fields on any given row minus 1 (unless one of your sequences is zero-bound).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
DECLARE @startRow INT ; SET @startrow = 50&lt;br /&gt;
&lt;br /&gt;
;WITH cols&lt;br /&gt;
&lt;br /&gt;
AS&lt;br /&gt;
&lt;br /&gt;
(&lt;br /&gt;
&lt;br /&gt;
SELECT table_name, column_name, &lt;br /&gt;
&lt;br /&gt;
ROW_NUMBER() OVER(ORDER BY table_name, column_name) AS seq, &lt;br /&gt;
&lt;br /&gt;
ROW_NUMBER() OVER(ORDER BY table_name DESC, column_name desc) AS totrows&lt;br /&gt;
&lt;br /&gt;
FROM [INFORMATION_SCHEMA].columns&lt;br /&gt;
&lt;br /&gt;
)&lt;br /&gt;
&lt;br /&gt;
SELECT table_name, column_name, totrows + seq -1 as TotRows&lt;br /&gt;
&lt;br /&gt;
FROM cols&lt;br /&gt;
&lt;br /&gt;
WHERE seq BETWEEN @startRow AND @startRow + 49&lt;br /&gt;
&lt;br /&gt;
ORDERBY seq&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This approach gives us our page of data and the total number of rows with zero additional overhead! (well, maybe one or two ms of CPU time, but that's it) The I/O statistics are identical to querying just the subset of records.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;
&lt;br /&gt;
Table 'syscolpars'. Scan count 1, logical reads 46, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;
&lt;br /&gt;
Table 'sysschobjs'. Scan count 1, logical reads 34, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Compare the stats above with the stats and query below (just returning one page of data).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
;WITH cols&lt;br /&gt;
&lt;br /&gt;
AS&lt;br /&gt;
&lt;br /&gt;
(&lt;br /&gt;
&lt;br /&gt;
SELECT table_name, column_name, &lt;br /&gt;
&lt;br /&gt;
ROW_NUMBER() OVER(ORDER BY table_name, column_name) AS seq&lt;br /&gt;
&lt;br /&gt;
FROM [INFORMATION_SCHEMA].columns&lt;br /&gt;
&lt;br /&gt;
)&lt;br /&gt;
&lt;br /&gt;
SELECT table_name, column_name&lt;br /&gt;
&lt;br /&gt;
FROM cols&lt;br /&gt;
&lt;br /&gt;
WHERE seq BETWEEN @startRow AND @startRow + 49&lt;br /&gt;
&lt;br /&gt;
ORDERBY seq&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;
&lt;br /&gt;
Table 'syscolpars'. Scan count 1, logical reads 46, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;
&lt;br /&gt;
Table 'sysschobjs'. Scan count 1, logical reads 34, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Conclusion&lt;br /&gt;
&lt;br /&gt;
I have found this approach to be best suited for smaller resultsets from complex queries where I/O is the primary bottleneck.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-900701848679908863?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/mPusel6nHdQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/900701848679908863/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2010/02/sql-server-2005-paging-holy-grail.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/900701848679908863?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/900701848679908863?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/mPusel6nHdQ/sql-server-2005-paging-holy-grail.html" title="SQL Server 2005 Paging – The Holy Grail" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2010/02/sql-server-2005-paging-holy-grail.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEQBQ38-fip7ImA9WxBQFEk.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-6595426382632353899</id><published>2010-01-13T21:19:00.000-08:00</published><updated>2010-01-13T21:19:12.156-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-01-13T21:19:12.156-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="common table expressions" /><category scheme="http://www.blogger.com/atom/ns#" term="CTE" /><title>Introduction to Common Table Expressions</title><content type="html">You can use common table expressions (CTEs) starting with SQL Server 2005 to simplify your T-SQL queries. CTEs allow you to separate part of your T-SQL logic from your main query instead of using a view, correlated subquery, or temp table. You define the CTE at the beginning of the statement, and then you can use the CTE in your main query just as if it were a table or view.&lt;br /&gt;
&lt;h2&gt;CTE Syntax&lt;/h2&gt;Once you get acquainted with CTEs, you will probably find them easy to use. Start with the word WITH, followed by a name for your CTE. Follow the name of the CTE with the word AS and a set of parentheses. Inside the parentheses, type in a valid SELECT query. You can then use the CTE in your main query just as if the CTE was a table or view. You can use the CTE in a SELECT, UPDATE, INSERT or DELETE statement, but the CTE itself will be a SELECT statement. Listing 1 shows two sample queries.&lt;br /&gt;
Listing 1: Simple CTE examples&lt;br /&gt;
&lt;pre class="brush: sql;"&gt;WITH emp AS (
    SELECT EmployeeID, FirstName, LastName, E.Title, ManagerID
    FROM HumanResources.Employee AS E 
    INNER JOIN Person.Contact AS C ON E.ContactID = C.ContactID 
    )
SELECT A.EmployeeID, A.FirstName, A.LastName, A.Title,
    A.ManagerID, B.FirstName AS MgrFirstName, 
    B.LastName AS MgrLastName, B.Title AS MgrTitle
FROM emp AS A 
INNER JOIN emp AS B ON A.ManagerID = B.EmployeeID;

WITH emp AS (
    SELECT EmployeeID, FirstName, LastName, E.Title, ManagerID
    FROM HumanResources.Employee AS E 
    INNER JOIN Person.Contact AS C ON E.ContactID = C.ContactID 
    ),
mgr (EmployeeID, MgrFirstName, MgrLastName, MgrTitle) AS (
    SELECT EmployeeID, FirstName, LastName, E.Title
    FROM HumanResources.Employee AS E 
    INNER JOIN Person.Contact AS C ON E.ContactID = C.ContactID 
)
SELECT emp.EmployeeID, FirstName, LastName, Title, ManagerID,
    MgrFirstName, MgrLastName, MgrTitle
FROM emp 
INNER JOIN mgr ON emp.ManagerID = mgr.EmployeeID; 

&lt;/pre&gt;The first query demonstrates how the CTE can be used multiple times within the main query. The main query joins &lt;span class="inlineCode"&gt;emp&lt;/span&gt; to itself to list all the employees who have a manager along with the manager’s name and title. The second query demonstrates that you can define more than one CTE in the same statement. Separate the CTE definitions with a comma. The second CTE, defining the managers, also shows how you can specify aliases for the CTE right after the CTE name instead of relying on the names in the CTE definition. One more thing to keep in mind is that, if the CTE is part of a batch, the previous statement must end in a semi-colon. I have often seen CTE queries with a semi-colon right before the word WITH as a precautionary measure.&lt;br /&gt;
&lt;h2&gt;Uses for CTEs&lt;/h2&gt;Now that you know how to create a CTE, you may wonder when you should use one. One of my favorite uses is to fix a poorly performing correlated subquery. Developers often use correlated subqueries to add one or more aggregate expressions to an otherwise non-aggregate query. Listing 2 shows a correlated subquery and how to get the same results using a better performing CTE.&lt;br /&gt;
Listing 2: Use a CTE to fix a correlated subquery&lt;br /&gt;
&lt;pre class="brush: sql;"&gt;SELECT SalesOrderID, CustomerID, 
    (SELECT COUNT(*) FROM Sales.SalesOrderHeader 
     WHERE CustomerID = S.CustomerID) AS CountOfSales,
    (SELECT AVG(TotalDue) FROM Sales.SalesOrderHeader 
     WHERE CustomerID = S.CustomerID) AS AvgSale,
    (SELECT MIN(TotalDue) FROM Sales.SalesOrderHeader 
     WHERE CustomerID = S.CustomerID) AS LowestSale,
    (SELECT MAX(TotalDue) FROM Sales.SalesOrderHeader 
     WHERE CustomerID = S.CustomerID) AS HighestSale
FROM Sales.SalesOrderHeader AS S;

WITH csales AS (
    SELECT COUNT(*) AS CountOfSales, AVG(TotalDue) AS AvgSale, 
        MIN(TotalDue) AS LowestSale, MAX(TotalDue) AS HighestSale,
        CustomerID 
     FROM Sales.SalesOrderHeader
     GROUP BY CustomerID)
SELECT SalesOrderID, S.CustomerID, CountOfSales, AvgSale, 
     LowestSale, HighestSale 
FROM Sales.SalesOrderHeader AS S 
INNER JOIN csales ON S.CustomerID = csales.CustomerID;
&lt;/pre&gt;The first query lists all of the sales orders and the customer ID for each. It uses four subqueries to calculate the count of sales, average sale, minimum and maximum sales for the customers. The second query produces the same results by including an aggregate query in the CTE. On my computer, the first computer takes three times longer to run than the second query!&lt;br /&gt;
&lt;br /&gt;
Another fantastic T-SQL feature you will find in SQL Server 2005 and 2008 is the ROW_NUMBER and other ranking functions. You can add row numbers to your queries using ROW_NUMBER, but you can not filter the results by including the ROW_NUMBER function in the WHERE clause. Listing 3 shows how you can combine the ROW_NUMBER function with a CTE to solve this problem.&lt;br /&gt;
Listing 3: Use the ROW_NUMBER function with a CTE&lt;br /&gt;
&lt;pre class="brush: sql;"&gt;WITH emp AS (
    SELECT ROW_NUMBER() OVER(ORDER BY LastName, FirstName) AS RowNumber,
         EmployeeID, FirstName, LastName 
    FROM HumanResources.Employee AS e 
    INNER JOIN Person.Contact AS c ON e.ContactID = c.ContactID)
SELECT RowNumber, EmployeeID, FirstName, LastName
FROM emp
WHERE RowNumber BETWEEN 11 AND 20
ORDER BY RowNumber;
&lt;/pre&gt;Another interesting way you can use a CTE is to join a CTE to a previous CTE. Take a look at Listing 4 to see how this works.&lt;br /&gt;
&lt;br /&gt;
Listing 4: One CTE based on another CTE&lt;br /&gt;
&lt;pre class="brush: sql;"&gt;WITH maxDate AS (
    SELECT MAX(OrderDate) AS MaxOrderDate, CustomerID
    FROM Sales.SalesOrderHeader
    GROUP BY CustomerID),
orders AS (
    SELECT SalesOrderID, soh.CustomerID, OrderDate 
    FROM Sales.SalesOrderHeader AS soh 
    INNER JOIN &lt;strong&gt;maxDate&lt;/strong&gt; ON soh.CustomerID = maxDate.CustomerID 
         AND soh.OrderDate = maxDate.MaxOrderDate)
SELECT CustomerID,ProductID, sod.SalesOrderID,OrderDate 
FROM orders 
INNER JOIN Sales.SalesOrderDetail AS sod ON sod.SalesOrderID = orders.salesOrderID;
&lt;/pre&gt;The query in Listing 4 returns a list of the customers along with the products ordered on the most recent order. The first CTE, maxDate, returns the maximum order date for each customer. The second CTE joins the first CTE, maxDate, to the Sales.SalesOrderHeader table, returning a list of customers and the most recent order ID. Finally, the main query joins the Sales.SalesOrderDetail table to the orders CTE.&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;You can use CTEs to separate part of the logic of your query instead of using correlated subqueries and temp tables. Once you start using CTEs in your T-SQL code, you will probably find lots of ways to use them.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-6595426382632353899?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/kQUXOfdjVXg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/6595426382632353899/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2010/01/introduction-to-common-table.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/6595426382632353899?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/6595426382632353899?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/kQUXOfdjVXg/introduction-to-common-table.html" title="Introduction to Common Table Expressions" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2010/01/introduction-to-common-table.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0MDRHo6fip7ImA9WxBREEo.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-2140005658512095580</id><published>2009-12-29T00:31:00.000-08:00</published><updated>2009-12-29T00:31:15.416-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-29T00:31:15.416-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="GROUPING SETS" /><category scheme="http://www.blogger.com/atom/ns#" term="FORCESEEK" /><category scheme="http://www.blogger.com/atom/ns#" term="New in Sql Server 2008" /><title>SQL Server 2008 T-SQL Enhancements Part - I</title><content type="html">&lt;h3&gt; Introduction&lt;/h3&gt;&lt;i&gt;&lt;/i&gt;&lt;br /&gt;
SQL Server 2008 has come up with different compelling new features. One of them is T-SQL Enhancements which increase the productivity of developers by reducing overall development time. In this first article I will write about few of the T-SQL enhancements and in the second article I will write more about the new data types introduced in SQL Server 2008.&lt;br /&gt;
&lt;h3&gt; Intellisense Enhancements&lt;/h3&gt;We can keep our context, find the information we need, insert T-SQL language elements directly into our code, and even have IntelliSense complete our typing for us. This can speed up software development by reducing the amount of keyboard input required and minimize references to external documentation. Enhancements include expanded T-SQL language coverage and a new colorization system.&lt;br /&gt;
This feature works like the automatic syntax-checking in Visual Studio. As we type, it automatically fills out the syntax for Transact-SQL, and for database objects - even variables we’ve declared earlier (see the images below).&amp;nbsp; The feature is configurable; we can turn it off or on.&lt;br /&gt;
&lt;div class="wideImageContainer" style="width: 483px;"&gt;&lt;img alt="Intellisense 1" src="http://www.sqlservercentral.com/images/2586.jpg" style="cursor: -moz-zoom-in; display: block; max-width: 483px;" /&gt;&lt;div&gt;&lt;img align="bottom" src="http://www.sqlservercentral.com/Resources/Images/zoom.gif" style="margin: 0px 3px;" /&gt;&lt;a href="javascript:;"&gt;Zoom in&lt;/a&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;|&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;a href="javascript:;"&gt;Open in new window&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="wideImageContainer" style="width: 483px;"&gt;&lt;img alt="Intellisense 2" src="http://www.sqlservercentral.com/images/2587.jpg" style="cursor: -moz-zoom-in; display: block; max-width: 483px;" /&gt;&lt;div&gt;&lt;img align="bottom" src="http://www.sqlservercentral.com/Resources/Images/zoom.gif" style="margin: 0px 3px;" /&gt;&lt;a href="javascript:;"&gt;Zoom in&lt;/a&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;|&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;a href="javascript:;"&gt;Open in new window&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;br /&gt;
&lt;h3&gt; Syntax Enhancements&lt;/h3&gt;There are actually three syntax enhancements as follows:&lt;br /&gt;
·&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Initialization of variables at the same time of its declaration: Now we can initialize variables inline as part of the variable declaration statement instead of using separate DECLARE and SET statements. It works with most of the data types, including SQLCLR data types, but not TEXT, NTEXT, or IMAGE data types.&lt;br /&gt;
For Example:&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0"&gt;&lt;tbody&gt;
&lt;tr&gt;   &lt;td&gt;&amp;nbsp;&lt;/td&gt;  &lt;/tr&gt;
&lt;tr&gt;   &lt;td&gt;&amp;nbsp;&lt;/td&gt;   &lt;td&gt;&lt;div class="wideImageContainer" style="width: 479px;"&gt;&lt;img alt="Rounded Rectangle: --Declare @TotalHours variabe and assign value to it DECLARE @TotalHours INT = 10 --Declare @WagesPerHour variabe and assign value to it DECLARE @WagesPerHour INT = 20 --Declare @TotalWages variabe and assign value to it --by multiplying two previously declared variables DECLARE @TotalWages INT = @TotalHours * @WagesPerHour SELECT @TotalWages As 'TotalWages' " src="http://www.sqlservercentral.com/images/2588.gif" style="cursor: -moz-zoom-in; display: block; max-width: 483px;" /&gt;&lt;div&gt;&lt;img align="bottom" src="http://www.sqlservercentral.com/Resources/Images/zoom.gif" style="margin: 0px 3px;" /&gt;&lt;a href="javascript:;"&gt;Zoom in&lt;/a&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;|&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;a href="javascript:;"&gt;Open in new window&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;  &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;
·&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Compound Operators: Compound operators are same as what we have in C++ and C#; they execute some operation and set an original value to the result of the operation. They help in writing cleaner and abbreviate code. (It also works in the SET clause of an UPDATE statement). These are list of supported compound operators in SQL Server 2008.&lt;br /&gt;
o&amp;nbsp;&amp;nbsp;&amp;nbsp;+= Add and assign&lt;br /&gt;
o&amp;nbsp;&amp;nbsp;&amp;nbsp;-= Subtract and assign&lt;br /&gt;
o&amp;nbsp;&amp;nbsp;&amp;nbsp;*= Multiply and assign&lt;br /&gt;
o&amp;nbsp;&amp;nbsp;&amp;nbsp;/= Divide and assign&lt;br /&gt;
o&amp;nbsp;&amp;nbsp;&amp;nbsp;%= Modulo and assign&lt;br /&gt;
o&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;amp;= Bitwise AND and assign&lt;br /&gt;
o&amp;nbsp;&amp;nbsp;&amp;nbsp;^= Bitwise XOR and assign&lt;br /&gt;
o&amp;nbsp;&amp;nbsp;&amp;nbsp;|= Bitwise OR and assign&lt;br /&gt;
For Example:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="wideImageContainer" style="width: 479px;"&gt;&lt;img alt="--Declare @TotalHours variabe and assign value to it DECLARE @TotalHours INT = 10 --Declare @WagesPerHour variabe and assign value to it DECLARE @WagesPerHour INT = 20 --Declare @TotalWages variabe and assign value to it --by multiplying two previously declared variables DECLARE @TotalWages INT = @TotalHours * @WagesPerHour --Declare @bonus variabe and assign value to it DECLARE @bonus INT = 15 SET @TotalWages += @bonus SELECT @TotalWages As 'TotalWages With Bonus' " src="http://www.sqlservercentral.com/images/2589.gif" style="cursor: -moz-zoom-in; display: block; max-width: 483px;" /&gt;&lt;div&gt;&lt;img align="bottom" src="http://www.sqlservercentral.com/Resources/Images/zoom.gif" style="margin: 0px 3px;" /&gt;&lt;a href="javascript:;"&gt;Zoom in&lt;/a&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;|&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;a href="javascript:;"&gt;Open in new window&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&amp;nbsp; &lt;br /&gt;
·&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Row Constructors (a.k.a. Table Value Constructors): Transact-SQL is enhanced to allow multiple value inserts within a single INSERT statement; it means multiple row predicates in the VALUES clause.&lt;br /&gt;
For Example:&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0"&gt;&lt;tbody&gt;
&lt;tr&gt;   &lt;td&gt;&amp;nbsp;&lt;/td&gt;  &lt;/tr&gt;
&lt;tr&gt;   &lt;td&gt;&amp;nbsp;&lt;/td&gt;   &lt;td&gt;&lt;div class="wideImageContainer" style="width: 479px;"&gt;&lt;img alt="Rounded Rectangle: USE tempdb; GO IF OBJECT_ID('dbo.Customers', 'U') IS NOT NULL DROP TABLE dbo.Customers; CREATE TABLE dbo.Customers ( CustomerID INT NOT NULL, CustomerName VARCHAR(25) NOT NULL CONSTRAINT PK_Customers PRIMARY KEY(CustomerID) ) GO INSERT INTO dbo.Customers(CustomerID, CustomerName) VALUES (1, 'Amar'), (2, 'Akbar'), (3, 'Anthony') GO SELECT * FROM dbo.Customers " src="http://www.sqlservercentral.com/images/2590.gif" style="cursor: -moz-zoom-in; display: block; max-width: 483px;" /&gt;&lt;div&gt;&lt;img align="bottom" src="http://www.sqlservercentral.com/Resources/Images/zoom.gif" style="margin: 0px 3px;" /&gt;&lt;a href="javascript:;"&gt;Zoom in&lt;/a&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;|&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;a href="javascript:;"&gt;Open in new window&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;  &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;
&lt;h3&gt;Object Dependencies Enhancements&lt;/h3&gt;The object dependencies improvement provides reliable discovery of dependencies between objects through newly introduced catalog view and dynamic management functions. Dependency information is always up-to-date for both schema-bound (where the object A cannot be deleted because object B depends on it) and non-schema-bound (where Object A can be deleted or may not even have been created, however Object B still depends on it) objects. Dependencies are tracked for stored procedures, tables, views, functions, triggers, user-defined types, XML schema-collections, and more. SQL Server 2008 introduces three new objects that provide object dependency information: &amp;nbsp;&lt;br /&gt;
·&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;strong&gt;sys.sql_expression_dependencies&lt;/strong&gt;catalog view : It provides object dependencies by name, it holds one record for each dependency on a user-defined object in the current database.&lt;br /&gt;
·&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;strong&gt;sys.dm_sql_referenced_entities&lt;/strong&gt;DMF : It provides all entities that the input entity depends on, returns one row for each user defined object referenced by name in the definition of the specified referencing entity.&lt;br /&gt;
&lt;br /&gt;
·&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;strong&gt;sys.dm_sql_referencing_entities&lt;/strong&gt;DMF: It provides all entities that depend on the input entity, it returns one record for each user defined object in the current database that references another user defined object by name.&lt;br /&gt;
There are two ways we can see the object dependencies by using SSMS(Right click on the object and then click on View Dependencies) or by writing queries against above mentioned view and DMFs as given below:&lt;br /&gt;
&lt;table border="1" cellpadding="0" cellspacing="0"&gt;&lt;tbody&gt;
&lt;tr&gt;   &lt;td valign="top"&gt;&lt;img alt="SSMS" height="285" src="http://www.sqlservercentral.com/images/2591.jpg" width="291" /&gt;&lt;/td&gt;   &lt;td valign="top"&gt;&lt;img alt="Properties" height="336" src="http://www.sqlservercentral.com/images/2592.jpg" width="322" /&gt;&lt;/td&gt;  &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;
&lt;table cellpadding="0" cellspacing="0"&gt;&lt;tbody&gt;
&lt;tr&gt;   &lt;td&gt;&amp;nbsp;&lt;/td&gt;  &lt;/tr&gt;
&lt;tr&gt;   &lt;td&gt;&amp;nbsp;&lt;/td&gt;   &lt;td&gt;&lt;div class="wideImageContainer" style="width: 483px;"&gt;&lt;img alt="Rounded Rectangle: USE tempdb; GO IF OBJECT_ID('dbo.MyTable', 'U') IS NOT NULL DROP TABLE dbo.MyTable GO IF OBJECT_ID('dbo.MyView', 'V') IS NOT NULL DROP VIEW dbo.MyView GO CREATE TABLE dbo.MyTable ( Col1 INT, Col2 INT ) GO CREATE VIEW dbo.MyView AS SELECT Col1,Col2 FROM dbo.MyTable GO SELECT OBJECT_NAME(referencing_id) AS referencing_entity_name ,referenced_server_name AS server_name ,referenced_database_name AS database_name ,referenced_schema_name AS schema_name , referenced_entity_name FROM sys.sql_expression_dependencies WHERE referencing_id = OBJECT_ID(N'dbo.MyView'); GO SELECT referenced_schema_name AS objschema, referenced_entity_name AS objname, referenced_minor_name AS minorname, referenced_class_desc AS class FROM sys.dm_sql_referenced_entities('dbo.MyView', 'OBJECT'); GO SELECT referencing_schema_name AS objschema, referencing_entity_name AS objname, referencing_class_desc AS class FROM sys.dm_sql_" src="http://www.sqlservercentral.com/images/2593.gif" style="cursor: -moz-zoom-in; display: block; max-width: 483px;" /&gt;&lt;div&gt;&lt;img align="bottom" src="http://www.sqlservercentral.com/Resources/Images/zoom.gif" style="margin: 0px 3px;" /&gt;&lt;a href="javascript:;"&gt;Zoom in&lt;/a&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;|&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;a href="javascript:;"&gt;Open in new window&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;  &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;
&lt;h3&gt;Using the FORCESEEK Table Hint&lt;/h3&gt;The FORCESEEK table hint may be useful when the query plan uses a table or index scan operator on a table or view, but an index seek operator may be more efficient (for example, in case of high selectivity). The FORCESEEK table hint forces the query optimizer to use only an index seek operation as the access path to the data in the table or view referenced in the query. We can use this table hint to override the default plan chosen by the query optimizer to avoid performance issues caused by an inefficient query plan. For example, if a plan contains table or index scan operators, and the corresponding tables cause a high number of reads during the execution of the query, forcing an index seek operation may yield better query performance. This is especially true when inaccurate cardinality or cost estimations cause the optimizer to favor scan operations at plan compilation time.&lt;br /&gt;
&lt;div class="wideImageContainer" style="width: 483px;"&gt;&lt;img alt="Rounded Rectangle: USE AdventureWorks; GO SELECT * FROM Sales.SalesOrderHeader AS h INNER JOIN Sales.SalesOrderDetail AS d ON h.SalesOrderID = d.SalesOrderID WHERE h.TotalDue &amp;gt; 100 AND (d.OrderQty &amp;gt; 5 OR d.LineTotal &amp;lt; 1000.00); GO --The following execution plan shows that the query optimizer chose a clustered index scan operator to access the data in both tables. SELECT * FROM Sales.SalesOrderHeader AS h INNER JOIN Sales.SalesOrderDetail AS d WITH (FORCESEEK) ON h.SalesOrderID = d.SalesOrderID WHERE h.TotalDue &amp;gt; 100 AND (d.OrderQty &amp;gt; 5 OR d.LineTotal &amp;lt; 1000.00); GO --A clustered index seek operation is used to access the data in the Sales.SalesOrderDetail table. " src="http://www.sqlservercentral.com/images/2594.gif" style="cursor: -moz-zoom-in; display: block; max-width: 483px;" /&gt;&lt;div&gt;&lt;img align="bottom" src="http://www.sqlservercentral.com/Resources/Images/zoom.gif" style="margin: 0px 3px;" /&gt;&lt;a href="javascript:;"&gt;Zoom in&lt;/a&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;|&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;a href="javascript:;"&gt;Open in new window&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&amp;nbsp; &lt;br /&gt;
&lt;br /&gt;
One of the scenarios where this hint can be useful is working around with Parameter Sniffing (It is a technique by which the SQL Server query optimizer to sniff the parameter value from the query during first invocation and generates an optimized execution plan based on that value). Let see this in action by running below queries against AdventureWorks database and analyzing different scenarios.&lt;br /&gt;
&lt;strong&gt;Scenario 1&lt;/strong&gt;: The first query returns 450 rows and has low selectivity compared to the second query which return just 16 records, hence first query uses Index Scan rather than the Index Seek and Lookup of second query.&lt;br /&gt;
&lt;div class="wideImageContainer" style="width: 483px;"&gt;&lt;img alt="Rounded Rectangle: SELECT * FROM Sales.SalesOrderHeader WHERE SalesPersonID = 275 SELECT * FROM Sales.SalesOrderHeader WHERE SalesPersonID = 288 GO " src="http://www.sqlservercentral.com/images/2595.gif" style="cursor: -moz-zoom-in; display: block; max-width: 483px;" /&gt;&lt;div&gt;&lt;img align="bottom" src="http://www.sqlservercentral.com/Resources/Images/zoom.gif" style="margin: 0px 3px;" /&gt;&lt;a href="javascript:;"&gt;Zoom in&lt;/a&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;|&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;a href="javascript:;"&gt;Open in new window&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&amp;nbsp; &lt;br /&gt;
&lt;br /&gt;
&lt;strong&gt;Scenario 2&lt;/strong&gt;: In this scenario, I am running the same queries as above but this time I am using variables to pass values to queries instead of hard-coding the value. If we look at the execution plan generated, we see both the queries are using the same Index Scan even though the parameter values are different, and as we saw in scenario 1 the second query has high selectivity and should have used Index Seek and Lookup. This is happening because on execution of first query the SQL Query optimizer does not know the parameter value until runtime as I have used variables and makes a hard coded guess on selectivity and creates the execution plan on the basis of it and cache it, the second query uses the same cached execution plan. (Look at the query cost of both the queries and compare it with the scenario 1).&lt;br /&gt;
&lt;div class="wideImageContainer" style="width: 483px;"&gt;&lt;img alt="Rounded Rectangle: DECLARE @SalesPersonID INT = 275 SELECT * FROM Sales.SalesOrderHeader WHERE SalesPersonID = @SalesPersonID SET @SalesPersonID = 288 SELECT * FROM Sales.SalesOrderHeader WHERE SalesPersonID = @SalesPersonID GO " src="http://www.sqlservercentral.com/images/2596.gif" style="cursor: -moz-zoom-in; display: block; max-width: 483px;" /&gt;&lt;div&gt;&lt;img align="bottom" src="http://www.sqlservercentral.com/Resources/Images/zoom.gif" style="margin: 0px 3px;" /&gt;&lt;a href="javascript:;"&gt;Zoom in&lt;/a&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;|&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;a href="javascript:;"&gt;Open in new window&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&amp;nbsp; &lt;br /&gt;
&lt;strong&gt;Scenario 3&lt;/strong&gt;: So what to do if the queries use parameter and most of the time these variable values result in high selectivity, how can be force Query Optimizer to do Index Seek instead of Index Scan. We have two options here; either to use FORCESEEK hint or use RECOMPILE option as given.&lt;br /&gt;
&lt;div class="wideImageContainer" style="width: 483px;"&gt;&lt;img alt="Rounded Rectangle: DECLARE @SalesPersonID INT = 275 SELECT * FROM Sales.SalesOrderHeader WHERE SalesPersonID = @SalesPersonID SET @SalesPersonID = 288 SELECT * FROM Sales.SalesOrderHeader WITH (FORCESEEK) WHERE SalesPersonID = @SalesPersonID GO DECLARE @SalesPersonID INT = 275 SELECT * FROM Sales.SalesOrderHeader WHERE SalesPersonID = @SalesPersonID SET @SalesPersonID = 288 SELECT * FROM Sales.SalesOrderHeader WHERE SalesPersonID = @SalesPersonID OPTION(RECOMPILE) GO " src="http://www.sqlservercentral.com/images/2597.gif" style="cursor: -moz-zoom-in; display: block; max-width: 483px;" /&gt;&lt;div&gt;&lt;img align="bottom" src="http://www.sqlservercentral.com/Resources/Images/zoom.gif" style="margin: 0px 3px;" /&gt;&lt;a href="javascript:;"&gt;Zoom in&lt;/a&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;|&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;a href="javascript:;"&gt;Open in new window&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&amp;nbsp; &lt;br /&gt;
FORCESEEK applies to both clustered and non-clustered index seek operations. It can be specified for any table or view in the FROM clause of a SELECT statement and in the FROM &lt;table_source _moz-userdefined=""&gt; clause of an UPDATE or DELETE statement.&lt;/table_source&gt;&lt;br /&gt;
&lt;strong&gt;Note:&lt;/strong&gt; Because the SQL Server query optimizer typically selects the best execution plan for a query, Microsoft recommends using hints only as a last resort by experienced developers and database administrators. In other words, use hints as a last resort for performance tuning (the optimizer does a good job most of the times).&lt;br /&gt;
&lt;h3&gt;GROUPING SETS&lt;/h3&gt;GROUPING SETS allow us to write one query that produces multiple groupings and returns a single result set. The result set is equivalent to a UNION ALL of differently grouped rows. By using GROUPING SETS, we can focus on the different levels of information (groupings) our business needs, rather than the mechanics of how to combine several query results. GROUPING SETS enables us to write reports with multiple groupings easily, with improved query performance. As the number of possible groupings increases, the simplicity and performance benefits provided by GROUPING SETS become even greater.&lt;br /&gt;
&lt;div class="wideImageContainer" style="width: 483px;"&gt;&lt;img alt="Rounded Rectangle: --Creating environment for testing USE tempdb GO IF OBJECT_ID('Customer', 'U') IS NOT NULL DROP TABLE Customer GO CREATE TABLE Customer (CustomerId INT, Year INT, Sales MONEY) INSERT Customer VALUES(1, 2005, 12000) INSERT Customer VALUES(1, 2006, 18000) INSERT Customer VALUES(1, 2007, 25000) INSERT Customer VALUES(2, 2005, 15000) INSERT Customer VALUES(2, 2006, 6000) INSERT Customer VALUES(3, 2006, 20000) INSERT Customer VALUES(3, 2007, 24000) --Before SQL Server 2008, we had to write query(UNION ALL of GROUB BY clause) as follows SELECT CustomerId, NULL AS Year, SUM(Sales) FROM Customer GROUP BY CustomerId UNION ALL SELECT NULL AS CustomerId, Year, SUM(Sales) FROM Customer GROUP BY Year UNION ALL SELECT NULL AS CustomerId, NULL AS Year, SUM(Sales) FROM Customer --With SQL Server 2008, we can re-write above query with GROUPING SET clause --GROUPING SET produces multiple groupings and returns a single result set SELECT CustomerId, " src="http://www.sqlservercentral.com/images/2598.gif" style="cursor: -moz-zoom-in; display: block; max-width: 483px;" /&gt;&lt;div&gt;&lt;img align="bottom" src="http://www.sqlservercentral.com/Resources/Images/zoom.gif" style="margin: 0px 3px;" /&gt;&lt;a href="javascript:;"&gt;Zoom in&lt;/a&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;|&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;a href="javascript:;"&gt;Open in new window&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&amp;nbsp; In other words, a GROUP BY clause that uses GROUPING SETS can generate a result set equivalent to that generated by a UNION ALL of multiple simple GROUP BY clauses because the GROUP BY clauses are invalid in combination.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-2140005658512095580?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/STaRAgVDvmE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/2140005658512095580/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2009/12/sql-server-2008-t-sql-enhancements-part.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/2140005658512095580?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/2140005658512095580?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/STaRAgVDvmE/sql-server-2008-t-sql-enhancements-part.html" title="SQL Server 2008 T-SQL Enhancements Part - I" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2009/12/sql-server-2008-t-sql-enhancements-part.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEEBQ3cyeip7ImA9WxBSFEo.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-8721199216376182171</id><published>2009-12-22T01:04:00.000-08:00</published><updated>2009-12-22T01:04:12.992-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-22T01:04:12.992-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="uniqueidentifier" /><category scheme="http://www.blogger.com/atom/ns#" term="Auto generated keys" /><category scheme="http://www.blogger.com/atom/ns#" term="IDENTITY  in SQL" /><title>Auto generated SQL Server keys - uniqueidentifier or IDENTITY</title><content type="html">there are a number of ways you can auto-generate key values for your tables. The most common ways are via the use of the IDENTITY column property or by specifying a uniqueidentifier (GUID) data type along with defaulting with either the NEWID() or NEWSEQUENTIALID() function. Futhermore, GUIDs are heavily used in SQL Server Replication to uniquely identify rows in Merge Replication or Transactional Replication with updating subscriptions. The most common, well known way to auto-generate a key value is via the use of the IDENTITY column property on a column that's typically declared as an integer. Once defined, the engine will automatically generate a sequential number based on the way the property has been declared on the column. The IDENTITY property takes an initial seed value as its first parameter and an increment value as its second parameter. &lt;br /&gt;
Consider the following example which creates and inserts into identity based tables that define the primary key as a clustered index: &lt;br /&gt;
&lt;br /&gt;
&lt;div align="center"&gt; &lt;table bgcolor="#f0f0f0" border="1" cellpadding="4" cellspacing="0" id="table2" style="width: 590px;"&gt;&lt;tbody&gt;
&lt;tr&gt; &lt;td&gt;&lt;span style="font-family: Courier New; font-size: x-small;"&gt;SET NOCOUNT ON&lt;br /&gt;
GO&lt;br /&gt;
USE MASTER&lt;br /&gt;
GO&lt;br /&gt;
CREATE DATABASE MSSQLTIPS&lt;br /&gt;
GO&lt;br /&gt;
&lt;br /&gt;
USE MSSQLTIPS&lt;br /&gt;
GO&lt;br /&gt;
-- Start at 1 and increment by 1&lt;br /&gt;
CREATE TABLE IDENTITY_TEST1 &lt;br /&gt;
(&lt;br /&gt;
ID INT IDENTITY(1,1) PRIMARY KEY,&lt;br /&gt;
TESTCOLUMN CHAR(2000) DEFAULT REPLICATE('X',2000)&lt;br /&gt;
)&lt;br /&gt;
GO&lt;br /&gt;
&lt;br /&gt;
-- Start at 10 and increment by 10&lt;br /&gt;
CREATE TABLE IDENTITY_TEST2&lt;br /&gt;
(&lt;br /&gt;
ID INT IDENTITY(10,10) PRIMARY KEY,&lt;br /&gt;
TESTCOLUMN CHAR(2000) DEFAULT REPLICATE('X',2000)&lt;br /&gt;
)&lt;br /&gt;
GO&lt;br /&gt;
&lt;br /&gt;
-- Start at 1000 and increment by 5&lt;br /&gt;
CREATE TABLE IDENTITY_TEST3&lt;br /&gt;
(&lt;br /&gt;
ID INT IDENTITY(1000,5) PRIMARY KEY,&lt;br /&gt;
TESTCOLUMN CHAR(2000) DEFAULT REPLICATE('X',2000)&lt;br /&gt;
)&lt;br /&gt;
GO&lt;br /&gt;
&lt;br /&gt;
-- INSERT 1000 ROWS INTO EACH TEST TABLE &lt;br /&gt;
DECLARE @COUNTER INT&lt;br /&gt;
SET @COUNTER = 1&lt;br /&gt;
&lt;br /&gt;
WHILE (@COUNTER &amp;lt;= 1000)&lt;br /&gt;
BEGIN&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;INSERT INTO IDENTITY_TEST1 DEFAULT VALUES &lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;INSERT INTO IDENTITY_TEST2 DEFAULT VALUES &lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;INSERT INTO IDENTITY_TEST3 DEFAULT VALUES &lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;SET @COUNTER = @COUNTER + 1&lt;br /&gt;
END&lt;br /&gt;
GO&lt;br /&gt;
&lt;br /&gt;
SELECT TOP 3 ID FROM IDENTITY_TEST1&lt;br /&gt;
SELECT TOP 3 ID FROM IDENTITY_TEST2&lt;br /&gt;
SELECT TOP 3 ID FROM IDENTITY_TEST3&lt;br /&gt;
GO&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;br /&gt;
&lt;div align="center"&gt;&lt;img border="1" src="http://www.mssqltips.com/tipimages/1600_fig-1.jpg" /&gt; &lt;/div&gt;Another way to auto-generate key values is to specify your column as a type of uniqueidentifier and DEFAULT using NEWID() or NEWSEQUENTIALID(). Unlike IDENTITY, a DEFAULT constraint must be used to assign a GUID value to the column. &lt;br /&gt;
How do NEWID() and NEWSEQUENTIALID() differ? NEWID() &lt;b&gt;randomly&lt;/b&gt; generates a guaranteed unique value based on the identification number of the server's network card plus a unique number from the CPU clock. In contrast, NEWSEQUENTIALID() generates these values in sequential order as opposed to randomly. &lt;br /&gt;
Let's create new tables that use a uniqueidentifier along with both NEWID() and NEWSEQUENTIALID()  &lt;br /&gt;
&lt;div align="center"&gt;&amp;nbsp;  &lt;table bgcolor="#f0f0f0" border="1" cellpadding="4" cellspacing="0" id="table2" style="width: 590px;"&gt;&lt;tbody&gt;
&lt;tr&gt; &lt;td&gt;&lt;span style="font-family: Courier New; font-size: x-small;"&gt;USE MSSQLTIPS&lt;br /&gt;
GO&lt;br /&gt;
&lt;br /&gt;
CREATE TABLE NEWID_TEST&lt;br /&gt;
(&lt;br /&gt;
ID UNIQUEIDENTIFIER DEFAULT NEWID() PRIMARY KEY,&lt;br /&gt;
TESTCOLUMN CHAR(2000) DEFAULT REPLICATE('X',2000)&lt;br /&gt;
)&lt;br /&gt;
GO&lt;br /&gt;
CREATE TABLE NEWSEQUENTIALID_TEST&lt;br /&gt;
(&lt;br /&gt;
ID UNIQUEIDENTIFIER DEFAULT NEWSEQUENTIALID() PRIMARY KEY,&lt;br /&gt;
TESTCOLUMN CHAR(2000) DEFAULT REPLICATE('X',2000)&lt;br /&gt;
)&lt;br /&gt;
GO&lt;br /&gt;
&lt;br /&gt;
-- INSERT 1000 ROWS INTO EACH TEST TABLE &lt;br /&gt;
DECLARE @COUNTER INT&lt;br /&gt;
SET @COUNTER = 1&lt;br /&gt;
&lt;br /&gt;
WHILE (@COUNTER &amp;lt;= 1000)&lt;br /&gt;
BEGIN&lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;INSERT INTO NEWID_TEST DEFAULT VALUES &lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;INSERT INTO NEWSEQUENTIALID_TEST DEFAULT VALUES &lt;br /&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;SET @COUNTER = @COUNTER + 1&lt;br /&gt;
END&lt;br /&gt;
GO&lt;br /&gt;
&lt;br /&gt;
SELECT TOP 3 ID FROM NEWID_TEST&lt;br /&gt;
SELECT TOP 3 ID FROM NEWSEQUENTIALID_TEST&lt;br /&gt;
GO&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;br /&gt;
&lt;div align="center"&gt;&lt;img border="1" src="http://www.mssqltips.com/tipimages/1600_fig-2.jpg" /&gt; &lt;/div&gt;As you can see, the first table which uses NEWID() generates random values while the second table that uses NEWSEQUENTIALID() generates sequential values. As opposed to the integers generated by the IDENTITY approach, the GUID values generated are not as friendly to look at or work with. There is one other item to note. SQL Server keeps the last generated identity value in memory which can be retrieved right after an INSERT using SCOPE_IDENTITY(), @@IDENTITY, or CHECK_IDENT (depending on the scope you require). There is nothing similar to capture the last generated GUID value. If you use a GUID, you'll have to create your own mechanism to capture the last inserted value (i.e. retrieve the GUID prior to insertion or use the SQL Server 2005 OUTPUT clause). &lt;br /&gt;
Now that we understand how to auto generate key values and what they look like, let's examine the storage impacts of each approach. As part of the previously created table definitions, I added a column of CHAR(2000) to mimic the storage of additional column data. Let's examine the physical storage of the data: &lt;br /&gt;
&lt;br /&gt;
&lt;div align="center"&gt; &lt;table bgcolor="#f0f0f0" border="1" cellpadding="4" cellspacing="0" id="table2" style="width: 590px;"&gt;&lt;tbody&gt;
&lt;tr&gt; &lt;td&gt;&lt;span style="font-family: Courier New; font-size: x-small;"&gt;USE MSSQLTIPS&lt;br /&gt;
GO&lt;br /&gt;
SELECT OBJECT_NAME([OBJECT_ID]) as tablename, avg_fragmentation_in_percent, fragment_count, page_count&lt;br /&gt;
FROM sys.dm_db_index_physical_stats (DB_ID(), null, null, null, null)&lt;br /&gt;
ORDER BY tablename&lt;br /&gt;
GO&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;br /&gt;
&lt;div align="center"&gt;&lt;img border="1" src="http://www.mssqltips.com/tipimages/1600_fig-3.jpg" /&gt; &lt;/div&gt;Looking at this output, you can see that the NEWID() test table is very fragmented as evidenced by its fragmentation percentage of 98%. Furthermore, you can see that the rows were dispersed among 490 pages. This is due to the page splitting that occurred due to the random nature of the key generation. In contrast, the IDENTITY and NEWSEQUENTIALID() test tables show minimal fragmentation since their auto generated keys occur in sequential order. As a result, they don't suffer from the page splitting condition that plagues the NEWID() approach. Though you can defragment the NEWID() table, the random nature of the key generation will still cause page splitting and fragmentation with all future table INSERTs. However, page splitting can be minimized by specifying an appropriate FILL FACTOR. &lt;br /&gt;
Looking at the NEWSEQUENTIALID() test table, we see it generated fewer pages than the NEWID() approach but it still generated more pages than the IDENTITY approach. Why is this? It's because the uniqueidentifier data type consumes 16 bytes of disk space as opposed to the 4 bytes used by the integer data type that was used for the IDENTITY. Considering that SQL Server pages are generally capped at 8K or roughly 8060 bytes (as of SQL Server 2005, there is a row-overflow mechanism that can kick in but that's for another discussion), this leads to more pages generated for the NEWSEQUENTIALID() approach as opposed to the IDENTITY approach. &lt;br /&gt;
Examining the database table space used, we see that the tables using the IDENTITY approach used the least amount disk space.  &lt;br /&gt;
&lt;div align="center"&gt;&amp;nbsp;  &lt;table bgcolor="#f0f0f0" border="1" cellpadding="4" cellspacing="0" id="table2" style="width: 590px;"&gt;&lt;tbody&gt;
&lt;tr&gt; &lt;td&gt;&lt;span style="font-family: Courier New; font-size: x-small;"&gt;exec sp_spaceused IDENTITY_TEST1&lt;br /&gt;
GO&lt;br /&gt;
exec sp_spaceused IDENTITY_TEST2&lt;br /&gt;
GO&lt;br /&gt;
exec sp_spaceused IDENTITY_TEST3&lt;br /&gt;
GO&lt;br /&gt;
exec sp_spaceused NEWID_TEST&lt;br /&gt;
GO&lt;br /&gt;
exec sp_spaceused NEWSEQUENTIALID_TEST&lt;br /&gt;
GO&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;br /&gt;
&lt;div align="center"&gt;&lt;img border="1" src="http://www.mssqltips.com/tipimages/1600_fig-4.jpg" /&gt; &lt;/div&gt;Now also consider this, since a uniqueidentifier data type consumes 16 bytes of data, the size of any defined non-clustered indexes on a table using a GUID as a clustered index are also affected because the leaf level of these non-clustered indexes contains the clustered index key as a pointer. As a result, the size of any non-clustered indexes would end up being larger than if an IDENTITY were defined as integer or bigint. &lt;br /&gt;
It's evident that using IDENTITY to auto-generate key values offers a few advantages over the GUID approaches:  &lt;br /&gt;
&lt;ul&gt;&lt;li&gt;IDENTITY generated values are configurable, easy to read, and easier to work with  &lt;/li&gt;
&lt;li&gt;Fewer database pages are required to satisfy query requests  &lt;/li&gt;
&lt;li&gt;In comparison to NEWID(), page splitting (and its associated overhead) is eliminated  &lt;/li&gt;
&lt;li&gt;Database size is minimized  &lt;/li&gt;
&lt;li&gt;System functions exist to capture the last generated IDENTITY value (i.e. SCOPE_IDENTITY(), etc)  &lt;/li&gt;
&lt;li&gt;Some system functions - such as MIN() and MAX(), for instance - cannot be used on uniqueidentifier columns&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-8721199216376182171?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/RzCpxEBKggo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/8721199216376182171/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2009/12/auto-generated-sql-server-keys.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/8721199216376182171?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/8721199216376182171?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/RzCpxEBKggo/auto-generated-sql-server-keys.html" title="Auto generated SQL Server keys - uniqueidentifier or IDENTITY" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2009/12/auto-generated-sql-server-keys.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ck8FQngyeSp7ImA9WxBSEEk.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-7329141962291452083</id><published>2009-12-17T01:06:00.000-08:00</published><updated>2009-12-17T01:06:53.691-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-17T01:06:53.691-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Exclusive locks" /><category scheme="http://www.blogger.com/atom/ns#" term="Durable" /><category scheme="http://www.blogger.com/atom/ns#" term="Isolation Levels" /><category scheme="http://www.blogger.com/atom/ns#" term="Locking" /><category scheme="http://www.blogger.com/atom/ns#" term="Transactions" /><category scheme="http://www.blogger.com/atom/ns#" term="Consistent" /><title>Transactions Part 2</title><content type="html">n order to qualify as a transaction an operation must meet several qualifications. These qualifications are known as the ACID properties of transactions. ACID is an acronym that stands for: &lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Atomic - This refers to the primary idea of the first two articles, that each part of a transaction must succeed or they all must fail together. &lt;/li&gt;
&lt;li&gt;Consistent - When a transaction is complete the database must be left in a consistent state. All rules (constraints) must be applied. This also applies to the physical data structures such as indexes and doubly linked lists which must be left in a consistent state. The physical data structures are entirely SQL Server's problem and will not be dealt with here. &lt;/li&gt;
&lt;li&gt;Isolated - The data modifications made by a transaction must be independent of any other transactions. This is the focus of this article so we will get into much more detail. &lt;/li&gt;
&lt;li&gt;Durable - Once committed the changes made by a transaction must be "permanent." I use quotes because, of course, few changes in a database are ever really permanent, since the data is subject to being changed by subsequent transactions. However, the changes must be recoverable in the event of system failure. Again, this is almost entirely the job of SQL Server, the ability that a DBA has to affect this property would more appropriately be covered in a discussion of backup and recovery strategies. &lt;/li&gt;
&lt;/ul&gt;&lt;strong&gt;Locking&lt;/strong&gt;&lt;br /&gt;
Locks are the mechanism used by SQL Server to control concurrency and isolation. Concurrency and isolation are pretty much the inverse of each other. The intelligent use of Isolation Levels (and by extension Lock Hints) allows the knowledgeable DBA to adjust queries so that they meet the transactions' need for isolation, and hopefully support an acceptable level of concurrency.&lt;br /&gt;
By default, whenever data is read or modified, SQL Server places locks on the data. These locks prevent two transactions from attempting to modify the same data at the same time. Once a transaction is done reading or altering the data, the locks are released and subsequent transactions can obtain locks on those resources. Locks are granted on a "First come, first served" basis. There are three kinds of locks that we are concerned with:&lt;br /&gt;
&lt;ol type="1"&gt;&lt;li class="MsoNormal" style="color: black; margin: 0in 0in 0pt;"&gt; Shared locks are normally obtained by any SELECT query. As the name implies, other connections can still read the data (obtain their own shared lock), but will not be able to modify it until the shared locks are released. &lt;/li&gt;
&lt;li class="MsoNormal" style="color: black; margin: 0in 0in 0pt;"&gt;Update locks are a kind of heavyweight shared lock; only one connection can obtain an Update lock on a given resource.Update locks allow a connection to have the option to either upgrade the lock to Exclusive and modify the data, or downgrade to a shared lock if it won't modify the data. Having an Update Lock will not prevent another transaction from obtaining Shared locks. &lt;/li&gt;
&lt;li class="MsoNormal" style="color: black; margin: 0in 0in 0pt;"&gt;Finally, Exclusive locks must be obtained if data is to be modified. Once an Exclusive lock is granted on a resource other connections must wait until the Exclusive lock is released to obtain ANY kind of lock on that resource. &lt;/li&gt;
&lt;/ol&gt;When I mention "resource" normally you can think of a single row of data since in most cases SQL Server will lock only a single row. However, if your transaction will update a number of rows, SQL Server might obtain Page locks, or even a Table lock. This concept is known as Lock Granularity and it has no effect on the types of locks obtained by a query. Setting the Transaction Isolation Level has no effect on the granularity of Locks obtained. However, Lock Granularity can have a huge impact on performance and concurrency; but with one exception--Range Locks, which will be discussed near the end of this article--a detailed discussion of this topic is beyond the scope of this series of articles.I have greatly simplified a very complex mechanism, but that should be enough detail to facilitate an understanding of the main topic.&lt;br /&gt;
&lt;strong&gt;Transaction Isolation Levels&lt;/strong&gt;&lt;br /&gt;
There are four Transaction Isolation Levels supported by SQL Server. The table below comes from Books Online and is a handy reference on how the levels differ from each other.The key to understanding Transaction Isolation Levels is to realize that all they do is control how long Shared locks are held within a transaction.&lt;br /&gt;
&lt;table border="1" cellpadding="2" cols="4" frame="box" rules="all" style="width: 587px;"&gt;&lt;tbody&gt;
&lt;tr valign="top"&gt; &lt;th class="label" width="30%"&gt;Isolation Level Dirty&lt;/th&gt; &lt;th class="label" width="21%"&gt;Read&lt;/th&gt; &lt;th class="label" width="31%"&gt;Nonrepeatable Read&lt;/th&gt; &lt;th class="label" width="18%"&gt;Phantom&lt;/th&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="30%"&gt;&lt;b&gt;Read Uncommitted&lt;/b&gt;&lt;/td&gt; &lt;td width="21%"&gt;Yes&lt;/td&gt; &lt;td width="31%"&gt;Yes&lt;/td&gt; &lt;td width="18%"&gt;Yes&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="30%"&gt;&lt;b&gt;Read Committed&lt;/b&gt;&lt;/td&gt; &lt;td width="21%"&gt;No&lt;/td&gt; &lt;td width="31%"&gt;Yes&lt;/td&gt; &lt;td width="18%"&gt;Yes&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="30%"&gt;&lt;b&gt;Repeatable Read&lt;/b&gt;&lt;/td&gt; &lt;td width="21%"&gt;No&lt;/td&gt; &lt;td width="31%"&gt;No&lt;/td&gt; &lt;td width="18%"&gt;Yes&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="30%"&gt;&lt;b&gt;Serializable&lt;/b&gt;&lt;/td&gt; &lt;td width="21%"&gt;No&lt;/td&gt; &lt;td width="31%"&gt;No&lt;/td&gt; &lt;td width="18%"&gt;No&lt;/td&gt; &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;You might be asking "OK, but what is a Phantom?" I'm glad you asked! Let's look at each of these conditions and what they mean to our transactions.&lt;br /&gt;
A Dirty Read is not a magazine that you wouldn't show to your mother! Dirty Reads are when a transaction is allowed to read uncommitted data. Notice that Dirty Reads are only allowed (appropriately enough) under the Read uncommitted Isolation Level. For our examples we'll revisit the simple table from &lt;a href="http://www.sqlservercentral.com/columnists/dpeterson/allabouttransactionspart1.asp"&gt;Part 1&lt;/a&gt;. Here is the DDL for the table and a simple script to populate it with a bit of data:&lt;br /&gt;
&lt;pre class="code"&gt;CREATE TABLE TranTest (
 Col1 int IDENTITY,
 Col2 int)
DECLARE @count int
SET @count = 0
WHILE @count &amp;lt;15
Begin
INSERT TranTest (Col2)
VALUES (0)
SET @count = @count + 1
END
&lt;/pre&gt;&lt;div dir="ltr"&gt;**Note: In the following examples I have intentionally left out any error handling for the sake of clarity and brevity.** &lt;br /&gt;
&lt;/div&gt;&lt;table border="1" cellpadding="2" cols="4" frame="box" rules="all" style="width: 700px;"&gt;&lt;tbody&gt;
&lt;tr valign="top"&gt; &lt;th width="35%"&gt;Connection 1 &lt;/th&gt; &lt;th width="35%"&gt;Connection 2 &lt;/th&gt; &lt;th width="30%"&gt;Comments &lt;/th&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt;&lt;span style="font-size: x-small;"&gt;BEGIN TRAN&lt;br /&gt;
INSERT INTO TranTest (Col2) VALUES (2)&lt;/span&gt;&lt;/td&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt;&lt;span style="font-size: x-small;"&gt;INSERT a new row in the table but don't COMMIT&lt;/span&gt;&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt;&lt;span style="font-size: x-small;"&gt;&lt;span style="color: red;"&gt;&lt;span style="color: black;"&gt;SET TRANSACTION ISOLATION LEVEL&lt;/span&gt; READ UNCOMMITTED&lt;br /&gt;
&lt;/span&gt; GO&lt;br /&gt;
SELECT *&lt;br /&gt;
FROM TranTest&lt;br /&gt;
WHERE Col2 = 2&lt;/span&gt;&lt;/td&gt; &lt;td width="35%"&gt; &lt;span style="font-size: x-small;"&gt;Set the Isolation Level&lt;/span&gt;&lt;br /&gt;
&lt;span style="font-size: x-small;"&gt;Returns 1 row showing that connection 2 can read the new row&lt;/span&gt;&lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt;&lt;span style="font-size: x-small;"&gt;ROLLBACK&lt;/span&gt;&lt;/td&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt; &lt;span style="font-size: x-small;"&gt;SELECT *&lt;br /&gt;
FROM TranTest&lt;br /&gt;
WHERE Col2 = 2&lt;/span&gt;&lt;/td&gt; &lt;td width="35%"&gt;&lt;span style="font-size: x-small;"&gt;Returns 0 rows&lt;/span&gt;&lt;/td&gt; &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;In this example Connection 2 was actually able to read a row that never logically existed. This is possible because under the Read Uncommitted Level, the SELECT statement didn't even attempt to obtain a shared lock on the rows it was reading and it ignored the Exclusive locks that Connection 1 had. Now let's take a look at the default behavior using the Read Committed Isolation Level:&lt;br /&gt;
&lt;table border="1" cellpadding="2" cols="4" frame="box" rules="all" style="width: 700px;"&gt;&lt;tbody&gt;
&lt;tr valign="top"&gt; &lt;th width="35%"&gt;Connection 1 &lt;/th&gt; &lt;th width="35%"&gt;Connection 2 &lt;/th&gt; &lt;th width="30%"&gt;Comments &lt;/th&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt;&lt;span style="font-size: x-small;"&gt;BEGIN TRAN&lt;br /&gt;
INSERT INTO TranTest (Col2) VALUES (2)&lt;/span&gt;&lt;/td&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt;&lt;span style="font-size: x-small;"&gt;INSERT a new row in the table but don't COMMIT&lt;/span&gt;&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt;&lt;span style="font-size: x-small;"&gt;&lt;span style="color: red;"&gt;&lt;span style="color: black;"&gt;SET TRANSACTION ISOLATION LEVEL&lt;/span&gt; READ COMMITTED&lt;br /&gt;
&lt;/span&gt; GO&lt;br /&gt;
SELECT *&lt;br /&gt;
FROM TranTest&lt;br /&gt;
WHERE Col2 = 2&lt;/span&gt;&lt;/td&gt; &lt;td width="35%"&gt;  &lt;span style="font-size: x-small;"&gt;The query is blocked because of the exclusive lock held by Connection 1&lt;/span&gt;&lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt;&lt;span style="font-size: x-small;"&gt;ROLLBACK&lt;/span&gt;&lt;/td&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt;&lt;span style="font-size: x-small;"&gt;The Connection 2 query returns 0 rows&lt;/span&gt;&lt;/td&gt; &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;Since Connection 2 was not able to obtain a shared lock on the row, it waited until Connection 1 released its Exclusive lock. So Dirty Reads are allowed only in the Read Uncommitted Isolation Level but you don't necessarily have to use the SET TRANSACTION ISOLATION LEVEL command to get there. You can get the same result by using the NOLOCK hint in your queries.&lt;br /&gt;
SELECT *&lt;br /&gt;
FROM TranTest with(NOLOCK)&lt;br /&gt;
WHERE Col2 = 2&lt;br /&gt;
The main difference between these two methods is that this last example effectively places the connection into Read Uncommitted only for the duration of that query; subsequent queries would revert back to Read Committed. I'll discuss Lock Hints more a bit later but I wanted to introduce the concept and give a brief example here.&lt;br /&gt;
A Non-Repeatable Read occurs when a transaction reads data multiple times and gets different results. This can happen because under the Read Committed Isolation Level, shared locks are held only as long as it takes to read the data. As soon as a row is read the lock is released, assuming row level locking is being used. This behavior is great for concurrency, but can cause some "interesting" results. Look at the following example:&lt;br /&gt;
&lt;table border="1" cellpadding="2" cols="4" frame="box" rules="all" style="width: 700px;"&gt;&lt;tbody&gt;
&lt;tr valign="top"&gt; &lt;th width="35%"&gt;Connection 1 &lt;/th&gt; &lt;th width="35%"&gt;Connection 2 &lt;/th&gt; &lt;th width="30%"&gt;Comments &lt;/th&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt;&lt;span style="font-size: x-small;"&gt;&lt;span style="color: red;"&gt;&lt;span style="color: black;"&gt;SET TRANSACTION ISOLATION LEVEL&lt;/span&gt; READ COMMITTED&lt;/span&gt;&lt;br /&gt;
GO&lt;br /&gt;
BEGIN TRAN&lt;br /&gt;
SELECT *&lt;br /&gt;
FROM TranTest&lt;br /&gt;
WHERE Col2 = 2&lt;/span&gt;&lt;/td&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt; &lt;span style="font-size: x-small;"&gt;Begin a transaction and run the first query&lt;/span&gt;&lt;br /&gt;
&lt;span style="font-size: x-small;"&gt;Returns 0 rows&lt;/span&gt;&lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt; &lt;span style="font-size: x-small;"&gt;UPDATE TranTest&lt;br /&gt;
SET Col2 = 2&lt;/span&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt; &lt;span style="font-size: x-small;"&gt;This assumes Autocommit Mode &lt;/span&gt;&lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt; &lt;span style="font-size: x-small;"&gt; SELECT *&lt;br /&gt;
FROM TranTest&lt;br /&gt;
WHERE Col2 = 2&lt;br /&gt;
COMMIT&lt;/span&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt; &lt;span style="font-size: x-small;"&gt;Run the same query again and end the transaction&lt;/span&gt;&lt;br /&gt;
&lt;span style="font-size: x-small;"&gt;Returns 15 rows&lt;/span&gt;&lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;&lt;br /&gt;
The default behavior of SQL Server means that the data can change on you right in the middle of a transaction. If we bump up the Isolation Level to Repeatable Read, this isn't allowed:&lt;br /&gt;
&lt;table border="1" cellpadding="2" cols="4" frame="box" rules="all" style="width: 700px;"&gt;&lt;tbody&gt;
&lt;tr valign="top"&gt; &lt;th width="35%"&gt;Connection 1 &lt;/th&gt; &lt;th width="35%"&gt;Connection 2 &lt;/th&gt; &lt;th width="30%"&gt;Comments &lt;/th&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt;&lt;span style="font-size: x-small;"&gt;&lt;span style="color: red;"&gt;&lt;span style="color: black;"&gt;SET TRANSACTION ISOLATION LEVEL&lt;/span&gt; REPEATABLE READ&lt;br /&gt;
&lt;/span&gt; GO&lt;br /&gt;
BEGIN TRAN&lt;br /&gt;
SELECT *&lt;br /&gt;
FROM TranTest&lt;br /&gt;
WHERE Col2 = 2&lt;/span&gt;&lt;/td&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt; &lt;span style="font-size: x-small;"&gt;Begin a transaction and run the first query&lt;/span&gt;&lt;br /&gt;
&lt;span style="font-size: x-small;"&gt;Returns 15 rows&lt;/span&gt; &lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt; &lt;span style="font-size: x-small;"&gt;UPDATE TranTest&lt;br /&gt;
&lt;/span&gt; &lt;span style="font-size: x-small;"&gt;SET Col2 = 1&lt;/span&gt; &lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt;  &lt;span style="font-size: x-small;"&gt;Set Col2 back to 1, but the transaction is blocked by Connection 1&lt;/span&gt; &lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt; &lt;span style="font-size: x-small;"&gt;SELECT *&lt;br /&gt;
&lt;/span&gt; &lt;span style="font-size: x-small;"&gt;FROM TranTest&lt;br /&gt;
&lt;/span&gt; &lt;span style="font-size: x-small;"&gt;WHERE Col2 = 2&lt;br /&gt;
&lt;/span&gt; &lt;span style="font-size: x-small;"&gt;COMMIT&lt;/span&gt; &lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt; &lt;span style="font-size: x-small;"&gt;Run the same query again and end the transaction&lt;/span&gt;&lt;br /&gt;
&lt;span style="font-size: x-small;"&gt;Returns 15 rows&lt;/span&gt; &lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt; &lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt;  &lt;br /&gt;
&lt;span style="font-size: x-small;"&gt;Connection 2 completes, 15 rows affected&lt;/span&gt;&lt;/td&gt; &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;Repeatable Read offers a bit more isolation (stability) to your transactions, but it comes at the price of reduced concurrency. Connection 2 was only able to proceed with its UPDATE after Connection 1 released its Shared Locks on the data. Note that increasing the Isolation Level did not change the types of locks that were acquired by either transaction, it forced Connection 1 to hold on to the Shared Locks until the entire transaction completed rather than releasing them as soon as the data was read. Thus Connection 2 could not upgrade its Update Locks to Exclusive Locks and entered a blocked state until Connection 1 committed.&lt;br /&gt;
Now let's look at Phantoms:  &lt;br /&gt;
&lt;table border="1" cellpadding="2" cols="4" frame="box" rules="all" style="width: 700px;"&gt;&lt;tbody&gt;
&lt;tr valign="top"&gt; &lt;th width="35%"&gt;Connection 1 &lt;/th&gt; &lt;th width="35%"&gt;Connection 2 &lt;/th&gt; &lt;th width="30%"&gt;Comments &lt;/th&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt;&lt;span style="color: black; font-size: x-small;"&gt;&lt;span style="color: red;"&gt;&lt;span style="color: black;"&gt;SET TRANSACTION ISOLATION LEVEL&lt;/span&gt; REPEATABLE READ&lt;br /&gt;
&lt;/span&gt; GO&lt;br /&gt;
BEGIN TRAN&lt;br /&gt;
SELECT *&lt;br /&gt;
FROM TranTest&lt;br /&gt;
WHERE Col2 = 2&lt;/span&gt;&lt;/td&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt; &lt;span style="font-size: x-small;"&gt;Begin a transaction and run the first query&lt;/span&gt;&lt;br /&gt;
&lt;span style="font-size: x-small;"&gt;Returns 15 rows&lt;/span&gt; &lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt; &lt;span style="font-size: x-small;"&gt;INSERT INTO TranTest (Col2)&lt;/span&gt;&lt;br /&gt;
&lt;span style="font-size: x-small;"&gt;VALUES (2)&lt;/span&gt; &lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt;  &lt;span style="font-size: x-small;"&gt;Add a new row&lt;/span&gt; &lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt; &lt;span style="font-size: x-small;"&gt;SELECT *&lt;br /&gt;
FROM TranTest&lt;br /&gt;
WHERE Col2 = 2&lt;br /&gt;
COMMIT&lt;/span&gt; &lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt; &lt;span style="font-size: x-small;"&gt;Run the same query again and end the transaction&lt;/span&gt;&lt;br /&gt;
&lt;span style="font-size: x-small;"&gt;Returns 16 rows&lt;/span&gt; &lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;The extra row in the second result set is our Phantom. So we see that Repeatable Read doesn't always guarantee a repeatable read. It only ensures that rows in your result set won't be changed by another transaction until you are done with them, it doesn't prevent another transaction from adding rows.&lt;br /&gt;
&lt;br /&gt;
&lt;table border="1" cellpadding="2" cols="4" frame="box" rules="all" style="width: 700px;"&gt;&lt;tbody&gt;
&lt;tr valign="top"&gt; &lt;th width="35%"&gt;Connection 1 &lt;/th&gt; &lt;th width="35%"&gt;Connection 2 &lt;/th&gt; &lt;th width="30%"&gt;Comments &lt;/th&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt;&lt;span style="font-size: x-small;"&gt;&lt;span style="color: red;"&gt;&lt;span style="color: black;"&gt;SET TRANSACTION ISOLATION LEVEL &lt;span style="color: red;"&gt;SERIALIZABLE&lt;/span&gt;&lt;br /&gt;
GO&lt;br /&gt;
BEGIN TRAN&lt;br /&gt;
SELECT *&lt;br /&gt;
FROM TranTest&lt;br /&gt;
WHERE Col2 = 2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt; &lt;span style="font-size: x-small;"&gt;Begin a transaction and run the first query&lt;/span&gt;&lt;br /&gt;
&lt;span style="font-size: x-small;"&gt;Returns 16 rows&lt;/span&gt; &lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt; &lt;span style="font-size: x-small;"&gt;INSERT INTO TranTest (Col2) VALUES (2)&lt;/span&gt; &lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt;  &lt;span style="font-size: x-small;"&gt;Add a new row, but the transaction is blocked by Connection 1&lt;/span&gt; &lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt; &lt;span style="font-size: x-small;"&gt;SELECT *&lt;br /&gt;
&lt;/span&gt; &lt;span style="font-size: x-small;"&gt;FROM TranTest&lt;br /&gt;
&lt;/span&gt; &lt;span style="font-size: x-small;"&gt;WHERE Col2 = 2&lt;br /&gt;
&lt;/span&gt; &lt;span style="font-size: x-small;"&gt;COMMIT&lt;/span&gt; &lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt; &lt;span style="font-size: x-small;"&gt;Run the same query again and end the transaction&lt;/span&gt;&lt;br /&gt;
&lt;span style="font-size: x-small;"&gt;Returns 16 rows&lt;/span&gt; &lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt; &lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt;  &lt;br /&gt;
&lt;span style="font-size: x-small;"&gt;Connection 2 completes&lt;/span&gt;&lt;/td&gt; &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;&lt;br /&gt;
You might think that SQL Server has to lock the entire table to prevent Phantoms. The good news is that it doesn't--as long as you have an index and your query is selective enough to not force a table scan or full index scan. The way SQL Server handles this is by what is known as a Range Lock.SQL Server grants only those locks necessary to satisfy the query. To demonstrate this point lets add a clustered index to our table.&lt;br /&gt;
CREATE CLUSTERED INDEX IE01 ON TranTest(Col2)&lt;br /&gt;
Now do the following: &lt;br /&gt;
&lt;div dir="ltr"&gt; &lt;br /&gt;
&lt;/div&gt;&lt;table border="1" cellpadding="2" cols="4" frame="box" rules="all" style="width: 700px;"&gt;&lt;tbody&gt;
&lt;tr valign="top"&gt; &lt;th width="35%"&gt;Connection 1 &lt;/th&gt; &lt;th width="35%"&gt;Connection 2 &lt;/th&gt; &lt;th width="30%"&gt;Comments &lt;/th&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt;&lt;span style="font-size: x-small;"&gt;&lt;span style="color: red;"&gt;&lt;span style="color: black;"&gt;SET TRANSACTION ISOLATION LEVEL &lt;span style="color: red;"&gt;SERIALIZABLE&lt;/span&gt;&lt;br /&gt;
GO&lt;br /&gt;
BEGIN TRAN&lt;br /&gt;
SELECT *&lt;br /&gt;
FROM TranTest&lt;br /&gt;
WHERE Col2 BETWEEN 1 AND 10&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt; &lt;span style="font-size: x-small;"&gt;Begin a transaction and run the first query&lt;/span&gt;&lt;br /&gt;
&lt;span style="font-size: x-small;"&gt;Returns 10 rows&lt;/span&gt; &lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt; &lt;span style="font-size: x-small;"&gt;INSERT INTO TranTest (Col2) VALUES (17)&lt;/span&gt; &lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt;  &lt;span style="font-size: x-small;"&gt;Add a new row outside the range of values in Connection 1's query.&lt;/span&gt; &lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="35%"&gt; &lt;span style="font-size: x-small;"&gt;SELECT *&lt;br /&gt;
FROM TranTest&lt;br /&gt;
WHERE Col1 BETWEEN 1 AND 10&lt;br /&gt;
COMMIT&lt;/span&gt; &lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="35%"&gt; &lt;span style="font-size: x-small;"&gt;Run the same query again and end the transaction&lt;/span&gt;&lt;br /&gt;
&lt;span style="font-size: x-small;"&gt;Returns 10 rows&lt;/span&gt; &lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;Notice that the INSERT did not wait for Connection 1 to complete because the row it was inserting fell outside the range of locks that Connection 1 held. If you run through the same steps again but switch the INSERT to include a value somewhere between 1 and 10 the INSERT will be blocked. This should demonstrate that setting the Isolation Level to Serializable may not have as large a negative impact on concurrency as you might expect. The major factors that determine how big the impact will be are: 1) the types of transactions you are running i.e. INSERTs vs. UPDATEs and DELETEs. And 2) how efficient the indexing scheme and transactions are.&lt;br /&gt;
&lt;strong&gt;&lt;u&gt;Lock Hints&lt;/u&gt;&lt;/strong&gt;&lt;br /&gt;
Earlier I showed an example of using a Lock Hint rather than the SET TRANSACTION ISOLATION LEVEL command. The chart below shows the various Isolation Levels and their equivalent Lock Hints:&lt;br /&gt;
&lt;table border="1" cellpadding="2" cols="2" frame="box" rules="all" style="width: 487px;"&gt;&lt;tbody&gt;
&lt;tr valign="top"&gt; &lt;th class="label" width="30%"&gt;Isolation level&lt;/th&gt; &lt;th class="label" width="30%"&gt;Lock Hints&lt;/th&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="30%"&gt;&lt;b&gt;Read Uncommitted&lt;/b&gt;&lt;/td&gt; &lt;td width="30%"&gt;READUNCOMMITTED&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="30%"&gt;&lt;br /&gt;
&lt;/td&gt;&lt;td width="30%"&gt;NOLOCK&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="30%"&gt;&lt;b&gt;Read Committed&lt;/b&gt;&lt;/td&gt; &lt;td width="30%"&gt;READCOMMITTED&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="30%"&gt;&lt;b&gt;Repeatable Read&lt;/b&gt;&lt;/td&gt; &lt;td width="30%"&gt;REPEATABLEREAD&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="30%"&gt;&lt;b&gt;Serializable&lt;/b&gt;&lt;/td&gt; &lt;td width="30%"&gt;SERIALIZABLE&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="30%"&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td width="30%"&gt;HOLDLOCK&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="30%"&gt;&lt;b&gt;*Special&lt;/b&gt;&lt;/td&gt; &lt;td width="30%"&gt;READPAST&lt;/td&gt; &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;*The READPAST Lock Hint is kind of midway between Read Committed and Read Uncommitted. READPAST only works on a query running in the Read Committed Isolation Level and it only works on SELECT queries.READPAST tells SQL Server to ignore any records that have locks on them. Unlike NOLOCK, READPAST doesn't ignore the locks, it ignores locked rows so rather than generating potential Dirty Reads, it can generate an incomplete result set. I.e. some records that meet the search criteria may not be returned because they were locked by another process and skipped. There are other Lock Hints that specify lock granularity, but I won't cover them here.&lt;br /&gt;
I know conventional wisdom says that you should avoid using Lock Hints, but I suspect that's primarily because most folks don't know what they are doing and are likely to cause more harm than good. I don't use Lock Hints extensively, and I almost never use Lock Hints to specify granularity. I have found that unless I'm trying to diagnose or troubleshoot something, SQL Server does a better job of managing Lock Granularity than I ever could. The Lock Hints I have covered here have proven to be very useful when SQL Server's default transaction behavior isn't what I need. In fact, I tend to use Lock Hints more than setting the Isolation Levels because Lock Hints only affect the query in which they are specified so you don't have to bother setting the Isolation Level back to what it was and worry about the effect it might have on other queries in the same connection.&lt;br /&gt;
I hope that this exploration of Locks and Transaction Isolation Levels has helped clarify what often seems to be a mystery. Understanding these concepts will significantly reduce the "@#$!" factor in your application design and troubleshooting efforts.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-7329141962291452083?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/Ic6TSsAajjA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/7329141962291452083/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2009/12/transactions-part-2.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/7329141962291452083?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/7329141962291452083?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/Ic6TSsAajjA/transactions-part-2.html" title="Transactions Part 2" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2009/12/transactions-part-2.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEAMQHY4fip7ImA9WxBTGUg.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-7055310400707142659</id><published>2009-12-16T01:46:00.000-08:00</published><updated>2009-12-16T01:46:21.836-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-16T01:46:21.836-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="COMMIT" /><category scheme="http://www.blogger.com/atom/ns#" term="BEGIN TRAN" /><category scheme="http://www.blogger.com/atom/ns#" term="ROLLBACK" /><category scheme="http://www.blogger.com/atom/ns#" term="transaction" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL transaction" /><title>Transactions - Part 1</title><content type="html">Everyone knows that a transaction is defined as a "Unit of work." The transaction completes only if all the individual steps are successful, otherwise it all rolls back as if nothing ever happened, right? That simple definition belies some devilishly complex behavior on the part of SQL Server. Odds are that if you are a DBA or developer SQL Server's transaction handling behavior has left you scratching your head and muttering "What in the world...?" at some point in your career.&lt;br /&gt;
It has become evident that many of us don't really understand how to put the power of transactions to work. For instance, just placing several SQL commands between a BEGIN TRAN&lt;action _moz-userdefined=""&gt; and a COMMIT or ROLLBACK does not ensure that they will act as a single transaction. To get consistent, reliable results you need to understand Transaction Modes, Transaction Isolation Levels, locking, nesting, error handling, Savepoints, XACT_ABORT, bound connections, and just for good measure you need to understand how ANSI_DEFAULTS and your choice of provider affect transaction behavior.&lt;/action&gt;&lt;br /&gt;
&lt;strong&gt;&lt;u&gt;Transaction Modes&lt;/u&gt;&lt;/strong&gt; SQL Server supports three Transaction Modes: Autocommit, Explicit, and Implicit. &lt;br /&gt;
&lt;ul dir="ltr"&gt;&lt;li&gt; &lt;div&gt;&lt;strong&gt;Autocommit&lt;/strong&gt; &lt;strong&gt;Mode&lt;/strong&gt; is the default behavior for SQL Server. In this mode each SQL statement that is issued acts as a single transaction and commits on completion so there is no need--or even an opportunity--to issue a COMMIT or ROLLBACK. Autocommit is the default behavior for ADO, OLE DB, ODBC, or DB-Library connections.&lt;/div&gt;&lt;/li&gt;
&lt;li&gt; &lt;div&gt;&lt;strong&gt;Explicit Mode&lt;/strong&gt; is entered whenever you issue a BEGIN TRAN command and ends when you COMMIT the top level transaction (see below for a discussion of Nested Transactions) or issue a ROLLBACK. There is no configuration setting that turns Explicit Mode on and off. Once your explicit transaction is committed or rolled back SQL Server immediately returns to the transaction mode it was in before.&lt;/div&gt;&lt;/li&gt;
&lt;li&gt; &lt;div&gt;&lt;strong&gt;Implicit Mode&lt;/strong&gt; is a bit trickier for most of us to grasp. By default IMPLICIT_TRANSACTIONS is off (in other words Autocommit Mode). If you issue the SET IMPLICIT_TRANSACTIONS ON command a transaction is started for each SQL statement listed in the table below, unless there is already an open transaction, but is not committed on completion. Subsequent commands join the transaction until you issue a COMMIT or ROLLBACK. SET ANSI_DEFAULTS ON puts SQL Server into Implicit Mode.&lt;/div&gt;&lt;div&gt;The following SQL statements start transactions. &lt;br /&gt;
&lt;table border="1" cellpadding="2" cols="2" frame="box" rules="all" style="width: 587px;"&gt;&lt;tbody&gt;
&lt;tr valign="top"&gt; &lt;td width="45%"&gt;ALTER TABLE&lt;/td&gt; &lt;td width="55%"&gt;INSERT&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="45%"&gt;CREATE&lt;/td&gt; &lt;td width="55%"&gt;OPEN*&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="45%"&gt;DELETE&lt;/td&gt; &lt;td width="55%"&gt;REVOKE&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="45%"&gt;DROP&lt;/td&gt; &lt;td width="55%"&gt;SELECT*&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="45%"&gt;FETCH*&lt;/td&gt; &lt;td width="55%"&gt;TRUNCATE TABLE&lt;/td&gt; &lt;/tr&gt;
&lt;tr valign="top"&gt; &lt;td width="45%"&gt;GRANT&lt;/td&gt; &lt;td width="55%"&gt;UPDATE&lt;/td&gt; &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;*Notice that SELECT, OPEN, and FETCH start transactions but COMMIT and ROLLBACK make only limited sense for them because they don't modify data. Nevertheless the transaction is held open until explicitly ended, depending on your Transaction Isolation Level, or Lock Hints used in the query this can have a major impact on concurrency if you're not careful.&lt;br /&gt;
There is a widely circulated myth that CREATE, ALTER, DROP, and TRUNCATE statements can't be rolled back, they can be if you are in Implicit or Explicit Mode. In Autocommit Mode nothing can be rolled back since any SQL statement automatically commits upon completion.&lt;br /&gt;
A demonstration seems be in order: (issue each statement in turn from Query Analyzer, if you execute them all as a batch it still works, but won't be as clear.) &lt;pre class="code"&gt;SET IMPLICIT_TRANSACTIONS ON

CREATE TABLE TranTest (
 Col1 int IDENTITY,
 Col2 int)

SELECT @@TRANCOUNT 
--Returns 1, indicating that there is one open transaction
--Now let's throw some data into our table with a simple loop
DECLARE @count int
SET @count = 0
WHILE @count &amp;lt;15
 Begin
 INSERT TranTest (Col2)
 VALUES (0)
 SET @count = @count + 1
 END
SELECT @@TRANCOUNT 
/*
This still returns 1 indicating that all our statements so far are part of the same 
transaction. Of course it could also mean that each previous statement was its own 
transaction and was committed when the next statement was executed and only the last 
statement's transaction is now left open, but I will show you that this is not the case.
*/
SELECT * FROM TranTest --We see our data.
COMMIT 
--Makes our table and data changes permanent. 
-- Notice there is no corresponding BEGIN TRAN since we are in Implicit Mode.
SELECT @@TRANCOUNT 
--This should now return 0 to show that there are no open transactions
--Now let's alter the table and add a few more rows to show that the 
-- statements are all lumped into a single transaction
ALTER TABLE TRANTEST ADD Col3 int
DECLARE @count int
SET @count = 0
WHILE @count &amp;lt;15
 Begin
 INSERT TranTest (Col2)
 VALUES (0)
 SET @count = @count + 1
 END
SELECT * FROM TranTest 
--Now we see Col3 and the additional data
SELECT @@TRANCOUNT 
--Returns 1
ROLLBACK
SELECT * FROM TranTest 
/*
Now you see it...Now you don't. Both the ALTER and INSERTs are rolled back 
showing that all the statements are grouped into a single transaction until a
COMMIT or ROLLBACK is issued.. 
*/
&lt;/pre&gt;&lt;h3 class="section"&gt;Nested Transactions&lt;/h3&gt;Transactions can be nested, but some aspects of nested transactions may be a bit surprising for the uninitiated; it does make sense though, so let's take a look. First, unlike stored procedures which are limited to 32 levels, there is apparently no practical limit to the number of nesting levels for transactions. I quit trying to reach the limit when I got to well over 200 million levels--which is way more than I'll ever need! @@TRANCOUNT's return data type is an integer so it would probably run into problems if your nesting level exceeded 2.14 billion or so... To nest transactions you simply string together two or more BEGIN TRAN statements without a COMMIT or ROLLBACK statement "between" them. It should be obvious that transactions can only be nested when in Explicit Mode. Or to be more precise, upon issuing the required BEGIN TRAN statements SQL enters Explicit Mode. &lt;pre class="code"&gt;BEGIN TRAN --We enter Explicit Mode here
INSERT TranTest (Col2)
VALUES (1)
SELECT @@TRANCOUNT
--Returns 1
SELECT * FROM TranTest
BEGIN TRAN
 DELETE TranTest
  WHERE Col1 = (SELECT MIN(Col1) FROM TranTest)
 SELECT @@TRANCOUNT 
 --Returns 2 indicating that there are now two open transactions.
 SELECT * FROM TranTest 
 --We can see that both the INSERT and DELETE appear to have taken effect.
COMMIT 
--Decrements @@TRANCOUNT by one but doesn't really commit the inner transaction.
ROLLBACK 
--Rolls back both transactions and sets @@TRANCOUNT to 0
&lt;/pre&gt;Remember that a transaction--to include any nested transactions--must all commit or rollback together. Because of this, the first COMMIT really does nothing more than closes the inner transaction, it can't make the changes permanent since the inner transaction depends on the outcome  of any higher level transactions. The COMMIT that is issued when @@TRANCOUNT is 1 is what I call the  "Golden COMMIT" and finally makes all the changes permanent. Even though the inner COMMIT doesn't  seem to do much, you can't neglect to issue it. The entire transaction is only committed after one  COMMIT has been issued for every open transaction. In contrast ROLLBACK will always roll back all  open transactions; "if one fails, they all fail" A result of ROLLBACK's behavior is that if the  ROLLBACK is issued and then you try to issue a COMMIT or ROLLBACK you will get a 3902 or 3903 error  respectively which indicates that there are no open transactions to COMMIT or ROLLBACK. For this, and  other reasons I'll get into later, you should include error handling in your SQL code. If you check  @@TRANCOUNT before issuing a COMMIT or ROLLBACK you can pretty well eliminate 3902 and 3903  errors. Just to cement the concept, try the following example: &lt;pre class="code"&gt;DECLARE @Count int
SET @Count = 0
WHILE @Count &amp;lt; 100
BEGIN
  BEGIN TRAN 
  --We aren't doing any work here, just nesting transactions...
  SET @Count = @Count + 1
END
SELECT @@TRANCOUNT 
--Should return 100
COMMIT
SELECT @@TRANCOUNT 
--"99 bottles of beer on the wall..."
ROLLBACK
SELECT @@TRANCOUNT 
--"Go to Jail, don't pass GO..." We're right back to 0
&lt;/pre&gt;The idea of nesting transactions applies to triggers and stored procedures too. If you have a complex set of stored procedures that call each other it can get difficult to follow all the possible paths, but any nested transactions will act exactly as I have already described. I should mention that there is always the error caveat that can really throw a monkey wrench into things. I'll deal with errors in some detail later, but will completely ignore errors for now. Below is a simple example of how you can nest a transaction across stored procedures: &lt;pre class="code"&gt;CREATE PROC TranProc1
AS
BEGIN TRAN --Start the first level transaction
 INSERT TranTest (Col2)
 VALUES (1)
 EXEC TranProc2 --Call the other procedure from within the transaction
COMMIT --First level

CREATE PROC TranProc2
AS
BEGIN TRAN --Start the second level transaction
 DELETE TranTest
 WHERE Col1 = (SELECT MIN(Col1) FROM TranTest)
COMMIT --Second level
&lt;/pre&gt;It doesn't matter where the BEGIN TRAN and COMMIT statements reside between the two procedures. &lt;pre class="code"&gt;CREATE PROC TranProc1
AS
BEGIN TRAN --Start the first level transaction
 INSERT TranTest (Col2)
 VALUES (1)
 BEGIN TRAN --Start the second level transaction
 EXEC TranProc2
 COMMIT --Second level
COMMIT --First level
&lt;/pre&gt;This stored procedure would do exactly the same thing as the first example, assuming that the second procedure lacked the BEGIN TRAN and COMMIT statements. Actually, even if the second procedure had the BEGIN...COMMIT statements in it, this procedure would work fine and perform the same function, we would just have three levels of transactions, the second of which would do nothing. So where you place your BEGIN...COMMIT is pretty much a matter of preference, not function. So far we have discussed SQL Server's Transaction Modes and the idea of nested transactions. I have tried to show how these two concepts relate to each other and hinted at how they relate to some of the other concepts listed in the introduction.&amp;nbsp;&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-7055310400707142659?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/IoKAGkTB0fA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/7055310400707142659/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2009/12/transactions-part-1.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/7055310400707142659?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/7055310400707142659?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/IoKAGkTB0fA/transactions-part-1.html" title="Transactions - Part 1" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2009/12/transactions-part-1.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUcNRno_eip7ImA9WxBTGEU.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-8311957297211410226</id><published>2009-12-15T05:18:00.000-08:00</published><updated>2009-12-15T05:18:17.442-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-15T05:18:17.442-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Optimizer Join Methods" /><category scheme="http://www.blogger.com/atom/ns#" term="Join" /><category scheme="http://www.blogger.com/atom/ns#" term="How To Use JOIN in  T-SQL" /><title>Optimizer Join Methods</title><content type="html">&lt;h3 class="section"&gt;Optimizer Join Methods&lt;/h3&gt;SQL Server uses three join operators to process joins within a query. For smaller result sets, the optimizer might use a nested loop join but for larger results, a hash join or merge join may be used. The Query Optimizer always tries to identify the fastest way to join tables and will sometimes rearrange or sort tables in order to utilize a faster join method. &lt;br /&gt;
&lt;b&gt;Nested loops&lt;/b&gt;&lt;br /&gt;
The nested loop join, also called a nested iteration, is the preferred join method for simple queries. The nested loop join operator has two inputs: an outer table and an inner table. When viewed in an execution plan, the top input for the nested loop join operator is the outer table while the bottom input to the nested loop join operator is the inner table. The outer table is located just below the nested loop operator in the execution plan. &lt;br /&gt;
The nested loop can be thought of as a set of loops, with the outer table being the outer loop and the inner table being the inner loop. The looping mechanism that a nested loop uses takes a row from the outer table and uses that row to scan the inner table for matching rows. If there is a matching row, the output is produced and if there is no matching rows; the mechanism will just go back to the outer table again and take the next row, repeating the scan of the inner table for matches. This process continues until all the rows in the outer table have been processed. &lt;br /&gt;
You can see how many times the inner table has been scanned by viewing the Executes column for the inner table in the execution plan. This will tell you how many rows of data exist in the outer table. Generally, the inner table or outer table selection for the nested loop is not important, but the optimizer tries to choose a smaller table for the inner loop if there is no index on both of the tables. &lt;br /&gt;
In cases where the inner table is very large, there is a possibility that the data pages which are accessed for the inner loop scan will be discarded between scans due to memory constraints on the server. The optimizer tries to see if the smaller table will be useful for the inner loop to keep the number of data pages small which may result in the pages not being flushed from the cache. If this will not work, it will choose another inner table, if there is no index on it. If there is an index, then it might choose a bigger table as an inner table just to speed up the scan process. &lt;br /&gt;
The optimizer might choose to perform a sort on some occasions to improve the seeking in the inner input. If we are joining the Customer table with the Orders table in the Northwind database, the customerIDs in the Orders table are random. If the Orders table is chosen as an outer table, then the outer loop takes each row of the outer table and searches the inner table, and then tries to find the match for the outer table. Because the customer IDs are not ordered in the outer table, the optimizer goes to the inner table may have to bring back data page that were previously placed into the cache for an earlier outer loop row if that data has been discarded. In this situation, it would be very useful for the optimizer to perform a sort on the Order table so that all the CustomerIDs will be group together allowing the data pages to be used multiple times in a row. So if you see a sort on the outer table in the execution plan, it's not bad. It's there to improve seeking capabilities or to adjust the performance of the query. &lt;br /&gt;
Nested loop joins are good in the fact that they consume very little memory and are usually superior to merge and hash joins on smaller inputs. One rule of thumb is to try to make sure that the joining columns always have an index on them, which may allow the optimizer to utilize a nested loop. &lt;br /&gt;
&lt;b&gt;Example of Nested Loop Join&lt;/b&gt;&lt;br /&gt;
USE pubs&lt;br /&gt;
GO&lt;br /&gt;
&lt;br /&gt;
SET STATISTICS PROFILE ON&lt;br /&gt;
GO&lt;br /&gt;
&lt;br /&gt;
SELECT a.au_id, a.au_fname + ' ' + a.au_lname, ta.title_id&lt;br /&gt;
FROM dbo.authors a&lt;br /&gt;
INNER JOIN dbo.titleauthor ta&lt;br /&gt;
ON a.au_id = ta.au_id&lt;br /&gt;
GO&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Execution Plan (Abridged)&lt;/b&gt;&lt;br /&gt;
SELECT a.au_id, a.au_fname + ' ' + a.au_lname, ta.title_id  FROM dbo.authors a  &lt;br /&gt;
INNER JOIN dbo.titleauthor ta  ON a.au_id = ta.au_id&lt;br /&gt;
|--Compute Scalar(DEFINE:([Expr1002]=[a].[au_fname]+' '+[a].[au_lname]))&lt;br /&gt;
|--Nested Loops(Inner Join, OUTER REFERENCES:([a].[au_id]))&lt;br /&gt;
|--Index Scan(OBJECT:([pubs].[dbo].[authors].[ncl_authors_phone] AS [a]))&lt;br /&gt;
|--Index Seek(OBJECT:([pubs].[dbo].[titleauthor].[auidind] AS [ta]), SEEK:([ta].[au_id]=[a].[au_id]) &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Merge Join: &lt;/b&gt;&lt;br /&gt;
Like the nested loop join, the merge join has two inputs with indexes being required on both inputs. The merge mechanism gets data rows from both inputs and compares each row of data. A matching row is created if both compared rows are equal. If the rows from both inputs are not equal, the row of data with the lower value is discarded and another row is obtained from the same input as the discarded row. The merge mechanism continues until all rows of data from both inputs are processed and all data has been merged into one result set. &lt;br /&gt;
With merge joins you might see a MANY-TO-MANY:() merge join in predicate along with the merge join operator. This means that the merge mechanism is performing a MANY-TO-MANY join within the tables. Often during the MANY-TO-MANY join operation, temporary tables are created for the MANY-TO-MANY join. This table creation will usually display a Table Spool operator just above the outer table as well as just above the inner table. The reason it generates this Table Spool is, in the case of many to many, it has to revisit the duplicate rows again; it has to rewind to the first duplicate value again. So in case of rewinding or revisiting the duplicates, it will be much more optimized if a temporary table is created and it visits those keys in the temporary table, rather than going back to the data page and reading the data row there. &lt;br /&gt;
Merge joins will be considered for all join clauses, except for CROSS JOIN and FULL JOIN clauses, with at least one of the joined columns having unique data. The presence of an ORDER BY clause will increase the chance that a merge join will be used. Merge joins are very fast if the columns are pre-indexed or pre-sorted. The optimizer might perform sorting on the fly, even if the columns are not in a sorted order, based on the cost if it decides if this sort is less costly than a corresponding nested loop join or hash join. &lt;br /&gt;
&lt;b&gt;Example of Merge Join &lt;/b&gt;&lt;br /&gt;
SET STATISTICS PROFILE ON&lt;br /&gt;
GO&lt;br /&gt;
&lt;br /&gt;
USE northwind&lt;br /&gt;
GO&lt;br /&gt;
&lt;br /&gt;
SELECT * FROM dbo.orders o&lt;br /&gt;
INNER JOIN dbo.[order details] od&lt;br /&gt;
ON o.orderid = od.orderid&lt;br /&gt;
GO&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Execution Plan (abridged) &lt;/b&gt;&lt;br /&gt;
SELECT * FROM dbo.orders o  INNER JOIN dbo.[order details] od  ON o.orderid = od.orderid&lt;br /&gt;
|--Merge Join(Inner Join, MERGE:([o].[OrderID])=([od].[OrderID]), RESIDUAL:([od].[OrderID]=[o].[OrderID]))&lt;br /&gt;
|--Clustered Index Scan(OBJECT:([Northwind].[dbo].[Orders].[PK_Orders] AS [o]), ORDERED FORWARD)&lt;br /&gt;
|--Clustered Index Scan(OBJECT:([Northwind].[dbo].[Order Details].[PK_Order_Details] AS [od]), ORDERED FORWARD)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Hash Join &lt;/b&gt;&lt;br /&gt;
As with the other join types, hash joins takes two inputs, a build input and a probe input with the smaller input being the probe input. When viewing an execution plan, the build input will be shown as the upper input and the probe input is shown as the bottom input. Hash joins utilizes two phases: the build phase and the probe phase. &lt;br /&gt;
The build phase consists of the build input being used to build the hash buckets using a hashing function. Hash buckets are like cells in memory, with the buckets being used to group the data into different groups according to the hash keys. &lt;br /&gt;
This grouping takes place when each row of data is taken from the build input on the tables and a hash function is performed to come up with the hash key. The hash key is an expression which consists of the set of columns in the equality predicate. In grouping operations, the columns of the group by list are the hash key. In set operations such as intersection, as well as in the removal of duplicates, the hash key consists of all columns in GROUP BY clause are used as the hash key. &lt;br /&gt;
If the hash key has a unique contiguous key, the key is know as minimal perfect hashing with every hash having its own slot with no gaps between the slots in the hash index. If the hash key is unique but noncontiguous, it is known as a perfect hashing with every hash having its own slot but there will be gaps between the slots in the hash index. &lt;br /&gt;
Whichever type of hash key is created, the hash key is used to place the rows of data into different hash buckets. With the possibility of having multiple hash buckets whose values are stored as a linked list and these linked lists are scanned when the probe input comes. &lt;br /&gt;
The probe phase consists of the optimizer taking one record at a time from the probe input and evaluating the hash key from the probe input to the hash buckets. The optimizer finds the appropriate hash bucket and scans all the values in that bucket by scanning the linked list values. During the scanning, the optimizer also tries to find out if there is a match to the hash key. If there is a match, an output is produced. If there is no match, then the optimizer continues with the next full value. Because the optimizer has to build the hash tables, the build input is completely read before actually reading the probe input. Then the optimizer goes to the probe table and reads all the rows in the probe input. The optimizer generally chooses a small table as a build table because all of the hash tables will be stored in memory. When the optimizer can place all the hash buckets into memory, the hash is called a “In-Memory Hash Join”. This fact makes hash joins generally memory-intensive. &lt;br /&gt;
Ideally, all these hash tables will be sitting in the memory itself, if they're small enough but if they're not small enough, SQL Optimizer will partition the build input and the probe input into multiple files and store them on a disk with each of these partitions being brought into the memory as they're needed. Using the hash function on the hash keys guarantees that any two joining records must be in the same pair of files; therefore, the problem of joining two large inputs has been reduced to multiple instances of the same problem, but of smaller size - a prototypical “divide-and-conquer” algorithm. In other words, we simply apply the same algorithm again to each pair of partition files -with a modified hash function, of course. This type of hash join using multiple files is called a “Grace Hash Join” and since the disk is involved, additional I/O are needed to get the partitions from the list to the memory. This process will cause performance degradation. &lt;br /&gt;
If the build input is so large that inputs for a standard external merge sorts would require multiple merge levels, multiple partitioning steps and multiple partitioning levels, a Recursive Hash Join will be used. If only some of the partitions are large, additional partitioning steps are used for only those specific partitions and in order to make all partitioning steps as fast as possible, large, asynchronous I/O operations are used so that a single thread can keep multiple disk drives busy. &lt;br /&gt;
If the build input is larger but not a lot larger than the available memory, elements of in-memory hash join and grace hash join are combined in a single step, producing a hybrid hash join. &lt;br /&gt;
Sometimes a residual predicate may be involved. If so, it will also use additional columns for when it is trying to compare the probe input value with a build probe input value. It generally uses a hash key value of the probe input versus the hash key value of the build input. There are some situations where it has to do a comparison of the actual column values of the probe input versus the actual column values of the build input. So in those situations you might see a residual predicate in the execution plan. &lt;br /&gt;
During the phases of the hash join the SQL Server optimizer might determine that the wrong table was used for the build input. In this case the optimizer will reverse the build and probe inputs in a process called “Role Reversal” in order to assure that the smaller of the two inputs is being used for the probe input. &lt;br /&gt;
Hash joins are very useful for ad hoc types of queries. Ad hoc queries are the queries for which we don't know what type of columns are used in the WHERE clause, so we cannot have indexes on all the columns. There is no index on a column, and those columns are mentioned in the WHERE clause, then hash join is the best one, because it scans the whole table and then performs the join on those tables. &lt;br /&gt;
When both join inputs are large and of similar size, performs as good as merge join, but when join inputs are large but differ significantly in size, a hash join will usually outperform a merge join by a fair margin. Remember, hash joins are both memory and CPU intensive and in the case of grace and recursive hash joins they can also require additional I/O costs. &lt;br /&gt;
&lt;b&gt;Example of Hash Join&lt;/b&gt;&lt;br /&gt;
USE pubs&lt;br /&gt;
GO&lt;br /&gt;
&lt;br /&gt;
SELECT * INTO #ttable1 FROM dbo.authors&lt;br /&gt;
&lt;br /&gt;
SELECT * INTO #ttable2 FROM dbo.titleauthor&lt;br /&gt;
&lt;br /&gt;
SELECT a.au_id, b.title_id &lt;br /&gt;
FROM #ttable1 a&lt;br /&gt;
INNER JOIN #ttable2 b&lt;br /&gt;
ON a.au_id = b.au_id&lt;br /&gt;
&lt;br /&gt;
drop table #ttable1&lt;br /&gt;
drop table #ttable2&lt;br /&gt;
GO&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Execution Plan (Abridged)&lt;/b&gt;&lt;br /&gt;
SELECT a.au_id, b.title_id   FROM #ttable1 a  INNER JOIN #ttable2 b  ON a.au_id = b.au_id&lt;br /&gt;
|--Hash Match(Inner Join, HASH:([a].[au_id])=([b].[au_id]), RESIDUAL:([b].[au_id]=[a].[au_id]))&lt;br /&gt;
|--Table Scan(OBJECT:([tempdb].[dbo].[#ttable1_____________________________00000000001A] AS [a]))&lt;br /&gt;
|--Table Scan(OBJECT:([tempdb].[dbo].[#ttable2_____________________________00000000001A] AS [b]))&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
In this case, a simple index on the join columns will change the join method to a different type of join instead of a hash join. &lt;br /&gt;
CREATE NONCLUSTERED INDEX ncl_ttable1 ON #ttable1(au_id)&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Execution Plan (Abridged)&lt;/b&gt;&lt;br /&gt;
SELECT a.au_id, b.title_id   FROM #ttable1 a  INNER JOIN #ttable2 b  ON a.au_id = b.au_id&lt;br /&gt;
|--Nested Loops(Inner Join, OUTER REFERENCES:([b].[au_id]))        |--Table Scan(OBJECT:([tempdb].[dbo].[#ttable2_____________________________00000000001A] AS [b]))&lt;br /&gt;
|--Index Seek(OBJECT:([tempdb].[dbo].[#ttable1_____________________________00000000001A].[ncl_ttable1]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Summary&lt;/b&gt;&lt;br /&gt;
Understanding the different types of joins used by the optimizer will help developers and DBAs understand how the optimizer is routing their queries. Developers often create queries without knowing that it would only take a few “tweaks” to produce an execution plan that utilizes one optimizer join method over another. These small “tweaks” can have dramatic effects on the optimization of the query and the ultimate satisfaction of the query by the end-users. &lt;br /&gt;
Knowing how to read and understand execution plans and the joins used by the optimizer can be one of those “tools” that a DBA can have in their tool chest that separates them from all the other DBAs in their company.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-8311957297211410226?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/3Syrf8oQTwI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/8311957297211410226/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2009/12/optimizer-join-methods.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/8311957297211410226?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/8311957297211410226?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/3Syrf8oQTwI/optimizer-join-methods.html" title="Optimizer Join Methods" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2009/12/optimizer-join-methods.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ck4MSXk6fyp7ImA9WxBTGEs.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-8028120938376415822</id><published>2009-12-14T23:09:00.000-08:00</published><updated>2009-12-14T23:09:48.717-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-14T23:09:48.717-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="SCPOPE_IDENTITY" /><category scheme="http://www.blogger.com/atom/ns#" term="IDENT_CURRENT" /><category scheme="http://www.blogger.com/atom/ns#" term="IDENTITY columns" /><category scheme="http://www.blogger.com/atom/ns#" term="AUTONUMBER" /><title>How do I get the IDENTITY / AUTONUMBER value for the row I inserted?</title><content type="html">&lt;ul&gt;With SQL Server 2000, there are a couple of new functions that are better  than @@IDENTITY. Both of these functions are not global to the connection, which  is an important weak point of @@IDENTITY. After doing an insert, you can  call:

&lt;table border="0" cellpadding="10" cellspacing="0"&gt;&lt;tbody&gt;
&lt;tr&gt; &lt;td class="code"&gt;PRINT  IDENT_CURRENT('table')&lt;br /&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
This will give the  most recent IDENTITY value for 'table' - regardless of whether you created it or  not (this overrides the connection limitation of @@IDENTITY -- which can be  useful).

Another thing you can do is:

&lt;table border="0" cellpadding="10" cellspacing="0"&gt;&lt;tbody&gt;
&lt;tr&gt; &lt;td class="code"&gt;PRINT SCOPE_IDENTITY()&lt;br /&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
This  will give the IDENTITY value last created within the current stored procedure,  trigger, etc. 

If you using a version of SQL Server prior to 2000 (or  you are in compatibility mode &amp;lt; 80), the best way is to use a single stored  procedure that handles both the INSERT and the IDENTITY retrieval using  @@IDENTITY.

Here is sample code for the stored procedure:

&lt;table border="0" cellpadding="10" cellspacing="0"&gt;&lt;tbody&gt;
&lt;tr&gt; &lt;td class="code"&gt;CREATE PROCEDURE myProc&lt;br /&gt;
&amp;nbsp; &amp;nbsp; @param1  INT&lt;br /&gt;
AS&lt;br /&gt;
BEGIN&lt;br /&gt;
&amp;nbsp; &amp;nbsp; SET NOCOUNT ON&lt;br /&gt;
&amp;nbsp; &amp;nbsp; INSERT INTO someTable&lt;br /&gt;
&amp;nbsp;  &amp;nbsp; (&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; intColumn&lt;br /&gt;
&amp;nbsp; &amp;nbsp; )&lt;br /&gt;
&amp;nbsp; &amp;nbsp; VALUES&lt;br /&gt;
&amp;nbsp; &amp;nbsp; (&lt;br /&gt;
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;  @param1&lt;br /&gt;
&amp;nbsp; &amp;nbsp; )&lt;br /&gt;
&amp;nbsp; &amp;nbsp; SELECT NEWID =  SCOPE_IDENTITY()&lt;br /&gt;
END&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
And you would call this  from ASP as follows:

&lt;table border="0" cellpadding="10" cellspacing="0"&gt;&lt;tbody&gt;
&lt;tr&gt; &lt;td class="code"&gt;&amp;lt;%&lt;br /&gt;
&amp;nbsp; &amp;nbsp; fakeValue = 5&lt;br /&gt;
&amp;nbsp; &amp;nbsp; set conn =  CreateObject("ADODB.Connection")&lt;br /&gt;
&amp;nbsp; &amp;nbsp; conn.open "&lt;conn _moz-userdefined="" string=""&gt;"&lt;br /&gt;
&amp;nbsp; &amp;nbsp;  set rs = conn.execute("EXEC myProc @param1=" &amp;amp; fakeValue)&lt;br /&gt;
&amp;nbsp; &amp;nbsp;  response.write "New ID was " &amp;amp; rs(0)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; rs.close: set rs =  nothing&lt;br /&gt;
&amp;nbsp; &amp;nbsp; conn.close: set conn =  nothing&lt;br /&gt;
%&amp;gt;&lt;/conn&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
If you are using SQL Server 7.0,  simply change the line in the stored procedure from ...

&lt;table border="0" cellpadding="10" cellspacing="0"&gt;&lt;tbody&gt;
&lt;tr&gt; &lt;td class="code"&gt;SELECT NEWID = SCOPE_IDENTITY()&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
...  to ...

&lt;table border="0" cellpadding="10" cellspacing="0"&gt;&lt;tbody&gt;
&lt;tr&gt; &lt;td class="code"&gt;SELECT NEWID = @@IDENTITY&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
The  reason SCOPE_IDENTITY() is preferred over @@IDENTITY is that if you perform an  INSERT, and that table has an INSERT TRIGGER which then, in turn, inserts into  another table with an IDENTITY column, @@IDENTITY is populated with the second  table's IDENTITY value. So, if you are stuck using SQL Server 7.0 and need a  workaround to retrieving the @@IDENTITY value because you have a trigger that  also inserts into another IDENTITY-bound table, you're in luck. You can add this  code to the first line of the trigger, but you will have to update all of your  application and stored procedure code to deal with this new SELECT:

&lt;table border="0" cellpadding="10" cellspacing="0"&gt;&lt;tbody&gt;
&lt;tr&gt; &lt;td class="code"&gt;CREATE TRIGGER triggerInsert_tablename ON tablename FOR INSERT AS  &lt;br /&gt;
BEGIN&lt;br /&gt;
&amp;nbsp; &amp;nbsp; SELECT @@IDENTITY&lt;br /&gt;
&amp;nbsp; &amp;nbsp; -- rest of trigger's  logic...&lt;br /&gt;
END&lt;br /&gt;
GO&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
With that said, there are  also potential cases where SCOPE_IDENTITY() can fail, but I think this  possibility is more remote than with @@IDENTITY. Observe this repro, provided by  David Portas:

&lt;table border="0" cellpadding="10" cellspacing="0"&gt;&lt;tbody&gt;
&lt;tr&gt; &lt;td class="code"&gt;CREATE TABLE Table1&lt;br /&gt;
(&lt;br /&gt;
&amp;nbsp; &amp;nbsp; i INTEGER IDENTITY(1,1) PRIMARY  KEY,&lt;br /&gt;
&amp;nbsp; &amp;nbsp; x INTEGER NOT NULL UNIQUE&lt;br /&gt;
)&lt;br /&gt;
GO&lt;br /&gt;
&lt;br /&gt;
CREATE TRIGGER  trg_Table1 ON Table1&lt;br /&gt;
INSTEAD OF INSERT&lt;br /&gt;
AS&lt;br /&gt;
BEGIN&lt;br /&gt;
&amp;nbsp; &amp;nbsp; SET NOCOUNT  ON&lt;br /&gt;
&amp;nbsp; &amp;nbsp; INSERT INTO Table1 (x)&lt;br /&gt;
&amp;nbsp; &amp;nbsp; SELECT x FROM  Inserted&lt;br /&gt;
END&lt;br /&gt;
GO&lt;br /&gt;
&lt;br /&gt;
INSERT INTO Table1 (x) VALUES  (1)&lt;br /&gt;
GO&lt;br /&gt;
&lt;br /&gt;
SELECT SCOPE_IDENTITY(),  IDENT_CURRENT('Table1')&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
Result:

&lt;table border="0" cellpadding="10" cellspacing="0"&gt;&lt;tbody&gt;
&lt;tr&gt; &lt;td class="code"&gt;------ ------&lt;br /&gt;
NULL&amp;nbsp;&amp;nbsp;&amp;nbsp;1&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
This is  because the actual INSERT happened outside of the scope of the caller, so  SCOPE_IDENTITY() was not populated there. I have requested that the  documentation for SCOPE_IDENTITY() be updated to reflect the above  scenario.&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-8028120938376415822?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/Jprdb0KJtg0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/8028120938376415822/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2009/12/how-do-i-get-identity-autonumber-value.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/8028120938376415822?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/8028120938376415822?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/Jprdb0KJtg0/how-do-i-get-identity-autonumber-value.html" title="How do I get the IDENTITY / AUTONUMBER value for the row I inserted?" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2009/12/how-do-i-get-identity-autonumber-value.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0MNRHg4eSp7ImA9WxBTF0Q.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-1145825459585660819</id><published>2009-12-14T04:58:00.000-08:00</published><updated>2009-12-14T04:58:15.631-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-14T04:58:15.631-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Index Seek" /><category scheme="http://www.blogger.com/atom/ns#" term="Index Scan" /><category scheme="http://www.blogger.com/atom/ns#" term="Clustered Index Seek" /><category scheme="http://www.blogger.com/atom/ns#" term="Show Execution Plan" /><category scheme="http://www.blogger.com/atom/ns#" term="Execution Plans" /><title>Execution Plans</title><content type="html">When I started using SQL Server, I was not using the “Show Execution Plan” properly to analyse the query. I always thought that when I compose a query, it is the best it can be; I never even think of performance on my query and I did not give much importance to looking at the “Execution Plan” on my query.&amp;nbsp; In the initial days of my career, I was happy to retrieve the data, not even knowing how it was returned to me and what sort of “Execution Plan” was used on the query. I presumed SQL Server would handle the query performance. I think it is the nature for software engineers, when starting out, or when first learning new technology, not to take the time to learn everything they really need to know before writing a code. Perhaps this is because of competition and immaturity in the IT field.&lt;br /&gt;
Days are starting moving and data has been growing on the database file.&amp;nbsp; On one fine day, my customer was not happy with the performance on the query using the application. He has come to me with an unhappy face stating that he was spending extra time to finish his office work due to the slowness of the queries.&amp;nbsp; Initially, I told the customer to increase the system resources, like increase the hard disk in the machine as a temporary solution. Albeit, hard disk cost is cheap but it is not the permanent solution for the performance degradation in the query. He agreed in half-mind that he will do the required things from his side but he asked me to re-look and fine-tune the queries for a permanent solution instead of suggesting he keep on increasing the system resources.&amp;nbsp; I have to consider his personal opinion because customer satisfaction is important in the IT industry.&amp;nbsp; I have promised to my customer that I will re-look and fine-tune the queries.&lt;br /&gt;
HOW?&lt;br /&gt;
In the initial days of my career, I know the basic things in MS-SQL Server. &amp;nbsp; To be frank, I was not having any idea on my mind whilst doing promise to my customer.&amp;nbsp; But, I personally felt that I would do something to achieve the task with the help of “GOOGLE” and “BOL”.&amp;nbsp; Thanks to “GOOGLE” and “BOL”.&lt;br /&gt;
I was reading MS-SQL books, BOL help and searching on websites. &amp;nbsp; I have heard and crossed the concept of “Show Execution Plan”.&amp;nbsp; We can set this option ON using SQL Query Analyzer.&amp;nbsp; Show Execution Plan is an important graphical tool that enables the developer and DBA to analyse, assist and optimise the query and improve the performance of the query.&lt;br /&gt;
Show Execution Plan displays different icons for a different task. &amp;nbsp; I am mainly interested on “&lt;b&gt;Table Scan&lt;/b&gt;”, “&lt;b&gt;Index Scan&lt;/b&gt;” &amp;amp; “&lt;b&gt;Index Seek&lt;/b&gt;” &amp;amp; “&lt;b&gt;Clustered Index Scan&lt;/b&gt;” &amp;amp; “&lt;b&gt;Clustered Index Seek&lt;/b&gt;” icons in this article. &amp;nbsp; May be I could be writing in my future article on other icons.&amp;nbsp; &lt;br /&gt;
As the days and years have moved along like a formula 1 race car, I have decided that it is time for me to fully understand how “&lt;b&gt;Table Scan&lt;/b&gt;”, “&lt;b&gt;Index Scan&lt;/b&gt;” &amp;amp; “&lt;b&gt;Index Seek&lt;/b&gt;” &amp;amp; “&lt;b&gt;Clustered Index Scan&lt;/b&gt;” &amp;amp; “&lt;b&gt;ClusteredIndex Seek&lt;/b&gt;” icons work. &lt;br /&gt;
Now, I am ready to analyse the queries and upgrade the performance on my queries. &amp;nbsp; Before analysing the queries, a few questions have been raised in my mind.&lt;br /&gt;
&lt;b&gt;When did MS-SQL Server use “Table Scan”?&lt;/b&gt;&lt;br /&gt;
&lt;b&gt;When did MS-SQL Server use “Index Scan”?&lt;/b&gt;&lt;br /&gt;
&lt;b&gt;When did MS-SQL Server use “Index Seek”?&lt;/b&gt;&lt;br /&gt;
&lt;b&gt;When did MS-SQL Server use “Clustered Index Scan”?&lt;/b&gt;&lt;br /&gt;
&lt;b&gt;When did MS-SQL Server use “Clustered Index Seek”?&lt;/b&gt;&lt;br /&gt;
I am mainly concern that in what bases MS-SQL Server would use one of this option to analyse the query.&amp;nbsp; I now have taken that time, and here is what I learned. This information should be useful to new developers and DBAs. I have decided to write this article to share my knowledge to help other to get better idea on these methods.&lt;br /&gt;
If you like, you can read this article as is, or in front of a SQL Server, following along with my exercises.&lt;br /&gt;
&lt;b&gt;Getting Started&lt;/b&gt;&lt;br /&gt;
To explain the SQL Server “&lt;b&gt;Table Scan&lt;/b&gt;”, “&lt;b&gt;Index Scan&lt;/b&gt;” &amp;amp; “&lt;b&gt;Index Seek&lt;/b&gt;” &amp;amp; “&lt;b&gt;Clustered Index Scan&lt;/b&gt;” &amp;amp; “&lt;b&gt;Clustered Index Seek&lt;/b&gt;” using Show Execution Plan; let’s start by creating a table and adding some sample data using the following script. I presume you have got a database to use the following script; otherwise you will need to create one.&lt;br /&gt;
&lt;pre class="code"&gt;Create Table PerformanceIssue
(
&amp;nbsp;&amp;nbsp;&amp;nbsp;PRID UniqueIdentifier NOT NULL,
   PRCode Int NOT NULL,
   PRDesc Varchar (100) NOT NULL
)
ON [PRIMARY]
&lt;/pre&gt;Table has been created and this table now needs some data; so let’s add 100,000 records in this table using this script. Script will take few minutes to finish executing insert statement for 100,000 records in a table, please bear with the query to get finish.&lt;br /&gt;
&lt;pre class="code"&gt;Declare @Loop  Int
Declare @PRID  UniqueIdentifier
Declare @ PRDesc Varchar (100)

Set @Loop = 1
Set @ PRDesc = ''

WHILE @Loop &amp;lt;= 100000
 BEGIN
&amp;nbsp;&amp;nbsp; Set @PRID =   NewID()
 &amp;nbsp; Set @PRDesc = ' PerformanceIssue - ' +  Convert( Varchar(10),@Loop )
&amp;nbsp;&amp;nbsp; Insert Into  PerformanceIssue Values (@PRID, @Loop, @PRDesc)
&amp;nbsp;&amp;nbsp; Set @Loop = @Loop + 1
 END
&lt;/pre&gt;Script has been successful completed.&amp;nbsp; Data has been inserted in the table.&amp;nbsp; &lt;br /&gt;
Now, let’s view the contents of the table by executing the following command in Query Analyzer:&lt;br /&gt;
&lt;pre class="code"&gt;Select PRID,  PRCode, PRDesc
 From PerformanceIssue
GO
&lt;/pre&gt;I would not display the output of the table due to 100,000 records and it will occupy more pages to display the output.&amp;nbsp; As you would expect, the data we inserted earlier has been displayed in Query Analyzer.&amp;nbsp; &lt;br /&gt;
As I told you before about the article, I would be explaining the “&lt;b&gt;Table Scan&lt;/b&gt;”, “&lt;b&gt;Index Scan&lt;/b&gt;” &amp;amp; “&lt;b&gt;Index Seek&lt;/b&gt;” &amp;amp; “&lt;b&gt;Clustered Index Scan&lt;/b&gt;” &amp;amp; “&lt;b&gt;Clustered Index Seek&lt;/b&gt;”.&amp;nbsp; Which one could be the ideal one that will upgrade the performance in the database system?&lt;br /&gt;
SQL Server returns data as it stored and we would like to know that what scan mechanism MS-SQL server has incorporate to retrieve the data. Let’s start with the “&lt;b&gt;Table Scan&lt;/b&gt;” topic. This got me to think about the following. When does “&lt;b&gt;Table Scan&lt;/b&gt;” happen in SQL Server for a table?&lt;br /&gt;
Let’s enable the “Show Execution Plan” in Query Analyzer using Query select “Show Execution Plan” or Alt + Q. Ctr + K is a shortcut method.&lt;br /&gt;
Let’s view the graphical execution plan for the query.&lt;br /&gt;
&lt;pre class="code"&gt;Select PRID,  PRCode, PRDesc 
From PerformanceIssue
GO
&lt;/pre&gt;&lt;img border="0" height="267" src="http://www.sqlservercentral.com/articles/Administration/executionplans/1345/ExecutionPlans/image002.jpg" v:shapes="_x0000_i1025" width="335" /&gt;&lt;br /&gt;
Query has been executed and saw the executions plan for the query.  MS-SQL server for the query has used “&lt;b&gt;Table Scan&lt;/b&gt;” method.&amp;nbsp; Data have been retrieved using "&lt;b&gt;Table Scan&lt;/b&gt;" method. &amp;nbsp; I was asking myself why the “&lt;b&gt;Table Scan&lt;/b&gt;” has happen and in what bases MS-SQL server uses "&lt;b&gt;Table Scan&lt;/b&gt;" method.&amp;nbsp; It is a simple table that contains 100,000 records.&amp;nbsp; Is it because I have asked MS-SQL server to retrieve 100,000 records from the table?&amp;nbsp; When I was thinking in different angles. &amp;nbsp; Some perplex in my mind. &amp;nbsp; Now, my question how do I prevent "&lt;b&gt;Table Scan&lt;/b&gt;" method on my query. &amp;nbsp; I was not having idea what scan mechanism MS-SQL server has used when I was executing the query.&amp;nbsp; I decided to optimise the query.&amp;nbsp; How? &amp;nbsp; I selected two columns [PRID, PRCode] out of three columns in the select statement.&lt;br /&gt;
&lt;pre class="code"&gt;Select PRID,  PRCode
 From  PerformanceIssue
go
&lt;/pre&gt;&lt;img border="0" height="279" src="http://www.sqlservercentral.com/articles/Administration/executionplans/1345/ExecutionPlans/image004.jpg" v:shapes="_x0000_i1026" width="339" /&gt;&lt;br /&gt;
Query has been executed and execution plan is same as my previous query. &amp;nbsp; I still want to optimise the query and decided to have only one [PRID] column in the select statement.&lt;br /&gt;
&lt;pre class="code"&gt;Select PRID 
 From PerformanceIssue

GO
&lt;/pre&gt;&lt;img border="0" height="285" src="http://www.sqlservercentral.com/articles/Administration/executionplans/1345/ExecutionPlans/image006.jpg" width="325" /&gt;&lt;br /&gt;
Query has been executed and execution plan is same as my first query. &amp;nbsp; Estimated row size was showing difference on the three-execution plan.&amp;nbsp; We are not giving much importance to the Estimated row size attribute. Quickly I decided to retrieve only one record and see what could be the execution plan for the query.&amp;nbsp;&amp;nbsp; I have executed the following query.&lt;br /&gt;
&lt;pre class="code"&gt;Select PRID,  PRCode, PRDesc 
 From PerformanceIssue
 Where PRID = 'D386C151-5F74-4C2A-B527-86FEF9712955'
-- PRID GUID value might be differ in your machine

GO
&lt;/pre&gt;Execution of the query was over and execution plan has been displayed.&lt;br /&gt;
&lt;img border="0" height="279" src="http://www.sqlservercentral.com/articles/Administration/executionplans/1345/ExecutionPlans/image008.jpg" width="333" /&gt;&lt;br /&gt;
Query has incorporated “&lt;b&gt;Table Scan&lt;/b&gt;” method to display the data. I saw the execution plan and still execution plan uses “&lt;b&gt;Table Scan&lt;/b&gt;” method on my query. &lt;br /&gt;
Then, I need to think some other alternative solution by spending some sleepless nights to avoid  "&lt;b&gt;Table Scan&lt;/b&gt;" method. "&lt;b&gt;Sleepless nights&lt;/b&gt;", yes, it is common in IT industry.&amp;nbsp; I was talking myself how do I solve this problem.&amp;nbsp; First think was come to my mind was "Index" in a table.&amp;nbsp;Currently, I haven't added an “Index" in PerformanceIssue table.&amp;nbsp; Is it because there is no index in the table? Query has used "&lt;b&gt;Table Scan&lt;/b&gt;" method.&amp;nbsp; Is that the case, I decided to create non-clustered index on PRID column in PerformanceIssue table.&amp;nbsp; Is this true adding index will solve my queries using "&lt;b&gt;Table Scan&lt;/b&gt;" method?  We will discuss those questions on "&lt;b&gt;Index Scan&lt;/b&gt;" &amp;amp; “&lt;b&gt;Index Seek&lt;/b&gt;” topic.&amp;nbsp; Let's now move on to "&lt;b&gt;Index Scan&lt;/b&gt;" &amp;amp; “&lt;b&gt;Index Seek&lt;/b&gt;” topic.&lt;br /&gt;
&lt;h4&gt;Index Scan &amp;amp; Index Seek&lt;/h4&gt;Let’s create a non-clustered index on PRID column on my table.&lt;br /&gt;
&lt;pre class="code"&gt;CREATE UNIQUE NONCLUSTERED INDEX UNC_PRID
&amp;nbsp; ON PerformanceIssue (PRID)
GO
&lt;/pre&gt;Non-Clustered index has been created successfully.&amp;nbsp;Index has been created.&amp;nbsp; I presume you have knowledge on non-clustered index and how non-clustered index works in MS-SQL Server.&lt;br /&gt;
&lt;br /&gt;
Let's execute the select statement and view the graphical execution plan for the query.&amp;nbsp; &lt;br /&gt;
&lt;pre class="Code"&gt;Select PRID,  PRCode, PRDesc 
 From PerformanceIssue
go
&lt;/pre&gt;&lt;img border="0" height="267" src="http://www.sqlservercentral.com/articles/Administration/executionplans/1345/ExecutionPlans/image002.jpg" width="335" /&gt;&lt;br /&gt;
Query has been executed and saw the executions plan for the query. "&lt;b&gt;Table Scan&lt;/b&gt;" method has been used on the query.&amp;nbsp; I was really shocked to see the "&lt;b&gt;Table Scan&lt;/b&gt;" method used on the query. I was more perplexed and rest less why the “&lt;b&gt;Table Scan&lt;/b&gt;” has happen?&amp;nbsp; Why MS-SQL Server has not utilise the non-clustered index on my query. I was asking myself when  would "&lt;b&gt;Index Scan&lt;/b&gt;" method come to the picture. I decided to optimise the query. I have selected two columns [PRID, PRCode] out of three columns. &lt;br /&gt;
&lt;pre class="code"&gt;Select PRID,  PRCode From  PerformanceIssue
GO
&lt;/pre&gt;&lt;img border="0" height="279" src="http://www.sqlservercentral.com/articles/Administration/executionplans/1345/ExecutionPlans/image004.jpg" width="339" /&gt;&lt;br /&gt;
Query has been executed and execution plan is same as my previous query. I still want to optimise the query and decided to have only one [PRID] column in the select statement.&lt;br /&gt;
&lt;pre class="code"&gt;Select PRID 
 From PerformanceIssue
GO
&lt;/pre&gt;Query has been executed and saw the execution plan for the query.&lt;br /&gt;
&lt;img border="0" height="291" src="http://www.sqlservercentral.com/articles/Administration/executionplans/1345/ExecutionPlans/image010.jpg" width="379" /&gt;&lt;br /&gt;
"&lt;b&gt;Index Scan&lt;/b&gt;" has been used on the query.&amp;nbsp;I am glad to see the "&lt;b&gt;Index Scan&lt;/b&gt;" has been used. My query is using "&lt;b&gt;Index Scan&lt;/b&gt;" method and my next question is when  would “&lt;b&gt;Index Scan&lt;/b&gt;” method happen and how?&amp;nbsp; I have created an index on "PRID" column and selected indexed column [PRID] in the select statement.&amp;nbsp; Query has been executed and MS-SQL server scans all the index pages and uses "&lt;b&gt;Index Scan&lt;/b&gt;" method.&amp;nbsp; We have clubbed the indexed and non-indexed columns in the select statement. MS-SQL server was not able recognized and use "&lt;b&gt;Index Scan&lt;/b&gt;" method for the previous two queries. MS-SQL Server would use "&lt;b&gt;Index Scan&lt;/b&gt;" method only indexed column on the select statement. I do not know how exactly MS-SQL Server does behind the code.&amp;nbsp; After this practical knowledge, understand and execution of the queries, I have come to conclusion that we select only the indexed column in the select statement.&amp;nbsp; MS-SQL server would use "&lt;b&gt;Index Scan&lt;/b&gt;" method for the query. &lt;br /&gt;
I was refreshing myself how  do  I incorporate “&lt;b&gt;Index Seek&lt;/b&gt;” method.&amp;nbsp;When I was using the word “Seek”, first think would strike to my mind was finding a particular/specific one.&amp;nbsp;For an example, I want to find out the MS-SQL server scholars and experts in the earth.&amp;nbsp;The keyword/filter is "MS-SQL Server" on skillset column.&amp;nbsp; I will filter down the scholars and experts name from knowledge table using the skillset column.&amp;nbsp; My output would be restricted to the particular hunt.&amp;nbsp; &lt;br /&gt;
Let’s see how to incorporate the “&lt;b&gt;Index Seek&lt;/b&gt;” using our  PerformanceIssue table.&amp;nbsp; We have already executed the query with different combination.&amp;nbsp; Execution plan has been display the "&lt;b&gt;Table Scan&lt;/b&gt;" and "&lt;b&gt;Index Scan&lt;/b&gt;" method.&amp;nbsp; We will see how to get the "&lt;b&gt;Index Seek&lt;/b&gt;" in the execution plan using our query.&amp;nbsp; After thinking all the options, I have decided to use WHERE clause in the select statement.&amp;nbsp; I confirmed to use three different select statements using WHERE clause. To find out which select statement MS-SQL Server uses "&lt;b&gt;Index Seek&lt;/b&gt;" in the execution plan. &amp;nbsp; I started with the 1&lt;sup&gt;st&lt;/sup&gt; select statement with the WHERE clause.&lt;br /&gt;
&lt;pre class="code"&gt;Select PRID,  PRCode, PRDesc 
 From PerformanceIssue
 Where  PRCode = 8
go
&lt;/pre&gt;Query has been executed and "&lt;b&gt;Table scan&lt;/b&gt;" has been used in the execution plan.&lt;br /&gt;
&lt;img border="0" height="281" src="http://www.sqlservercentral.com/articles/Administration/executionplans/1345/ExecutionPlans/image012.jpg" width="337" /&gt;&lt;br /&gt;
I moved on to use the 2&lt;sup&gt;nd&lt;/sup&gt; select statement with the WHERE clause.&lt;br /&gt;
&lt;pre class="code"&gt;Select PRID,  PRCode, PRDesc
 From PerformanceIssue
 Where  PRDesc = ' PerformanceIssue - 8'
go
&lt;/pre&gt;Query has been executed and "&lt;b&gt;Table scan&lt;/b&gt;" has been used in the execution plan. &lt;br /&gt;
&lt;img border="0" height="287" src="http://www.sqlservercentral.com/articles/Administration/executionplans/1345/ExecutionPlans/image014.jpg" width="327" /&gt;&lt;br /&gt;
I moved on to use the 3&lt;sup&gt;rd&lt;/sup&gt; select statement with the WHERE clause.&lt;br /&gt;
&lt;pre class="code"&gt;Select PRID,  PRCode, PRDesc
 From PerformanceIssue 
 Where PRID = 'D386C151-5F74-4C2A-B527-86FEF9712955'

-- PRID GUID value might be differ in your machine
go
&lt;/pre&gt;&lt;img border="0" height="293" src="http://www.sqlservercentral.com/articles/Administration/executionplans/1345/ExecutionPlans/image016.jpg" width="355" /&gt; &lt;img border="0" height="291" src="http://www.sqlservercentral.com/articles/Administration/executionplans/1345/ExecutionPlans/image018.jpg" width="361" /&gt;&lt;br /&gt;
Query has been executed and "&lt;b&gt;Index Seek&lt;/b&gt;" along with "&lt;b&gt;Bookmark Lookup&lt;/b&gt;" has been used in the execution plan. We have understand after execution the three select statement with the WHERE clause. "&lt;b&gt;Index Seek&lt;/b&gt;" has been used when we use the indexed column [PRID] in the WHERE clause.&amp;nbsp; We have selected non-indexed column in the select statement. Execution plan has used "&lt;b&gt;Index Seek&lt;/b&gt;" and "&lt;b&gt;Bookmark Lookup&lt;/b&gt;". "&lt;b&gt;Bookmark Lookup&lt;/b&gt;" has been used to find out the non-index column value from the table or Clustered Index. &amp;nbsp; We can ignore the "&lt;b&gt;Bookmark Lookup&lt;/b&gt;" in the execution plan by removing non-indexed column from the select statement.&amp;nbsp; Execution plan would be "&lt;b&gt;Index Seek&lt;/b&gt;".&amp;nbsp; This does not make meaningful to the select statement to retrieve the PRID value which we already passed in the WHERE clause.&lt;br /&gt;
I personally consider "&lt;b&gt;Index Seek&lt;/b&gt;" is better than "&lt;b&gt;Index Scan&lt;/b&gt;" and "&lt;b&gt;Table Scan&lt;/b&gt;" in couple of points to upgrade the performance in the system.&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;&lt;b&gt;Index Seek&lt;/b&gt;" does not need to scan through "Table" and "Index" page as “&lt;b&gt;Table Scan&lt;/b&gt;” and “&lt;b&gt;Index Scan&lt;/b&gt;” does.&amp;nbsp; &lt;/li&gt;
&lt;li&gt;&lt;b&gt;Index Seek&lt;/b&gt;" will search and retrieve the hunted row [used in WHERE clause] much faster than the "&lt;b&gt;Index Scan&lt;/b&gt;" and "&lt;b&gt;Table Scan&lt;/b&gt;". &lt;/li&gt;
&lt;/ol&gt;After I finish all the combination, I was explaining to my colleagues. One my colleague asked a genuine question to me.&amp;nbsp;&amp;nbsp; When would MS-SQL server use “&lt;b&gt;Clustered Index Scan&lt;/b&gt;” and “&lt;b&gt;Clustered Index Seek&lt;/b&gt;”? I was speechless at that point of time. I appreciated him and I decided to find the use of “&lt;b&gt;Clustered Index Scan&lt;/b&gt;” and “&lt;b&gt;Clustered Index Seek&lt;/b&gt;” topics.&lt;br /&gt;
I decided to find the use of “&lt;b&gt;Clustered Index Scan&lt;/b&gt;” and “&lt;b&gt;Clustered Index Seek&lt;/b&gt;”. I should have “Clustered index” in a table to get the “&lt;b&gt;Clustered Index Scan&lt;/b&gt;” and “&lt;b&gt;Clustered Index Seek&lt;/b&gt;” in the execution plan.&amp;nbsp; I decided to drop the existing non-clustered index and create a clustered index on PRCode column in PerformanceIssue table.&amp;nbsp; Let's now move on to "&lt;b&gt; Clustered&lt;/b&gt;&lt;b&gt; Index Scan&lt;/b&gt;" &amp;amp; “&lt;b&gt;Clustered Index Seek&lt;/b&gt;” topic.&lt;br /&gt;
&lt;b&gt;Clustered Index Scan &amp;amp; Clustered Index Seek&lt;/b&gt;&lt;br /&gt;
The following script will drop the existing index on PRID column in PerformanceIssue table create a clustered index on  PRCode column in  PerformanceIssue table.&lt;br /&gt;
&lt;pre class="code"&gt;Drop Index  PerformanceIssue.UNC_PRID
GO
CREATE UNIQUE CLUSTERED INDEX UC_PRCode
  ON  PerformanceIssue( PRCode)
go
-------------
Clustered index has been created successfully.&amp;nbsp;&lt;/pre&gt;&lt;pre class="code"&gt;&lt;/pre&gt;I will execute the select statement to find out the “&lt;b&gt;Clustered Index Scan&lt;/b&gt;” in the execution plan.&amp;nbsp; I have executed the following query.&lt;br /&gt;
Select PRID,  PRCode, PRDesc    From PerformanceIssue go  Query has been executed and "&lt;b&gt;Clustered Index Scan&lt;/b&gt;" has been used in the execution plan.&amp;nbsp; &lt;br /&gt;
&lt;img border="0" height="291" src="http://www.sqlservercentral.com/articles/Administration/executionplans/1345/ExecutionPlans/image020.jpg" width="385" /&gt;&lt;br /&gt;
We should have a "Clustered index" in a table to get the "&lt;b&gt;Clustered Index Scan&lt;/b&gt;" in the execution plan for the select statement. We could have a single column or more than one column in the select statement, execution plan would use "&lt;b&gt;Clustered Index Scan&lt;/b&gt;".&amp;nbsp; When do we have "&lt;b&gt;Clustered Index Seek&lt;/b&gt;" in the execution plan? How do we get the "&lt;b&gt;Clustered Index Seek&lt;/b&gt;" in the execution plan? &amp;nbsp; &lt;br /&gt;
We will see how to get the "&lt;b&gt;Clustered Index Seek&lt;/b&gt;" in the execution plan using our query.&amp;nbsp; After I was rolling my head, I took decision to use WHERE clause in the select statement. &amp;nbsp; I confirmed to use three different select statements using WHERE clause.&amp;nbsp; To find out which select statement MS-SQL Server uses "&lt;b&gt;Clustered Index Seek&lt;/b&gt;" in the execution plan.&amp;nbsp; I started with the 1&lt;sup&gt;st&lt;/sup&gt; select statement with the WHERE clause.&amp;nbsp; &lt;br /&gt;
Select PRID,  PRCode, PRDesc   From PerformanceIssue  Where  PRDesc = ' PerformanceIssue - 8' go  Query has been executed and "&lt;b&gt;Clustered Index Scan&lt;/b&gt;" has been used in the execution plan.&amp;nbsp; &lt;br /&gt;
&lt;img border="0" height="287" src="http://www.sqlservercentral.com/articles/Administration/executionplans/1345/ExecutionPlans/image022.jpg" width="397" /&gt;&lt;br /&gt;
I decided to use the 2&lt;sup&gt;nd&lt;/sup&gt; select statement with the WHERE clause.&lt;br /&gt;
Select PRID,  PRCode,  PRDesc    From PerformanceIssue  Where PRID = 'D386C151-5F74-4C2A-B527-86FEF9712955'  -- PRID GUID value might be differ in your machine go   Query has been executed and "&lt;b&gt;Clustered Index Scan&lt;/b&gt;" has been used in the execution plan.&amp;nbsp; &lt;br /&gt;
&lt;img border="0" height="289" src="http://www.sqlservercentral.com/articles/Administration/executionplans/1345/ExecutionPlans/image024.jpg" width="360" /&gt;&lt;br /&gt;
I decided to use the 3&lt;sup&gt;rd&lt;/sup&gt; select statement with the WHERE clause.&lt;br /&gt;
Select PRID,  PRCode, PRDesc    From PerformanceIssue  Where  PRCode = 8 go  Query has been executed and “&lt;b&gt;Clustered Index Seek&lt;/b&gt;” has been used in the execution plan.&lt;br /&gt;
&lt;img border="0" height="287" src="http://www.sqlservercentral.com/articles/Administration/executionplans/1345/ExecutionPlans/image026.jpg" width="379" /&gt;&lt;br /&gt;
Query has been executed and "&lt;b&gt;Clustered Index Seek&lt;/b&gt;" has been used in the execution plan.&amp;nbsp; We have understand after execution the three select statement with the WHERE clause. &amp;nbsp; "&lt;b&gt;Clustered Index Seek&lt;/b&gt;" has been used when we use the indexed column [ PRCode] in the WHERE clause.&amp;nbsp; We have selected non-indexed column in the select statement.&amp;nbsp; Execution plan has used "&lt;b&gt;Clustered Index Seek&lt;/b&gt;". &amp;nbsp; Execution plan will not use "&lt;b&gt;Bookmark Lookup&lt;/b&gt;" for the non-indexed columns in the select statement for the clustered index table.&amp;nbsp; &lt;br /&gt;
I personally feel "&lt;b&gt;Clustered Index Seek&lt;/b&gt;" is better than "&lt;b&gt;Clustered Index Scan&lt;/b&gt;" and "&lt;b&gt;Index Seek&lt;/b&gt;" in couple of points to upgrade the performance in the system.&amp;nbsp; &lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;&lt;b&gt;Clustered Index Seek&lt;/b&gt;" does not need to scan through all the Clustered index pages.&amp;nbsp; &lt;/li&gt;
&lt;li&gt;&lt;b&gt;Clustered Index Seek&lt;/b&gt;" does not create a “&lt;b&gt;Bookmark Lookup&lt;/b&gt;” comparing to "&lt;b&gt;Index Seek&lt;/b&gt;" for the non-indexed columns in the select statement.&amp;nbsp; &lt;/li&gt;
&lt;/ol&gt;I understand the execution plan and gained practical knowledge on execution plan.&amp;nbsp; I know which scan mechanism will improve the performance in the system and bring back the customer smiling face. Now, I will have couple of beer to have peaceful sleep.&lt;br /&gt;
&lt;pre class="code"&gt;Index has been created.&amp;nbsp;
&lt;/pre&gt;I presume you have knowledge on non-clustered index and how non-clustered index works in MS-SQL Server.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-1145825459585660819?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/JfelYhvciFw" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/1145825459585660819/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2009/12/execution-plans.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/1145825459585660819?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/1145825459585660819?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/JfelYhvciFw/execution-plans.html" title="Execution Plans" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2009/12/execution-plans.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A04BSXg9eCp7ImA9WxBTF0s.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-1525579414847552938</id><published>2009-12-13T21:52:00.003-08:00</published><updated>2009-12-13T21:52:38.660-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-13T21:52:38.660-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Null" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL Aggregate Functions and NULL" /><category scheme="http://www.blogger.com/atom/ns#" term="Aggregate Functions" /><title>SQL Aggregate Functions and NULL</title><content type="html">This article was written to help answer some of those questions by explaining how SQL aggregate functions handle NULLs, and to describe some other "gotcha's" when dealing with SQL aggregate functions. Each section of the article ends with a summary of "gotcha's" to look out for when using these function.  &lt;br /&gt;
The ANSI SQL-92 Standard defines five &lt;i&gt;aggregate functions&lt;/i&gt; (which are also referred to in the SQL-92 standard as &lt;i&gt;set functions&lt;/i&gt;.) SQL NULLs receive special internal handling that the database designer/programmer needs to be aware of when using these functions. This article describes how ANSI SQL-92 aggregate functions handle NULLs, per the ANSI SQL-92 standard.&lt;br /&gt;
I reference the ANSI SQL-92 Standard throughout the article, and the sample code requires the Microsoft SQL Server Northwind Sample Database to be run. All samples were run on SQL Server 2000.&lt;br /&gt;
&lt;h3 class="section"&gt;Aggregate Functions&lt;/h3&gt;ANSI SQL-92 defines five &lt;i&gt;aggregate functions&lt;/i&gt;.  For all set functions that specify a column name, the following process takes place behind the scenes:&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;The search expression (the &lt;b&gt;WHERE&lt;/b&gt; clause) is applied.&lt;/li&gt;
&lt;li&gt;The &lt;i&gt;value expression&lt;/i&gt; (scalar functions on the column like &lt;b&gt;COALESCE()&lt;/b&gt;)     is applied to each row of the results of Step 1.&lt;/li&gt;
&lt;li&gt;NULL values are eliminated from our results&lt;span style="color: green;"&gt;*&lt;/span&gt;.&lt;/li&gt;
&lt;/ol&gt;&lt;span style="color: green;"&gt;*NOTE:&amp;nbsp; &lt;b&gt;COUNT(*)&lt;/b&gt; is a special case, which we describe in detail below.&lt;/span&gt;&lt;br /&gt;
ANSI SQL-92 specifies that a warning message will be returned when NULLs are eliminated by set functions.  You can turn on  this option by using the &lt;b&gt;SET ANSI_WARNINGS ON&lt;/b&gt; command.  The &lt;b&gt;SET ANSI_WARNINGS ON&lt;/b&gt; option returns the following error message  &lt;i&gt;"Warning: Null value is eliminated by an aggregate or other set operation." &lt;/i&gt;&lt;br /&gt;
The &lt;b&gt;SET ANSI_WARNINGS ON&lt;/b&gt; also affects  how SQL Server handles division by zero, arithmetic overflow and character/binary data truncation behavior.  Before changing  your &lt;b&gt;SET ANSI_WARNINGS&lt;/b&gt; option, be sure to reference the following MSDN article:   &lt;a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/tsqlref/ts_set-set_6d2r.asp"&gt;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/tsqlref/ts_set-set_6d2r.asp&lt;/a&gt;&lt;br /&gt;
&lt;table border="1"&gt;&lt;tbody&gt;
&lt;tr&gt;     &lt;td bgcolor="#00ffff" width="100%"&gt;&lt;b&gt;Gotcha:&lt;/b&gt;&amp;nbsp; &lt;b&gt;SET ANSI_WARNINGS       ON&lt;/b&gt; affects several aspects of SQL Server behavior.&amp;nbsp; Be sure to       check   &lt;a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/tsqlref/ts_set-set_6d2r.asp"&gt;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/tsqlref/ts_set-set_6d2r.asp&lt;/a&gt;       before changing this option.&lt;/td&gt;   &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h3 class="section"&gt;COUNT()&lt;/h3&gt;The &lt;b&gt;COUNT()&lt;/b&gt; set function has two forms:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;b&gt;COUNT(&lt;i&gt;column&lt;/i&gt;)&lt;/b&gt; – Returns the count of rows in a table, less NULLs&lt;/li&gt;
&lt;li&gt;&lt;b&gt;COUNT(*)&lt;/b&gt; – Returns the cardinality (total number of rows) of a table&lt;/li&gt;
&lt;/ul&gt;The following code demonstrates the difference between &lt;b&gt;COUNT(&lt;i&gt;column&lt;/i&gt;)&lt;/b&gt; and &lt;b&gt;COUNT(*)&lt;/b&gt;:&lt;br /&gt;
&lt;pre class="code"&gt;SELECT COUNT(*) AS TotalRows, COUNT(region) AS NonNULLRows, COUNT(*) - COUNT(region) AS NULLRows
FROM [Northwind].[dbo].[suppliers]&lt;/pre&gt;This query returns the following result:&lt;br /&gt;
&lt;table border="1"&gt;&lt;tbody&gt;
&lt;tr align="center"&gt;&lt;th&gt;TotalRows&lt;/th&gt;&lt;th&gt;NonNULLRows&lt;/th&gt;&lt;th&gt;NULLRows&lt;/th&gt;&lt;/tr&gt;
&lt;tr align="center"&gt;&lt;td&gt;29&lt;/td&gt;&lt;td&gt;9&lt;/td&gt;&lt;td&gt;20&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;ol&gt;&lt;li&gt;TotalRows is &lt;b&gt;COUNT(*)&lt;/b&gt;, the cardinality (total number of rows) of the suppliers table&lt;/li&gt;
&lt;li&gt;NonNULLRows is &lt;b&gt;COUNT(region)&lt;/b&gt;, the total number of rows of the suppliers table in which region is not NULL&lt;/li&gt;
&lt;li&gt;NULLRows is &lt;b&gt;COUNT(*) – COUNT(region)&lt;/b&gt;, the difference between the cardinality of the suppliers table and the number  of rows in which region is not NULL; basically this returns the number of rows   in which region *is* NULL.&lt;/li&gt;
&lt;/ol&gt;The fact that &lt;b&gt;COUNT(*)&lt;/b&gt; and &lt;b&gt;COUNT(&lt;i&gt;column&lt;/i&gt;)&lt;/b&gt; treat NULLs differently can be used to your advantage in  many situations such as calculating the NULLRows column above.&lt;br /&gt;
Notice that we can get the NULLRows count from above with the following query as well:&lt;br /&gt;
&lt;pre class="code"&gt;SELECT COUNT(*) AS NULLRows
FROM [Northwind].[dbo].[suppliers]
WHERE region IS NULL&lt;/pre&gt;This will not work if you specify the region column in your count, however. Here we'll take a look at why it is important to understand the order in which SQL performs the steps in a &lt;b&gt;SELECT&lt;/b&gt; statement that contains an aggregate function. This query returns zero as the NULLRows count:&lt;br /&gt;
&lt;pre class="code"&gt;SELECT COUNT(region) AS NULLRows
FROM [Northwind].[dbo].[suppliers]
WHERE region IS NULL&lt;/pre&gt;Why does this query return zero?  Remember, by definition &lt;b&gt;COUNT(&lt;i&gt;column&lt;/i&gt;)&lt;/b&gt; applies the &lt;i&gt;value expression&lt;/i&gt;, the &lt;i&gt;search expression &lt;/i&gt; and  then eliminates NULLs before giving you a final count!  Here's a play-by-play of what happens in our example:&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;The &lt;b&gt; WHERE &lt;/b&gt; clause eliminates every row in which region is not NULL.&lt;/li&gt;
&lt;li&gt;The &lt;i&gt;value expression&lt;/i&gt; in this example is simply a column name - &lt;i&gt;region&lt;/i&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;b&gt;COUNT(region)&lt;/b&gt; function eliminates every row in which region is NULL.&lt;/li&gt;
&lt;li&gt;Since we've explicitly eliminated all non-NULL columns with the &lt;b&gt;WHERE&lt;/b&gt; clause and implicitly eliminated all NULL  columns with the &lt;b&gt;COUNT(region)&lt;/b&gt; statement, we end up with a zero rows.&lt;/li&gt;
&lt;/ol&gt;&lt;table border="1"&gt;&lt;tbody&gt;
&lt;tr&gt;     &lt;td bgcolor="#00ffff" width="100%"&gt;&lt;b&gt;Gotcha:&lt;/b&gt;&amp;nbsp; &lt;b&gt;COUNT(&lt;i&gt;column&lt;/i&gt;)&lt;/b&gt;       eliminates NULL values; &lt;b&gt;COUNT(*)&lt;/b&gt; returns the cardinality (total       number of rows) for an entire table.&amp;nbsp; The order in which SQL performs       the steps in aggregate calculations can have implications for your       results.&amp;nbsp;&lt;/td&gt;   &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h3 class="section"&gt;SUM()&lt;/h3&gt;&lt;b&gt;SUM()&lt;/b&gt;, like the other set functions, applies the &lt;i&gt;search expression&lt;/i&gt;, then the &lt;i&gt;value expression&lt;/i&gt;, eliminates NULLs and finally returns a sum of the  remaining rows.  It is important to note that &lt;b&gt;SUM()&lt;/b&gt; excludes NULLs, as NULL scalar arithmetic normally returns NULL.  For  instance, let's do a scalar addition of all the values in the reportsto column of the employees table:&lt;br /&gt;
&lt;pre class="code"&gt;SELECT 2 + NULL + 2 + 2 + 2 + 5 + 5 + 2 + 5&lt;/pre&gt;This &lt;b&gt;SELECT &lt;/b&gt;statement will return NULL, since NULL plus anything always returns NULL.  If we use the &lt;b&gt;SUM()&lt;/b&gt; function on  the table, however, we can rest assured that NULLs are eliminated from our result:&lt;br /&gt;
&lt;pre class="code"&gt;SELECT SUM(reportsto) AS RowSum
FROM [Northwind].[dbo].[employees]&lt;/pre&gt;The result of this query:&lt;br /&gt;
&lt;table border="1"&gt;&lt;tbody&gt;
&lt;tr align="center"&gt;&lt;th&gt;RowSum&lt;/th&gt;&lt;/tr&gt;
&lt;tr align="center"&gt;&lt;td&gt;25&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&amp;nbsp; &lt;table border="1"&gt;&lt;tbody&gt;
&lt;tr&gt;     &lt;td bgcolor="#00ffff" width="100%"&gt;&lt;b&gt;Gotcha:&lt;/b&gt;&amp;nbsp; NULLs are eliminated       by the &lt;b&gt;SUM()&lt;/b&gt; function; in scalar arithmetic, however, NULL added to       anything equals NULL.&lt;/td&gt;   &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h3 class="section"&gt;AVG() NULL-handling&lt;/h3&gt;The &lt;b&gt;AVG()&lt;/b&gt; function returns the average of the column, excluding NULLs.  There are a couple of "gotcha's"  concerning the &lt;b&gt;AVG()&lt;/b&gt; function.  The first "gotcha" is that NULLs are excluded, as with other set functions,  so your answer may not be what you expect.  The second "gotcha" is that, by definition, &lt;b&gt;AVG()&lt;/b&gt; returns a number  of the same scale as the column it is performed on.  Again, this could throw your answer off a bit.&lt;br /&gt;
We'll tackle the NULL issue first.  &lt;b&gt;AVG()&lt;/b&gt; specifically excludes NULLs when it calculates averages.  The following query divides the sum of the reportsto column by the count of non-NULL rows.  In our example, one row is excluded from the  calculation:&lt;br /&gt;
&lt;pre class="code"&gt;SELECT AVG(reportsto) AS ColAvg
FROM [Northwind].[dbo].[employees]&lt;/pre&gt;Our result is:&lt;br /&gt;
&lt;table border="1"&gt;&lt;tbody&gt;
&lt;tr align="center"&gt;&lt;th&gt;ColAvg&lt;/th&gt;&lt;/tr&gt;
&lt;tr align="center"&gt;&lt;td&gt;3&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;This is the same result as if we performed this query:&lt;br /&gt;
&lt;pre class="code"&gt;SELECT SUM(reportsto)/COUNT(reportsto)
FROM [Northwind].[dbo].[employees]&lt;/pre&gt;So how does excluding NULLs affect our average?  We can use &lt;b&gt;COALESCE()&lt;/b&gt; function to see. &lt;br /&gt;
&lt;pre class="code"&gt;SELECT AVG(COALESCE(reportsto, 0)) AS ColAvg
FROM [Northwind].[dbo].[employees]&lt;/pre&gt;The &lt;b&gt;COALESCE(reportsto, 0)&lt;/b&gt; statement forces &lt;b&gt;AVG()&lt;/b&gt; to count NULL rows as zeroes; forcing every single row to be  counted.  This gives us the following result:&lt;br /&gt;
&lt;table border="1"&gt;&lt;tbody&gt;
&lt;tr align="center"&gt;&lt;th&gt;ColAvg&lt;/th&gt;&lt;/tr&gt;
&lt;tr align="center"&gt;&lt;td&gt;2&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;We can get the same result using the &lt;b&gt;SUM()&lt;/b&gt; and &lt;b&gt;COUNT()&lt;/b&gt; set functions:&lt;br /&gt;
&lt;pre class="code"&gt;SELECT SUM(reportsto)/COUNT(*) AS ColAvg
FROM [Northwind].[dbo].[employees]&lt;/pre&gt;&lt;table border="1"&gt;&lt;tbody&gt;
&lt;tr align="center"&gt;&lt;th&gt;ColAvg&lt;/th&gt;&lt;/tr&gt;
&lt;tr align="center"&gt;&lt;td&gt;2&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;Here we are performing a &lt;b&gt;SUM()&lt;/b&gt; on the rows that are not NULL, then dividing by the total number of rows, effectively  treating the NULL rows as zeroes in our average.  In this instance we're dealing with smaller numbers and only nine rows.  With  larger numbers and more rows the difference is more pronounced.&lt;br /&gt;
So which method should you use?  That depends on the result you're looking for.  Consider an example of calculating the  average payroll for a department.  Suppose you have the following table:&lt;br /&gt;
&lt;table border="1"&gt;&lt;tbody&gt;
&lt;tr align="center"&gt;&lt;th&gt;Name&lt;/th&gt;&lt;th&gt;Weekly Pay&lt;/th&gt;&lt;/tr&gt;
&lt;tr align="center"&gt;&lt;td&gt;Joe M.&lt;/td&gt;&lt;td&gt;400.00&lt;/td&gt;&lt;/tr&gt;
&lt;tr align="center"&gt;&lt;td&gt;Lisa D.&lt;/td&gt;&lt;td&gt;500.00&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;Our average is $450.00 per week.  Now let's say that we hire a new guy, but his weekly pay has not been put into the system  yet.  Now we have:&lt;br /&gt;
&lt;table border="1"&gt;&lt;tbody&gt;
&lt;tr align="center"&gt;&lt;th&gt;Name&lt;/th&gt;&lt;th&gt;Weekly Pay&lt;/th&gt;&lt;/tr&gt;
&lt;tr align="center"&gt;&lt;td&gt;Joe M.&lt;/td&gt;&lt;td&gt;400.00&lt;/td&gt;&lt;/tr&gt;
&lt;tr align="center"&gt;&lt;td&gt;Lisa D.&lt;/td&gt;&lt;td&gt;500.00&lt;/td&gt;&lt;/tr&gt;
&lt;tr align="center"&gt;&lt;td&gt;Tom J.&lt;/td&gt;&lt;td&gt;NULL&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;If we include Tom's missing salary in our average (treat it like 0.00), our average goes down to $300.00 per week.  If we  leave Tom out of it for now – the default for &lt;b&gt;AVG()&lt;/b&gt; – we get $450.00 per week again.&lt;br /&gt;
So which method is correct? Ignore NULLs or treat them as zeroes in your averages? That depends entirely on your objectives and corporate policies. The main thing is to be consistent in your calculations. If you ignore NULLs in calculating averages, use that same methodology every time; otherwise your results will not be comparable from year to year, quarter to quarter or even day to day.&lt;br /&gt;
&lt;table border="1"&gt;&lt;tbody&gt;
&lt;tr&gt;     &lt;td bgcolor="#00ffff" width="100%"&gt;&lt;b&gt;Gotcha:&amp;nbsp; AVG()&lt;/b&gt;       eliminates NULLs when calculating your average.&amp;nbsp;&lt;/td&gt;   &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h3 class="section"&gt;AVG() Scale&lt;/h3&gt;The second issue is scale.  In our examples, the averages we received were calculated and returned with the same scale  (digits after the decimal point) as the column; in this case the column is an INT data type, so no decimals were returned.   We'll apply the &lt;b&gt; CAST&lt;/b&gt; operator to see what would have been returned if we were using a floating-point data type:&lt;br /&gt;
&lt;pre class="code"&gt;SELECT CAST(AVG(reportsto) AS FLOAT) AS ColAvg
FROM [Northwind].[dbo].[employees]&lt;/pre&gt;&lt;table border="1"&gt;&lt;tbody&gt;
&lt;tr align="center"&gt;&lt;th&gt;ColAvg&lt;/th&gt;&lt;/tr&gt;
&lt;tr align="center"&gt;&lt;td&gt;3.0&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;So far, not much has changed.  We received 3 previously and 3.0 this time.  So let's look at the equivalent average using  &lt;b&gt;SUM()&lt;/b&gt; and &lt;b&gt;COUNT()&lt;/b&gt;:&lt;br /&gt;
&lt;pre class="code"&gt;SELECT CAST(SUM(reportsto) AS FLOAT)/COUNT(reportsto) AS ColAvg
FROM [Northwind].[dbo].[employees]&lt;/pre&gt;&lt;table border="1"&gt;&lt;tbody&gt;
&lt;tr align="center"&gt;&lt;th&gt;ColAvg&lt;/th&gt;&lt;/tr&gt;
&lt;tr align="center"&gt;&lt;td&gt;3.125&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;Notice that we are forcing floating point precision now by casting the reportsto column as a FLOAT.&amp;nbsp; We can get this same result by moving the &lt;b&gt;CAST&lt;/b&gt; inside the &lt;b&gt;AVG()&lt;/b&gt; function, like this:&lt;br /&gt;
&lt;pre class="code"&gt;SELECT (AVG(CAST(reportsto AS FLOAT))) AS ColAvg
FROM [Northwind].[dbo].[employees]&lt;/pre&gt;So how did we end up with a different value the second time?  Since &lt;b&gt;AVG()&lt;/b&gt; calculates using the same scale as the  column data type, our INT column average is calculated with no decimal places.  In the first example, we &lt;b&gt;CAST&lt;/b&gt; the result  of the &lt;b&gt;AVG()&lt;/b&gt; function to a FLOAT type, but the &lt;b&gt;AVG()&lt;/b&gt; was calculated as an INT first.&lt;br /&gt;
In the second example, we &lt;b&gt;CAST&lt;/b&gt; the &lt;b&gt; SUM()&lt;/b&gt; of the reportsto column to a floating point type, and then divided; so we  didn't lose any digits after the decimal point in the calculation.  As shown, we can get the same result by using &lt;b&gt;CAST &lt;/b&gt;on the column inside the  &lt;b&gt;AVG()&lt;/b&gt; function.&amp;nbsp; This is particularly important to note in financial and scientific calculations, where the scale is particularly important to our calculations.&lt;br /&gt;
If your scale requirements are greater than the scale of your column, you might need to use the  &lt;b&gt;SUM(&lt;i&gt;column&lt;/i&gt;)/COUNT(&lt;i&gt;column&lt;/i&gt;)&lt;/b&gt; method of calculating your average and &lt;b&gt;CAST &lt;/b&gt;it to a floating or fixed-point type,  or &lt;b&gt;CAST &lt;/b&gt;inside of the &lt;b&gt;AVG()&lt;/b&gt; function.&lt;br /&gt;
&lt;table border="1"&gt;&lt;tbody&gt;
&lt;tr&gt;     &lt;td bgcolor="#00ffff" width="100%"&gt;&lt;b&gt;Gotcha:&lt;/b&gt;&amp;nbsp; &lt;b&gt;AVG() &lt;/b&gt;also       uses the precision (total number of digits) and scale (number of digits       after the decimal point) of the column you are performing the &lt;b&gt;AVG()&lt;/b&gt;       on.&lt;/td&gt;   &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h3 class="section"&gt;MIN(), MAX()&lt;/h3&gt;The &lt;b&gt;MIN() &lt;/b&gt;and &lt;b&gt;MAX()&lt;/b&gt; functions return the minimum and maximum values of a column, respectively.  Like the other  set functions, these also eliminate NULLs before returning a result.&lt;br /&gt;
For numeric data types, &lt;b&gt;MIN()&lt;/b&gt; and &lt;b&gt;MAX()&lt;/b&gt; simply return the minimum or maximum number in the column.  For  character data types, the values are compared using standard SQL form:  the shorter of the values is right-padded with  'pad characters' (usually spaces).  This means that character columns with trailing spaces are mixed in with character columns  without trailing spaces.  As an example:&lt;br /&gt;
&lt;pre class="code"&gt;IF 'hi' = 'hi '
  PRINT 'Equal'
ELSE
  PRINT 'Not Equal'&lt;/pre&gt;Depending on your collation settings, string comparisons might be case-sensitive or case-insensitive.&lt;br /&gt;
&lt;table border="1"&gt;&lt;tbody&gt;
&lt;tr&gt;     &lt;td bgcolor="#00ffff" width="100%"&gt;&lt;b&gt;Gotcha:&lt;/b&gt;&amp;nbsp; NULLs are eliminated       by &lt;b&gt;MIN() &lt;/b&gt;and &lt;b&gt;MAX()&lt;/b&gt; functions.&amp;nbsp; For character data       types, shorter values are right-padded with 'pad characters' (often       spaces) before comparing them.&lt;/td&gt;   &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h3 class="section"&gt;Conclusion&lt;/h3&gt;SQL set functions are very useful in calculating basic statistical values.  By knowing how SQL set functions handle NULL  values you can ensure that you always get accurate, high-quality results.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-1525579414847552938?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/mgC7mcL5O9g" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/1525579414847552938/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2009/12/sql-aggregate-functions-and-null.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/1525579414847552938?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/1525579414847552938?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/mgC7mcL5O9g/sql-aggregate-functions-and-null.html" title="SQL Aggregate Functions and NULL" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2009/12/sql-aggregate-functions-and-null.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUYDSHYzcSp7ImA9WxBTFU4.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-8885347178615347999</id><published>2009-12-11T05:12:00.000-08:00</published><updated>2009-12-11T05:12:59.889-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-11T05:12:59.889-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Read Only Tables in Sql" /><category scheme="http://www.blogger.com/atom/ns#" term="DENY permissions" /><category scheme="http://www.blogger.com/atom/ns#" term="GRANT permissions" /><category scheme="http://www.blogger.com/atom/ns#" term="Read Only Tables in T-SQL" /><category scheme="http://www.blogger.com/atom/ns#" term="Read Only Tables" /><title>Read Only Tables</title><content type="html">Most SQL Server DBAs probably know that you can have a read only database for those situations where you don't want anything changed, like an archived set of data. &lt;br /&gt;
I had someone query the other day about setting a table to read only. They'd known you could do an entire database, but what about a table?&amp;nbsp; &lt;br /&gt;
&lt;h3 class="section"&gt;The Easy Way&lt;/h3&gt;Most people that want to set something to read only usually want to prevent most people from changing things, but they want to be able to make a change themselves. Usually this is someone that is administering the file and they mark the file as read-only. Users can access the file, but not change it. If the administrator wants to make a chance, they unmark the read-only flag, make their change, and save it, setting the read-only flag again. &lt;br /&gt;
While tables in SQL Server don't have any specific "read-only" setting, there is a simple way to ensure they are read only. It's similar to the ACL permissions that exist in the file system. You just grant them SELECT permissions on the table with no other permissions. &lt;br /&gt;
This means that as you setup your roles and make use of the fixed database roles that are built into SQL Server, you need to be careful of permissions granted to those roles. In general, you will not want to grant INSERT/UPDATE/DELETE permissions to any role that does not need to change the table. This means the builtin db_datawriter should not be assigned. &lt;br /&gt;
If you find yourself in a situation where you want a group of people to have read-only access, but they are members of other roles that have access, you can easily solve this issue as well. Create a new role for the read-only group and DENY permissions for INSERT, UPDATE, and DELETE on the table. This will override the permissions granted from other roles. &lt;br /&gt;
&lt;i&gt;&lt;b&gt;Note:&lt;/b&gt; One caveat here. Permissions granted on a column basis are not overridden by DENY. This is a bug that may be corrected by the time you read this, so check this before you implement a role with DENY permissions. &lt;/i&gt; &lt;br /&gt;
The nice thing about this approach is that some users see the table as read-only and others can make changes. However since the user does not have permissions errors are reported to the calling application. If the application doesn't handle these errors well, there could be issues with this method. &lt;br /&gt;
&lt;h3 class="section"&gt;The Hard Way&lt;/h3&gt;There is a second way to make a table read-only, which was the first thing to come to mind for me. Since I've been doing DDL trigger work with SQL Server 2005, this was the first thing I thought of when I was asked the question. &lt;br /&gt;
You can setup a trigger on a table that fires for the INSERT, UPDATE, and DELETE events and rolls back any changes. This is simple to do and for a table called Customers, you would compile this: &lt;br /&gt;
&lt;pre class="code"&gt;create trigger trCustomers
 on Customers
 for insert, update, delete
as
rollback transaction
go
&lt;/pre&gt;This trigger will allow a user to make a change, immediately undo the change by rolling back the implicit transaction, and report success to the user. If you've ever been the victim of this, it can be really annoying, but it does prevent all changes to the table. The downside of this approach is that no one can make changes to the table without the trigger being disabled or removed, depending on your version of SQL Server. &lt;br /&gt;
There is one other downside of this method. If you have users that are unaware of its implementation, then your phone may ring more often than you'd like because their data changes are not being recorded. &lt;br /&gt;
&lt;h3 class="section"&gt;Conclusion&lt;/h3&gt;Setting a table to read only isn't a complicated process, but the two methods each have their advantages and disadvantages. Personally I think the first method is the best way to handle things, but if you do not want errors returned to users, then the second way may work better in your environment. &lt;br /&gt;
One last variation on the read-only table is the logging table. Often once something is logged, for troubleshooting, tracking, or regulatory purposes, you do not want it changed, or possibly even read. I have seen systems that granted only INSERT permissions or maybe INSERT and SELECT permissions on a table to ensure that users could only add data to it. In this case, you might want to also add a trigger to prevent updates or deletes and ensure that your data is protected.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-8885347178615347999?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/UxQISI7QBdg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/8885347178615347999/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2009/12/read-only-tables.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/8885347178615347999?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/8885347178615347999?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/UxQISI7QBdg/read-only-tables.html" title="Read Only Tables" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2009/12/read-only-tables.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEEARn86fip7ImA9WxBTFU4.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-4271015921943901936</id><published>2009-12-11T05:04:00.000-08:00</published><updated>2009-12-11T05:04:07.116-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-11T05:04:07.116-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Not Equal To" /><category scheme="http://www.blogger.com/atom/ns#" term="The Pitfall of &quot;Not Equal To&quot;" /><title>The Pitfall of "Not Equal To"</title><content type="html">&lt;h3&gt;INTRODUCTION&lt;/h3&gt;The "Not Equal To" (&amp;lt;&amp;gt;) operator is useful when you need to filter your data and exclude some rows from the result of query, but there are cases that when you really mean MyColumn&amp;lt;&amp;gt;'SomeValue', you should not use this operator in order to get the correct result! &lt;br /&gt;
Here is an example: Suppose that a Testing Center registers students for IT exams. Obviously each student can take more than one exam. Let's create the required tables and populate them with some rows: &lt;br /&gt;
&lt;pre class="code"&gt;CREATE TABLE Students (
StID INT PRIMAEY KEY,
StName NVARCHAR(50))
GO

INSERT Students VALUES (1,'Jack')
INSERT Students VALUES (2,'Anna')
INSERT Students VALUES (3,'Bob')
GO

CREATE TABLE StudentExam (
StID INT,
ExamName VARCHAR(50))

INSERT StudentExam VALUES (1,'SQL Server')
INSERT StudentExam VALUES (2,'VB.NET')
INSERT StudentExam VALUES (2,'C#.NET')
INSERT StudentExam VALUES (1,'XML')
&lt;/pre&gt;In a perfect design, we would need another table called Exams which stores the full specifications of each exam, but I have denormalized it with StudentExam table for the sake of simplification. Now we are asked to prepare a report that lists information of students who have taken SQL Server exam: &lt;br /&gt;
&lt;pre class="Code"&gt;SELECT s.* FROM Students s
 JOIN StudentExam se
 ON s.StID=se.StID
  WHERE se.ExamName='SQL Server'

StID        StName
----------- --------------------------------------------------
1           Jack

(1 row(s) affected)
&lt;/pre&gt;Well, everything is OK so far, but the issue turns up when we are asked to query the students who have NOT taken SQL Server exam. The first thing that might come to mind is replacing the "Equal" operator in WHERE clause with "Not Equal To" operator: &lt;br /&gt;
&lt;pre class="Code"&gt;SELECT s.* FROM Students s
 JOIN StudentExam se
 ON s.StID=se.StID
  WHERE se.ExamName&amp;lt;&amp;gt;'SQL Server'

StID        StName
----------- --------------------------------------------------
1           Jack
2           Anna
2           Anna

(3 row(s) affected)
&lt;/pre&gt;Note that although Bob did not appear in the result because he has not taken any exam to satisfy an INNER JOIN, again Jack is there! &lt;br /&gt;
The subtle reason of this is that Jack still satisfies the WHERE clause because of his second exam, XML! This exam causes the WHERE clause return "True" when he is being checked by the condition. Basically you must take care of this problem when dealing with one-to-many joins. If the relationship between Students and StudentExam was one-to-one (each student could take only one exam), the previous query would work fine. &lt;br /&gt;
To produce the correct report, you can simply use a subquery to build the list of students who have taken SQL Server exam: &lt;br /&gt;
&lt;pre class="Code"&gt;SELECT StID FROM StudentExam WHERE ExamName='SQL Server'
&lt;/pre&gt;Then any student that does not appear in this list, must be returned for our report: &lt;br /&gt;
&lt;pre class="Code"&gt;SELECT * FROM Students WHERE StID NOT IN
(SELECT StID FROM StudentExam WHERE ExamName='SQL Server')
&lt;/pre&gt;&lt;img src="http://www.sqlservercentral.com/articles/Advanced+Querying/2561/Article01_Pic01.GIF" /&gt;&lt;br /&gt;
As you see in the graphical execution plan, an OUTER JOIN is performed by the Query Optimizer. Alternatively you may write this query and use an OUTER JOIN yourself: &lt;br /&gt;
&lt;pre class="Code"&gt;SELECT s.* FROM Students s LEFT JOIN
 (SELECT StID FROM StudentExam WHERE ExamName='SQL Server') se
 ON s.StID=se.StID
  WHERE se.StID IS NULL
&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-4271015921943901936?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/PObtOi7PDcw" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/4271015921943901936/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2009/12/pitfall-of-not-equal-to.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/4271015921943901936?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/4271015921943901936?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/PObtOi7PDcw/pitfall-of-not-equal-to.html" title="The Pitfall of &quot;Not Equal To&quot;" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2009/12/pitfall-of-not-equal-to.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0AFQ3Y4cCp7ImA9WxBTFUw.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-3395542831795202377</id><published>2009-12-10T22:08:00.000-08:00</published><updated>2009-12-10T22:08:32.838-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-10T22:08:32.838-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="OUTPUT Clause" /><category scheme="http://www.blogger.com/atom/ns#" term="Multi-Row Inserts" /><category scheme="http://www.blogger.com/atom/ns#" term="Identity Values" /><title>Using the OUTPUT Clause to Capture Identity Values on Multi-Row Inserts</title><content type="html">SQL Server 2005 introducted the OUTPUT clause which we can use to capture values from the inserted and deleted virtual tables. Previously this data was only available through triggers. We can use this in an INSERT ... SELECT statement to capture all the inserted identity values. Previously this required some type of loop or temporarily altering the target table. &lt;br /&gt;
&lt;!-- &lt;hr  /&gt;&lt;br /&gt;
  --&gt; &lt;div id="article_body"&gt;  We'll start with two tables: a product table and table of products to insert. The scenario is a vendor that sends you a complete list of all their products and you only need to insert the rows that don't already exist. However you need to insert those new rows into multiple tables. The following script will create the tables in tempdb based on data in AdventureWorks.&lt;br /&gt;
&lt;pre&gt;USE tempdb
GO
IF  EXISTS (SELECT * FROM sys.objects 
  WHERE object_id = OBJECT_ID(N'[dbo].[Product]') AND type in (N'U'))
 DROP TABLE [dbo].[Product]
GO
IF  EXISTS (SELECT * FROM sys.objects 
  WHERE object_id = OBJECT_ID(N'[dbo].ProductsToInsert') AND type in (N'U'))
 DROP TABLE [dbo].ProductsToInsert
GO

CREATE TABLE Product (
 ProductID INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
 [Name] NVARCHAR(50) NOT NULL,
 ProductNumber NVARCHAR(25) NOT NULL,
 ListPrice MONEY NOT NULL)
GO
CREATE UNIQUE INDEX IX_Product_ProductNumber ON Product ( ProductNumber )
GO

CREATE TABLE ProductsToInsert (
 RowID INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
 [Name] NVARCHAR(50) NOT NULL,
 ProductNumber NVARCHAR(25) NOT NULL,
 ListPrice MONEY NOT NULL,
 InsertedIdentityValue INT NULL)
GO
INSERT Product ([Name], ProductNumber, ListPrice)
SELECT TOP 450 [Name], ProductNumber, ListPrice
FROM AdventureWorks.Production.Product
ORDER BY SellStartDate, ProductID
GO
INSERT ProductsToInsert ([Name], ProductNumber, ListPrice)
SELECT  [Name], ProductNumber, ListPrice
FROM AdventureWorks.Production.Product
GO&lt;/pre&gt;The Product table has an identity column as its primary key. Product number is a natural key on the table. The ProductsToInsert table has the ProductNumber column and a column for whatever identity value is inserted when we put the row into the Product table. You'll notice I only put 450 of the products in the Product table to start with but all 504 in the ProductsToInsert table.&lt;br /&gt;
A simple script to insert the new products looks like this:&lt;br /&gt;
&lt;pre&gt;use tempdb
GO
INSERT Product ([Name], ProductNumber, ListPrice)
SELECT 
    [Name], ProductNumber, ListPrice
FROM
    ProductsToInsert I
WHERE
    NOT EXISTS (SELECT 1 
                FROM Product 
                WHERE ProductNumber = I.ProductNumber)&lt;/pre&gt;That inserts the 54 products that weren't in the table previously.  We can         use the OUTPUT clause to return back the rows that were inserted.  That looks like this:&lt;br /&gt;
&lt;pre&gt;INSERT Product ([Name], ProductNumber, ListPrice)
    OUTPUT inserted.ProductID,
           inserted.[Name],
           inserted.ProductNumber,
           inserted.ListPrice
SELECT 
 [Name], ProductNumber, ListPrice
FROM
 ProductsToInsert I
WHERE
 NOT EXISTS (SELECT 1 FROM Product 
    WHERE ProductNumber = I.ProductNumber)&lt;/pre&gt;When that statement is run it returns the following recordset back to the client:&lt;br /&gt;
&lt;pre&gt;ProductID Name                                ProductNumber         ListPrice
----------- ----------------------------------- --------------------- -------------
        451 LL Bottom Bracket                   BB-7421                       53.99
        452 ML Bottom Bracket                   BB-8107                      101.24
        453 HL Bottom Bracket                   BB-9108                      121.49

. . . 

        504 HL Touring Handlebars               HB-T928                       91.57

(54 row(s) affected)&lt;/pre&gt;That is almost what we want.  We have the identity values in the result set         but we don't have a way to work with the data and we don't have a way to tie it         back to the original source row.  We'll need to add two things.  First         we'll need to store this result set in a table variable.  I'll also remove         a few columns to make it easier to read and add an update statement to save the         identity value.  That script looks like this:&lt;br /&gt;
&lt;pre&gt;DECLARE @InsertedRows TABLE (ProductID INT, ProductNumber NVARCHAR(25) )

INSERT Product ([Name], ProductNumber, ListPrice)
    OUTPUT inserted.ProductID,
           inserted.ProductNumber
    INTO @InsertedRows
SELECT 
 [Name], ProductNumber, ListPrice
FROM
 ProductsToInsert AS I
WHERE
 NOT EXISTS (SELECT 1 FROM Product 
    WHERE ProductNumber = I.ProductNumber)

UPDATE  ProductsToInsert
SET     InsertedIdentityValue = T.ProductID
FROM    ProductsToInsert I
JOIN    @InsertedRows T ON T.ProductNumber = I.ProductNumber

SELECT  RowID, ProductNumber, InsertedIdentityValue
FROM    ProductsToInsert
WHERE   InsertedIdentityValue IS NOT NULL&lt;/pre&gt;We declare a table variable to store the results of the OUTPUT clause.   We         use the OUTPUT &lt;column_list _moz-userdefined=""&gt; INTO &lt;table_variable _moz-userdefined=""&gt; syntax to store the         results into the table variable.  Next we use the table variable to update         the source table with the inserted identity columns.  After that a simple SELECT         statement returns the new values which we can easily use in other statements:&lt;/table_variable&gt;&lt;/column_list&gt;&lt;br /&gt;
&lt;pre&gt;RowID ProductNumber             InsertedIdentityValue
----------- ------------------------- ---------------------
        451 HB-T721                                     503
        452 HB-T928                                     504
        453 FB-9873                                     502

. . . 

        504 BK-R19B-52                                  470

(54 row(s) affected)&lt;/pre&gt;The OUTPUT clause can also be used with UPDATE and DELETE statements and return         values from either the inserted or deleted table.  In its simplest form the         OUTPUT clause greatly simplifies importing data into SQL Server.&lt;br /&gt;
&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-3395542831795202377?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/S2Jt6xdy3Cw" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/3395542831795202377/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2009/12/using-output-clause-to-capture-identity.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/3395542831795202377?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/3395542831795202377?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/S2Jt6xdy3Cw/using-output-clause-to-capture-identity.html" title="Using the OUTPUT Clause to Capture Identity Values on Multi-Row Inserts" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2009/12/using-output-clause-to-capture-identity.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0UNQ3g5fyp7ImA9WxBTFE8.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-6480782857047101306</id><published>2009-12-09T22:08:00.000-08:00</published><updated>2009-12-09T22:08:12.627-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-09T22:08:12.627-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="TOP Clause" /><category scheme="http://www.blogger.com/atom/ns#" term="“WITH TIES” options" /><category scheme="http://www.blogger.com/atom/ns#" term="How to get the BOTTOM records" /><title>Creative Ways to Use the TOP Clause</title><content type="html">&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Have you ever wanted to return the last 100 rows of a given set of records?  What about the second set of 100 records?  Or possibly, you have wanted to return the first so many billing records for each billing month. This article will discuss how to use the TOP clause to help solve those requests where you want to restrict the number of records returned based on a record count.   But first I will  discuss the new features incorporated into the TOP clause with SQL Server 2005.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;strong&gt;New Features in TOP Clause Implemented with SQL Server 2005&lt;/strong&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;SQL Server 2005 brought a couple of new features to the TOP clause.  The syntax of the TOP clause in SQL Server 2005 now looks like this:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;TOP &lt;strong&gt;(&lt;/strong&gt;&lt;em&gt;expression&lt;/em&gt;&lt;strong&gt;)&lt;/strong&gt; [PERCENT]
     [ WITH TIES ]&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;In SQL Server 2005 you are allowed to specify the number of rows you want to return with an expression, where as in prior versions you where only allowed to hard code a number.  Being able to specify a variable allows you to more easily control the number of rows returned programmatically.  &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;The second new feature is the “WITH TIES” options.  This option instructs SQL Server to bring back additional rows should the last row have the same order by value as the next row.  This option is only valid in a SELECT statement that contains an ORDER BY clause.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;To demonstrate how to use these new TOP clause features let me go through a couple of examples.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Sample Data for Examples&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Prior to going through my examples, I will need a table with some test data.  Below is my sample data and a script to populate my sample TopDemo table:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;table border="1" cellpadding="3" cellspacing="0" style="border-collapse: collapse; margin-left: 4.65pt; width: 258px;"&gt;&lt;tbody&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   &lt;strong&gt;PaymentID&lt;/strong&gt;&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   &lt;strong&gt;Amount&lt;/strong&gt;&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   &lt;strong&gt;PayDate&lt;/strong&gt;&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   1&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   12.23&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   1/2/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   2&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   73.18&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   1/10/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   3&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   92.20&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   1/21/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   4&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   62.20&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   1/21/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   5&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   12.45&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   1/23/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   6&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   10.99&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   2/4/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   7&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   34.23&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   2/7/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   8&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   34.87&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   2/12/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   9&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   58.32&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   2/15/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   10&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   34.23&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   2/23/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   11&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   98.26&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   3/1/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   12&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   45.32&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   3/15/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   13&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   47.30&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   3/16/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   14&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   35.21&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   3/24/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   15&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   57.23&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   3/26/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   16&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   24.56&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   3/30/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   17&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   34.23&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   4/4/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   18&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   23.99&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   4/6/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   19&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   56.32&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   4/8/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   20&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   47.53&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   6/25/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   21&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   30.56&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   7/7/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   22&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   65.34&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   7/8/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   23&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   44.55&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   8/4/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   24&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   84.23&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   8/8/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   25&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   23.56&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   8/22/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   26&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   45.77&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   9/12/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;tr style="height: 12.75pt;"&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 72.85pt;" valign="bottom" width="97"&gt;   27&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 56.15pt;" valign="bottom" width="75"&gt;   65.90&lt;br /&gt;
&lt;/td&gt;   &lt;td nowrap="nowrap" style="height: 12.75pt; padding: 0in 5.4pt; width: 64.2pt;" valign="bottom" width="86"&gt;   9/25/2006&lt;br /&gt;
&lt;/td&gt;  &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;set nocount on
create table TopDemo (
PaymentID int identity, 
Amount money, 
PayDate DateTime
)
insert into TopDemo(Amount, PayDate) values (12.23,'1/2/2006')
insert into TopDemo(Amount, PayDate) values (73.18,'1/10/2006')
insert into TopDemo(Amount, PayDate) values (92.20,'1/21/2006')
insert into TopDemo(Amount, PayDate) values (62.20,'1/21/2006')
insert into TopDemo(Amount, PayDate) values (12.45,'1/23/2006')
insert into TopDemo(Amount, PayDate) values (10.99,'2/4/2006')
insert into TopDemo(Amount, PayDate) values (34.23,'2/7/2006')
insert into TopDemo(Amount, PayDate) values (34.87,'2/12/2006')
insert into TopDemo(Amount, PayDate) values (58.32,'2/15/2006')
insert into TopDemo(Amount, PayDate) values (34.23,'2/23/2006')
insert into TopDemo(Amount, PayDate) values (98.26,'3/1/2006')
insert into TopDemo(Amount, PayDate) values (45.32,'3/15/2006')
insert into TopDemo(Amount, PayDate) values (47.30,'3/16/2006')
insert into TopDemo(Amount, PayDate) values (35.21,'3/24/2006')
insert into TopDemo(Amount, PayDate) values (57.23,'3/26/2006')
insert into TopDemo(Amount, PayDate) values (24.56,'3/30/2006')
insert into TopDemo(Amount, PayDate) values (34.23,'4/4/2006')
insert into TopDemo(Amount, PayDate) values (23.99,'4/6/2006')
insert into TopDemo(Amount, PayDate) values (56.32,'4/8/2006')
insert into TopDemo(Amount, PayDate) values (47.53,'6/25/2006')
insert into TopDemo(Amount, PayDate) values (30.56,'7/7/2006')
insert into TopDemo(Amount, PayDate) values (65.34,'7/8/2006')
insert into TopDemo(Amount, PayDate) values (44.55,'8/4/2006')
insert into TopDemo(Amount, PayDate) values (84.23,'8/8/2006')
insert into TopDemo(Amount, PayDate) values (23.56,'8/22/2006')
insert into TopDemo(Amount, PayDate) values (45.77,'9/12/2006')
insert into TopDemo(Amount, PayDate) values (65.90,'9/25/2006')
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Using an EXPRESSION in the TOP Clause&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;SQL Server 2005 makes it easier to programmatically control the number of records you want to return when you use the TOP clause.   With these changes to the TOP clause, you can now use a variable to identify the number of rows to return.   Below is an example that returns the top 3 records from my TopDemo table:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;declare @top int
set @top = 3
select top (@top) * from TopDemo
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Here I defined an integer variable @top and set its value to 3.  I then used that variable in the SELECT statement immediately following the TOP clause and enclosed it in parentheses.  When the SQL Server 2005 engine executes this command, it evaluates the value of the variable @top to determine the number of rows to return.  In my example, I return the following 3 records:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;PaymentID   Amount                PayDate
----------- --------------------- -----------------------
1           12.23                 2006-01-02 00:00:00.000
2           73.18                 2006-01-10 00:00:00.000
3           92.20                 2006-01-21 00:00:00.000
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Now if you have some code that determines the number of records you want to return, it is a simple matter of just getting that record number into a variable, then placing the variable into a TOP clause to constrain your returned record set to a specific number of records.  Being able to dynamically control the number of rows returned in SQL Server 2005 means you no longer have to using dynamic SQL to accomplish this task.  &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Using the WITH TIES option&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;SQL Server 2005 also offers a WITH TIES option.  This option allows you to specify that you want to return all the records that have the same value as last records returned by the TOP clause, based on the ORDER BY clause.  Here is an example that demonstrates the WITH TIES option:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;select top 3 with ties * from TopDemo 
order by PayDate
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Here I am specifying that I want to return the top 3 records and any additional records that have the same PayDate as the third record.  When I run this query this is my result set:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;PaymentID   Amount                PayDate
----------- --------------------- -----------------------
1           12.23                 2006-01-02 00:00:00.000
2           73.18                 2006-01-10 00:00:00.000
3           92.20                 2006-01-21 00:00:00.000
4           62.20                 2006-01-21 00:00:00.000
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;As you can see, 4 records were returned.  This is because the third and forth record have the same PayDate.  So now, if you want to return all the records that have the same ORDER BY value, but still what to loosely constrain your returned set to a number of records, then the WITH TIES option should help.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Returning the Second Set of 4 Records&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Some times, you don’t want to return the top set of so many records, but you want to return the next set of so many records.  Here is an example that returns the second set of 4 records:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;select top 4 * from TopDemo a 
 where PaymentID in (select top 8 PaymentID from TopDemo) 
    order by PaymentID desc
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;In order to return the second set of 4 records I used a correlated subquery.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;The correlated subquery returns “PaymentID” column values for the top 8 records.  I then return only the first 4 matching TopDemo records that have one of the 8 PaymentID’s returned from the subquery.  Since I requested my final set to be to be sorted in descending order by the PaymentID I will get the second set of 4 records.  Here is the result set from the above query:&lt;/span&gt;&lt;/span&gt;  &lt;br /&gt;
&lt;pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;PaymentID   Amount                PayDate
----------- --------------------- -----------------------
8           34.87                 2006-02-12 00:00:00.000
7           34.23                 2006-02-07 00:00:00.000
6           10.99                 2006-02-04 00:00:00.000
5           12.45                 2006-01-23 00:00:00.000
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;As you can see, this set is ordered in descending sequence by PaymentID.  If you really wanted the payments to be sorted sequentially by PaymentID, starting with the lowest PaymentID first, then you would have to run the following T-SQL statement, which sorts the final results set in ascending order by PaymentID:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;select * from 
  (select top 4 * from TopDemo a 
   where PaymentID in (select top 8 PaymentID from TopDemo) 
   order by PaymentID desc) a
order by PaymentID
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Here I just placed an alias named “a”  around my original query’s result set, so I could re-sort my four records into PaymentID order. &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;How to get the BOTTOM 5 records&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;SQL Server does not supply a BOTTOM clause, but that doesn’t mean you can’t easily get the BOTTOM so many records.  It is relatively simple to use the TOP clause to retrieve the bottom “x” number of records.  Here is an example where I retrieved the bottom 5 records based on PayDate from my TopDemo table.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;select top 5 * from TopDemo 
order by PayDate desc
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;In order to return the bottom five records all I had to do was specify the descending clause on my order by statement.  This returned the bottom 5 records based on PayDate, although the final record set returned is ordered in descending order by PayDate.  Below is the result set returned when I run the above query:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;PaymentID   Amount                PayDate
----------- --------------------- -----------------------
27          65.90                 2006-09-25 00:00:00.000
26          45.77                 2006-09-12 00:00:00.000
25          23.56                 2006-08-22 00:00:00.000
24          84.23                 2006-08-08 00:00:00.000
23          44.55                 2006-08-04 00:00:00.000
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Now normally you probably would want to return the bottom so many records and have those records be ordered based on a key in ascending order, instead of having them sort like my above results set where the PayDate is in descending order.  This can be solved by wrapping another query around the above query, like so: &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;select * from (select top 5 * from TopDemo 
order by PayDate desc) a
order by PayDate
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;The above query will order my final result set in ascending PayDate order.  Below is the result set of the above query:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;PaymentID   Amount                PayDate
----------- --------------------- -----------------------
23          44.55                 2006-08-04 00:00:00.000
24          84.23                 2006-08-08 00:00:00.000
25          23.56                 2006-08-22 00:00:00.000
26          45.77                 2006-09-12 00:00:00.000
27          65.90                 2006-09-25 00:00:00.000
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;How to get the First Record of Each Month&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;For my last example, I will show you how you can use the TOP clause to return the first TopDemo payment record for each PayDate month.  To accomplish this I will once again use a correlated subquery to identify the top payment record for each month, as the code below demonstrates:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;select * from TopDemo a 
 where PayDate = (select top 1 PayDate from TopDemo 
    where month(PayDate) = Month(a.PayDate)
    order by PayDate) 
order by PayDate
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Here the inner query (“select top 1.....”) a correlated subquery takes the month identification from the outer query to select the first PayDate for the that specific month.  The first PayDate of the month is then used in the WHERE clause to return the first payment for each month.  Below is the result set from the above correlated subquery: &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;PaymentID   Amount                PayDate
----------- --------------------- -----------------------
1           12.23                 2006-01-02 00:00:00.000
6           10.99                 2006-02-04 00:00:00.000
11          98.26                 2006-03-01 00:00:00.000
17          34.23                 2006-04-04 00:00:00.000
20          47.53                 2006-06-25 00:00:00.000
21          30.56                 2006-07-07 00:00:00.000
23          44.55                 2006-08-04 00:00:00.000
26          45.77                 2006-09-12 00:00:00.000
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;In my T-SQL code above, the inner query returns 8 different PayDate’s, one for the first payment date of each month that existed in my TopDemo table.  These unique payment dates were then used in the outer query to return any TopDemo records that had one of those 8 payment dates.    &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Conclusion&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;The TOP clause is useful in helping you constrain your set to specific number of records. The new SQL Server 2005 features of the TOP clause now provides you with some alternatives for how to write your code for some specific data retrieve criteria.  Being able to dynamically control the number of records returned is a big help when you don’t know exactly how many records you want to return.  Now you can use the WITH TIES option as an easy method to return those records that have the same value as the last record in your TOP criteria.  &lt;/span&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-6480782857047101306?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/BMsoC8jCMDU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/6480782857047101306/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2009/12/creative-ways-to-use-top-clause.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/6480782857047101306?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/6480782857047101306?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/BMsoC8jCMDU/creative-ways-to-use-top-clause.html" title="Creative Ways to Use the TOP Clause" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2009/12/creative-ways-to-use-top-clause.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkMAR3c4fyp7ImA9WxBTE0s.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-1841267938651693615</id><published>2009-12-09T04:07:00.000-08:00</published><updated>2009-12-09T04:07:26.937-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-09T04:07:26.937-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="UPPER and LOWER Function" /><category scheme="http://www.blogger.com/atom/ns#" term="Lower Case" /><category scheme="http://www.blogger.com/atom/ns#" term="Upper Case" /><category scheme="http://www.blogger.com/atom/ns#" term="Upper and Lower Case" /><category scheme="http://www.blogger.com/atom/ns#" term="Comparing Columns with Different Case" /><category scheme="http://www.blogger.com/atom/ns#" term="Dealing With Upper and Lower Case Data" /><title>Dealing With Upper and Lower Case Data</title><content type="html">&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;When working with data in SQL Server you might run across different needs to change the case of the data. For instance, you might want to convert all the characters in a varchar column to upper case, lower case, or change only the first character of a column value to upper case. In addition, you might want to compare different character strings and either have them be equal or not equal when the characters are the same but the case is different. In this article, I will show you different examples of how to deal with different situations related to the case of character strings.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;What is Collation?&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Each server, database, or character column has a default collation. A collation is a definition for how each character is represented, stored and compared. Part of the collation setting, known as case sensitivity, determines if an upper case “A” and a low case “a” when compared against either other are the same or different. If the collation is case-insensitive then a lower case “a” and an upper case “A” when compared are the same. So when using case insensitive data the following two strings are equal, “AaAaA” and “aaaaa”. On the other hand, if your collation setting is case-sensitive then the preceding two strings of A’s will not be equal. Therefore, when you are working with data you need to understand what the case sensitivity is for the character data you are dealing with. &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;UPPER and LOWER Function&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Within SQL Server, two functions are provided to manage the case of character strings. These functions are UPPER and LOWER. The UPPER function accepts a character string as a parameter and returns the same character string where all the characters have been converted to upper case. If the character string “AbCd1234” is passed to the UPPER function, then this function returns “ABCD1234”. The LOWER function does just the opposite and converts all characters it receives to lower case, so if “AbCd1234” is passed to the LOWER function then it would return “abcd1234”. &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Converting the First Character of Each Column to Upper Case&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;For my first example let me show you how to convert the first character of each column value to upper case and lower casing the rest of the column. Here is the T-SQL code for my example:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;SET NOCOUNT ON 
IF exists (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[mytable]') 
 and OBJECTPROPERTY(id, N'IsUserTable') = 1)
DROP TABLE [mytable]
GO
CREATE TABLE [mytable] (
A CHAR(10))
go
INSERT INTO mytable VALUES('aAaA')
INSERT INTO mytable VALUES('bBbB')
INSERT INTO mytable VALUES('cCcC')
INSERT INTO mytable VALUES('dDdD')
INSERT INTO mytable VALUES('DDdD')
-- Uppercase first letter of each value in column
SELECT UPPER(SUBSTRING(A,1,1)) + LOWER(SUBSTRING(A,2,LEN(A))) 
 [Upper Case First Letter Only] 
FROM mytable
DROP TABLE mytable
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;In this example, I created a table “mytable” that contained a single column. I then insert five different records into this table. The column in each row contains a character string that has a mixed set of cases. In the SELECT statement above, I use the substring function to get the first character of column “A”. I then pass that first character to the UPPER function. This upper cases the first character of column “A”. I then use the concatenation operator (+) to append the rest of column “A” to that upper cased first character. Before the rest of column “A” is concatenated to the first character, it is lower cased by using the LOWER function on the substring of column “A” starting at second character of column “A”. Here is the output I get when I run the above script:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Upper Case First Letter Only
----------------------------
Aaaa 
Bbbb 
Cccc 
Dddd 
Dddd
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Proper Casing a Persons Name&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;In this example, I will take a column that contains a persons name and proper case the name. By proper case I mean I will take the first character of each name (First, Middle, Last, etc.) and upper case it and then lower case the rest of each name. &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;The bulk of my proper case example is coded in a T-SQL function. The function handles proper casing any number of names. It also handles multiple spaces between names, Irish type names,  and with apostrophe or parentheses within in the name.  Here is the code for my proper case function:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;IF OBJECT_ID (N'dbo.ProperCase', N'FN') IS NOT NULL
    DROP FUNCTION dbo.ProperCase
GO
CREATE FUNCTION ProperCase (@String VARCHAR(8000)) RETURNS VARCHAR(8000) AS BEGIN DECLARE @TempString VARCHAR(8000) DECLARE @PS VARCHAR(8000)
SET @PS = ''
-- lower case entire string
SET @TempString = lower(@String)
WHILE patindex('%[-( '']%',@TempString) &amp;gt; 0 BEGIN
  -- Check to see if first character of @TempString is whitespace
  IF (patindex('%[-( '']%',SUBSTRING(@TempString,1,1)) &amp;gt; 0)
  BEGIN
    SET @PS = @PS + SUBSTRING(@TempString,1,1)
  END
  ELSE -- @TempString starts with a Name
  BEGIN
   IF SUBSTRING(@TempString,1,2) = 'mc'
   BEGIN
     SET @PS = @PS + 'Mc'
     SET @TempString = SUBSTRING(@Tempstring,3,LEN(@TempString))
   END
   IF SUBSTRING(@TempString,1,3) = 'mac'
   BEGIN
     SET @PS = @PS + 'Mac'
     SET @TempString = SUBSTRING(@Tempstring,4,LEN(@TempString))
   END
      
    -- upper case first character and return string up to the next space
    SET @PS = @PS + UPPER(SUBSTRING(@TempString,1,1)) +
     SUBSTRING(@TempString,2,patindex('%[-( '']%',@TempString)-1)
    
    
  END
  -- truncation string that we have already processed
  
  SET @TempString = SUBSTRING(@TempString,
     patindex('%[-( '']%',@TempString)+1,LEN(@TempString))
  -- Trim off leading spaces
  SET @TempString = LTRIM(@TempString)
END
IF SUBSTRING(@TempString,1,2) = 'mc'
BEGIN
  SET @PS = @PS + 'Mc'
   SET @TempString = SUBSTRING(@Tempstring,3,LEN(@TempString))
END
IF SUBSTRING(@TempString,1,3) = 'mac'
BEGIN
  SET @PS = @PS + 'Mac'
  SET @TempString = SUBSTRING(@Tempstring,4,LEN(@TempString))
END
-- proper case last word/name
SET @PS = @PS + UPPER(SUBSTRING(@TempString,1,1)) +
SUBSTRING(@TempString,2,LEN(@TempString))
-- check for spaces in front of special characters
SET @PS = Replace(@PS,' -','-')
SET @PS = Replace(@PS,' ''','''')

RETURN (@PS)
END
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Here is an example of how to execute my ProperCase function:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;CREATE TABLE #temp (
PersonName VARCHAR(100))
INSERT INTO #temp VALUES('GREGory A. LARSEN')
INSERT INTO #temp VALUES('TODD JOShUA   SMITH')
INSERT INTO #temp VALUES('HARRY  JOnes')
INSERT INTO #temp VALUES('Danielle i. RobBins')
INSERT INTO #temp VALUES('mr. john d. johnson')
INSERT INTO #temp VALUES('Jon o''connor') 
INSERT INTO #temp VALUES('mARtiN mCGalpan')
INSERT INTO #temp VALUES('sARah   maCdonald')
INSERT INTO #temp VALUES('maRy   Smith-foster')
INSERT INTO #temp VALUES('Robert (bob)  smith')
SELECT dbo.ProperCase(PersonName) [Proper Case Names] FROM #temp
DROP TABLE #temp 
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;When I run the above code, I get the following output:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Proper Case Names
-------------------------------------
Gregory A. Larsen
Todd Joshua   Smith
Harry  Jones
Danielle I. Robbins
Mr. John D. Johnson
Jon O'Connor
Martin McGalpan
Sarah MacDonald
Mary Smith-Foster
Robert (Bob) Smith
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;My code above, that calls my proper case function, first creates and populates a temporary table that contains a PersonName column where the names are specified in a mix of upper and lower case characters. In the SELECT statement, I call the ProperCase function and pass it the PersonName column. The ProperCase function takes the passed parameter string and first converts all the characters to lower case using the LOWER function and sets a variable @TempString to the lower case value. The function then processes through the @TempString with a WHILE loop looking for special characters in the name (dash, parentheses, spaces or a quote). Each pass through the WHILE loop, the @TempString is evaluated to determine if the first character is one of these special characters, or is it the first character of a name. If a special character is found the special character is added to the @PS variable. When a new name is found, (first character is not a special character) then the case of the first character is converted to upper case, and then both the first character of the name and the rest of the name are added to the @PS string. The @TempString is shortened to only include the characters after the first space. The WHILE loop continues processing until the @TempString contains no more special character. Once the WHILE loop completes, the final part of the name is then proper cased and added to the @PS variable. The @PS variable is then returned to the calling SELECT statement. &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;The function makes it easy to implement a process to convert a character string to proper case. Having the proper case code embedded in a function simplifies the code for the SELECT statement to proper case a column.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Comparing Columns with Different Case&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;As stated earlier the collation of a character string will determine how SQL Server compares character strings. If you store your data using a case-insensitive format then when comparing the character string “AAAA” and “aaaa” they will be equal. If you want to be able to compare two case-insensitive strings and have the comparison be false when the two strings are the same except for the case of the characters then you need to use the COLLATE clause when comparing the two strings. To demonstrate look at the following code:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;CREATE TABLE #mytable (
C CHAR(10) COLLATE Latin1_General_CI_AS)
INSERT INTO #mytable VALUES('aaaa')
SELECT C FROM #mytable WHERE C = 'AAAA' 
SELECT C FROM #mytable WHERE C = 'AAAA' COLLATE Latin1_General_CS_AS
DROP TABLE #mytable
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;When I run this code on my machine, I get the following output.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;C
----------
aaaa      
(1 row(s) affected)
C
----------
(0 row(s) affected)
&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Here you can see the WHERE statement in the first SELECT statement returned the single record from #mytable. The WHERE statement evaluates to true even though the value of column C contains 4 lower case a’s and it was being compared to 4 upper case A’s. This WHERE statement was true because both the column C and the character string “AAAA” have a collation of Latin1_General_CI_AS, which is a case-insensitive collation. A character string is assigned the default collation of the database when the string is not associated with a COLLATE clause. To make sure the 4 upper case A’s don’t match the 4 lower case a’s in column C I need to specify a case-sensitive collation on one side of the equal operator. By specifically casting the character string “AAAA” to a case-sensitive collation using the “COLLATE Latin_General_CS_AS” clause in the second SELECT statement I forced the sting on the right of the equal operator to be case sensitive, which caused “aaaa” to not equal “AAAA”. &lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;Conclusion&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;&lt;span style="font-family: Verdana,Arial,Helvetica,sans-serif;"&gt;This article provided you with some examples of how to physically change the case of some column values and how to set the collation of a character string. Depending on your application’s needs you might need to convert or compare character strings that contain upper and lower case characters. The use of the UPPER and LOWER functions allows you to convert your character strings to whatever case you need. The COLLATE clause helps you explicitly convert a string of characters to the desired collation. When comparing character strings it is important to understand the case-sensitivity of all strings to make sure the outcome of your comparison is what you expect. &lt;/span&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-1841267938651693615?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/NTPV-uWD9yU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/1841267938651693615/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2009/12/dealing-with-upper-and-lower-case-data.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/1841267938651693615?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/1841267938651693615?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/NTPV-uWD9yU/dealing-with-upper-and-lower-case-data.html" title="Dealing With Upper and Lower Case Data" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2009/12/dealing-with-upper-and-lower-case-data.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0cCRHc7fip7ImA9WxBTE0g.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-2715821475631710175</id><published>2009-12-09T03:44:00.000-08:00</published><updated>2009-12-09T03:44:25.906-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-09T03:44:25.906-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="IP Address" /><category scheme="http://www.blogger.com/atom/ns#" term="IP Address in SQL" /><category scheme="http://www.blogger.com/atom/ns#" term="Storing the IP address in an efficient format" /><title>How To Store IP Addresses in SQL Server</title><content type="html">&lt;h2&gt;What is the best way to store an IP Address in SQL Server&lt;/h2&gt;Imagine that you are asked to design a database for a data analysis team to perform web site traffic analysis. This brief will have many requirements and amongst them will be the need to store IP addresses.&lt;br /&gt;
Search engines such as Google have a fixed range of IP addresses so the team can easily separate out traffic from true visitors Vs traffic from bots.  Although the team have a good rudimentary knowledge of T-SQL the data must  be relatively simple to query.  So with regard to IP addresses the requirement is as follows:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Store IP addresses efficiently&lt;/li&gt;
&lt;li&gt;Allow retrieval of IP addresses in a machine readable format&lt;/li&gt;
&lt;li&gt;Allow simple querying on a range or ranges of IP addresses&lt;/li&gt;
&lt;/ul&gt;&lt;h3 class="section"&gt;The data type question&lt;/h3&gt;If traffic to your web site is high then the choice of data types is going to be important. You could keep the IP address as a VARCHAR(15) but given the nature of what an IP address actually is, 4 integers in the range 0 to 255, this seems a trifle wasteful.&lt;br /&gt;
So what possiblities do we have?  Let us consider the ip address 192.168.0.5&lt;br /&gt;
&lt;table border="1" cellpadding="3" cellspacing="0" summary="DataType possbilities"&gt;&lt;thead&gt;
&lt;tr&gt;             &lt;th align="left"&gt;Method&lt;/th&gt;             &lt;th align="left"&gt;Storage&lt;/th&gt;             &lt;th align="left"&gt;Comment&lt;/th&gt;         &lt;/tr&gt;
&lt;/thead&gt;     &lt;tbody&gt;
&lt;tr&gt;             &lt;td&gt;VARCHAR(15)&lt;/td&gt;             &lt;td&gt;Between 7 and 15 bytes&lt;/td&gt;             &lt;td&gt;Stores the IP address in human readable but this is wasteful.&lt;/td&gt;         &lt;/tr&gt;
&lt;tr&gt;             &lt;td&gt;BIGINT&lt;/td&gt;             &lt;td&gt;8 bytes&lt;/td&gt;             &lt;td&gt;We can represent our IP address as 192168000005. It is stretching the definition of human readability somewhat but this depends on your audience.&lt;/td&gt;         &lt;/tr&gt;
&lt;tr&gt;             &lt;td&gt;INT&lt;/td&gt;             &lt;td&gt;4 bytes&lt;/td&gt;             &lt;td&gt;Our IP address is no longer human readable being represented as 1084751877.&lt;/td&gt;         &lt;/tr&gt;
&lt;tr&gt;             &lt;td&gt;Four separate TINYINT fields&lt;/td&gt;             &lt;td&gt;4 bytes&lt;/td&gt;             &lt;td&gt;Our address is now both efficient and human readable just as Joe Celko pointed out.&lt;/td&gt;         &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;&lt;h3 class="section"&gt;SQL 2005 CLR User Defined Types&lt;/h3&gt;SQL2005 provides us with one other possiblity.  The .NET assembly user defined type.&lt;br /&gt;
I was fortunate to go on the Microsoft "Updating Your Database Development Skills to Microsoft SQL Server 2005" (Course 2734B) which included an IP address UDT. As I am not sure of the copyright issues surrounding the code for the UDT I am not including the source code here but the UDT provided the following functionality.&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Accept an ip address in the form nnn.nnn.nnn.nnn&lt;/li&gt;
&lt;li&gt;Return the individual bytes of an IP address&lt;/li&gt;
&lt;li&gt;Return the string representation of the IP address&lt;/li&gt;
&lt;li&gt;Return a varbinary representation of the IP address&lt;/li&gt;
&lt;li&gt;Return a string with the PING command and the IP address.  I removed this from the code as it was irrelevant.&lt;/li&gt;
&lt;/ul&gt;There are similar functions available on the web.&lt;br /&gt;
&lt;h3 class="section"&gt;Test methodology&lt;/h3&gt;I decided to create five tables and into each put 1 million rows. Each table would contain a single INT IDENTITY field (IPID)acting as the primary key, plus the chosen method of storing an IP address.&lt;br /&gt;
&lt;table border="1" cellpadding="3" cellspacing="0" summary="Table types"&gt;&lt;thead&gt;
&lt;tr&gt;             &lt;th align="left"&gt;Table&lt;/th&gt;             &lt;th align="left"&gt;Comment&lt;/th&gt;         &lt;/tr&gt;
&lt;/thead&gt;     &lt;tbody&gt;
&lt;tr&gt;             &lt;td&gt;IPAddressSource&lt;/td&gt;             &lt;td&gt;IP Address stored as 4 separate TINY INT fields&lt;/td&gt;         &lt;/tr&gt;
&lt;tr&gt;             &lt;td&gt;IPAddressINT&lt;/td&gt;             &lt;td&gt;IP Address stored as a single INT field&lt;/td&gt;         &lt;/tr&gt;
&lt;tr&gt;             &lt;td&gt;IPAddressBIGINT&lt;/td&gt;             &lt;td&gt;IP Address stored as a single BIGINT field&lt;/td&gt;         &lt;/tr&gt;
&lt;tr&gt;             &lt;td&gt;IPAddressVARCHAR&lt;/td&gt;             &lt;td&gt;IP Address stored as a single VARCHAR(15) field&lt;/td&gt;         &lt;/tr&gt;
&lt;tr&gt;             &lt;td&gt;IPAddressUDT&lt;/td&gt;             &lt;td&gt;IP Address stored in a .NET CLR data type&lt;/td&gt;         &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;After population I ran an sp_spaceused on each table using the stored procedures described in  &lt;a href="http://www.sqlservercentral.com/columnists/dpoole/2771.asp"&gt;Automated Monitoring Database Size Using sp_spaceused&lt;/a&gt;.   For those who caught the article first time around I have added the equivalent procedure for SQL2000 in the discussion forum  attached to the article.&lt;br /&gt;
&lt;h3 class="section"&gt;Initial results&lt;/h3&gt;&lt;table border="1" cellpadding="3" cellspacing="0" summary="Initial sp_spaceused"&gt;&lt;thead&gt;
&lt;tr&gt;             &lt;th align="left"&gt;TableName&lt;/th&gt;             &lt;th align="left"&gt;Rows&lt;/th&gt;             &lt;th align="left"&gt;Reserved&lt;/th&gt;             &lt;th align="left"&gt;Data&lt;/th&gt;             &lt;th align="left"&gt;IndexSize&lt;/th&gt;             &lt;th align="left"&gt;Unused&lt;/th&gt;         &lt;/tr&gt;
&lt;/thead&gt;     &lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;dbo.IPAddressBIGINT&lt;/td&gt;&lt;td&gt;1,000,000&lt;/td&gt;&lt;td&gt;20,872&lt;/td&gt;&lt;td&gt;20,784&lt;/td&gt;&lt;td&gt;88&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;dbo.IPAddressINT&lt;/td&gt;&lt;td&gt;1,000,000&lt;/td&gt;&lt;td&gt;16,904&lt;/td&gt; &lt;td&gt;16,808&lt;/td&gt;&lt;td&gt;72&lt;/td&gt;&lt;td&gt;16&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;dbo.IPAddressSource&lt;/td&gt;&lt;td&gt;1,000,000&lt;/td&gt;&lt;td&gt;16,904&lt;/td&gt;&lt;td&gt;16,808&lt;/td&gt;&lt;td&gt;72&lt;/td&gt;&lt;td&gt;24&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;dbo.IPAddressUDT&lt;/td&gt;&lt;td&gt;1,000,000&lt;/td&gt;&lt;td&gt;&lt;span style="color: red;"&gt;49,928&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&lt;span style="color: red;"&gt;49696&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&lt;span style="color: red;"&gt;192&lt;/span&gt;&lt;/td&gt;&lt;td&gt;40&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;dbo.IPAddressVARCHAR&lt;/td&gt;&lt;td&gt;1,000,000&lt;/td&gt;&lt;td&gt;30,088&lt;/td&gt;&lt;td&gt;29,960&lt;/td&gt;&lt;td&gt;120&lt;/td&gt;&lt;td&gt;8&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;Straight away we can see that the table using the CLR UDT is by far the worst in terms of storage size taking nearly 50MB  to store our million rows.&lt;br /&gt;
Running an &lt;span style="color: maroon;"&gt;sp_help&lt;/span&gt; reveals why.&lt;br /&gt;
&lt;table border="1" cellpadding="3" cellspacing="0" summary="sp_help"&gt;&lt;thead&gt;
&lt;tr&gt;             &lt;th align="left"&gt;Column_name&lt;/th&gt;             &lt;th align="left"&gt;Type&lt;/th&gt;             &lt;th align="left"&gt;Computed&lt;/th&gt;             &lt;th align="left"&gt;Length&lt;/th&gt;             &lt;th align="left"&gt;...etc&lt;/th&gt;         &lt;/tr&gt;
&lt;/thead&gt;     &lt;tbody&gt;
&lt;tr&gt;             &lt;td&gt;IPID&lt;/td&gt;             &lt;td&gt;int&lt;/td&gt;             &lt;td&gt;no&lt;/td&gt;             &lt;td&gt;4&lt;/td&gt;             &lt;td&gt;&amp;nbsp;&lt;/td&gt;         &lt;/tr&gt;
&lt;tr&gt;             &lt;td&gt;IPAddress&lt;/td&gt;             &lt;td&gt;IPAddress&lt;/td&gt;             &lt;td&gt;no&lt;/td&gt;             &lt;td&gt;&lt;span style="color: red;"&gt;&lt;strong&gt;37&lt;/strong&gt;&lt;/span&gt;&lt;/td&gt;             &lt;td&gt;&amp;nbsp;&lt;/td&gt;         &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;There is very little information available on the storage of data in CLR UDTs but what appears to happen is that  the instance of the datatype (including structure and data) is serialised and stored in a VARBINARY type format in the field.&lt;br /&gt;
This sort of begs the question as to what real-world use are CLR UDTs?&lt;br /&gt;
The motivation to try a CLR IPAddress type was that a single type could encapsulate the IP address.  The type is atomic.&lt;br /&gt;
Storing the data as 4 TINYINT fields means that any one of the four parts of the IP address is meaningless in its own right.  It is not atomic.&lt;br /&gt;
In this case we can see that there is a heavy overhead in gaining atomicity.&lt;br /&gt;
&lt;h3 class="section"&gt;The indexing question&lt;/h3&gt;We have established that for readability and efficient storage of data storing an IP address as 4 separate TINYINT fields is the most efficient. But what about searching on IP Addresses?&lt;br /&gt;
What I need to be able to do is to identify ranges of IP addresses that correspond to Web Bots. Given the sheer volume of records I am going to need to discover the most efficient method of doing this.&lt;br /&gt;
The SQL Query engine decides for itself whether or not there is a benefit in using an index. If the index is not regarded as being selective enough the engine will simply ignore the index.&lt;br /&gt;
To give an example I had a table containing details of cars where the primary key was make, model and year. &lt;br /&gt;
&lt;span style="color: blue;"&gt;WHERE &lt;/span&gt;make=&lt;span style="color: red;"&gt;'FORD'&lt;/span&gt; resulted in a TABLE SCAN &lt;br /&gt;
&lt;span style="color: blue;"&gt;WHERE &lt;/span&gt;make=&lt;span style="color: red;"&gt;'MASERATI'&lt;/span&gt; resulted in an INDEX SEEK&lt;br /&gt;
The only difference in the WHERE clause was the value being searched for.&lt;br /&gt;
Again if you look at the &lt;a href="http://www.sqlservercentral.com/forums/shwmessage.aspx?forumid=335&amp;amp;messageid=327630&amp;amp;p=6"&gt;discussion&lt;/a&gt; for Lee Everest's article to make the index as selective as possible Joe Celko suggests that an index be placed across the four TINYINT columns representing the IP Address but from the right-most first, in effect recording the IP address backwards. As the digits in an IP address vary the most in the right hand portion and least in the left hand portion this should make the index as selective as it can possibly be.&lt;br /&gt;
My first port of call was to create such an index on my dbo.IPAddressSource table.&lt;br /&gt;
The next step was to devise a query that would search for a range of IP addresses such as 72.100.5.25 to 74.50.25.1&lt;br /&gt;
&lt;hr /&gt; &lt;pre&gt;&lt;span style="color: green;"&gt;
/*
    Define parameters to test various ranges of IP addresses
*/
&lt;/span&gt;
&lt;span style="color: blue;"&gt;DECLARE&lt;/span&gt;
 @LowIP1 &lt;span style="color: blue;"&gt;TINYINT&lt;/span&gt; , @HighIP1 &lt;span style="color: blue;"&gt;TINYINT&lt;/span&gt; ,
 @LowIP2 &lt;span style="color: blue;"&gt;TINYINT&lt;/span&gt; , @HighIP2 &lt;span style="color: blue;"&gt;TINYINT&lt;/span&gt; ,
 @LowIP3 &lt;span style="color: blue;"&gt;TINYINT&lt;/span&gt; , @HighIP3 &lt;span style="color: blue;"&gt;TINYINT&lt;/span&gt; ,
 @LowIP4 &lt;span style="color: blue;"&gt;TINYINT&lt;/span&gt; , @HighIP4 &lt;span style="color: blue;"&gt;TINYINT&lt;/span&gt; 

&lt;span style="color: blue;"&gt;SELECT &lt;/span&gt;
 @LowIP1 = 72 , @HighIP1 = 74 ,
 @LowIP2 = 100 , @HighIP2 = 50 ,
 @LowIP3 = 5 , @HighIP3 = 25 ,
 @LowIP4 = 25 , @HighIP4 = 1 
 
&lt;span style="color: blue;"&gt;SELECT&lt;/span&gt; * &lt;span style="color: blue;"&gt;FROM&lt;/span&gt; dbo.IPAddressSource
&lt;span style="color: blue;"&gt;WHERE&lt;/span&gt;
 (
  IP1= @LowIP1 
  &lt;span style="color: silver;"&gt;AND&lt;/span&gt; (
   IP2&amp;gt;@LowIP2
   &lt;span style="color: silver;"&gt;OR&lt;/span&gt; (IP2=@LowIP2 &lt;span style="color: silver;"&gt;AND&lt;/span&gt; ((IP3 = @LowIP3 &lt;span style="color: silver;"&gt;AND&lt;/span&gt; IP4&amp;gt;=@LowIP4) OR IP3&amp;gt;@LowIP3))
  )
 )
 &lt;span style="color: silver;"&gt;OR&lt;/span&gt; (IP1&amp;gt;@LowIP1 &lt;span style="color: silver;"&gt;AND&lt;/span&gt; IP1&amp;lt;@HighIP1)
&lt;span style="color: silver;"&gt;OR&lt;/span&gt;
 (
  IP1 = @HighIP1
  &lt;span style="color: silver;"&gt;AND&lt;/span&gt; (
   IP2 &amp;lt; @HighIP2
   &lt;span style="color: silver;"&gt;OR&lt;/span&gt; (IP2=@HighIP2  &lt;span style="color: silver;"&gt;AND&lt;/span&gt; ((IP3=@HighIP3 &lt;span style="color: silver;"&gt;AND&lt;/span&gt; IP4&amp;lt;=@HighIP4) &lt;span style="color: silver;"&gt;OR&lt;/span&gt; IP3&amp;lt;@HighIP3))
 )
)&lt;/pre&gt;&lt;pre&gt;&lt;/pre&gt;&lt;h3 class="section"&gt;An alternative approach&lt;/h3&gt;We have established that storing an IP address as 4 TINYINT fields achieves two objectives&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Storing the IP address in an efficient format&lt;/li&gt;
&lt;li&gt;Storing the IP address in a format that is easily read by a human being&lt;/li&gt;
&lt;/ul&gt;The problem areas are as follows&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Searching for a range of addresses requires convoluted WHERE conditions&lt;/li&gt;
&lt;li&gt;The query is expensive&lt;/li&gt;
&lt;/ul&gt;As an alternative experiment I decided to create a new table called dbo.IPAddressCALC as follows&lt;br /&gt;
&lt;span style="color: blue;"&gt;IF&lt;/span&gt; &lt;span style="color: darkgrey;"&gt;NOT EXISTS&lt;/span&gt;(&lt;span style="color: blue;"&gt;SELECT&lt;/span&gt; 1 &lt;span style="color: blue;"&gt;FROM&lt;/span&gt; &lt;span style="color: green;"&gt;INFORMATION_SCHEMA.TABLES&lt;/span&gt; WHERE TABLE_NAME=&lt;span style="color: red;"&gt;'IPAddressCalc'&lt;/span&gt;)  &lt;span style="color: blue;"&gt;CREATE TABLE&lt;/span&gt; dbo.IPAddressCalc(   IPID &lt;span style="color: blue;"&gt;int&lt;/span&gt; &lt;span style="color: darkgrey;"&gt;NOT NULL&lt;/span&gt;,   IP1 &lt;span style="color: blue;"&gt;tinyint&lt;/span&gt; &lt;span style="color: darkgrey;"&gt;NOT NULL&lt;/span&gt;,   IP2 &lt;span style="color: blue;"&gt;tinyint&lt;/span&gt; &lt;span style="color: darkgrey;"&gt;NOT NULL&lt;/span&gt;,   IP3 &lt;span style="color: blue;"&gt;tinyint&lt;/span&gt; &lt;span style="color: darkgrey;"&gt;NOT NULL&lt;/span&gt;,   IP4 &lt;span style="color: blue;"&gt;tinyint&lt;/span&gt; &lt;span style="color: darkgrey;"&gt;NOT NULL&lt;/span&gt;,   IPAddress &lt;span style="color: blue;"&gt;AS&lt;/span&gt;    &lt;span style="color: magenta;"&gt;CAST&lt;/span&gt;(    ((&lt;span style="color: magenta;"&gt;CAST&lt;/span&gt;(IP1 &lt;span style="color: blue;"&gt;AS BIGINT&lt;/span&gt;)* 16777216)   + ( &lt;span style="color: magenta;"&gt;CAST&lt;/span&gt;(IP2 &lt;span style="color: blue;"&gt;AS BIGINT&lt;/span&gt;)* 65536)   + ( &lt;span style="color: magenta;"&gt;CAST&lt;/span&gt;(IP3 &lt;span style="color: blue;"&gt;AS BIGINT&lt;/span&gt;)*256)   + IP4)   -2147483648 &lt;span style="color: blue;"&gt;AS INT&lt;/span&gt;),   &lt;span style="color: blue;"&gt;CONSTRAINT&lt;/span&gt; PK_IPAddressCalc &lt;span style="color: blue;"&gt;PRIMARY KEY CLUSTERED&lt;/span&gt; ( IPID ASC)  )  &lt;span style="color: blue;"&gt;GO&lt;/span&gt;  Our IPAddress field is a calculated field using the bitmasking technique and contains an INT value.&lt;br /&gt;
To aid our search I placed an index on this column.  To search this column now our query would become&lt;br /&gt;
&lt;span style="color: blue;"&gt;SELECT&lt;/span&gt; * &lt;span style="color: blue;"&gt;FROM&lt;/span&gt; dbo.IPAddressCalc &lt;span style="color: blue;"&gt;WHERE&lt;/span&gt; IPAddress &lt;span style="color: darkgrey;"&gt;BETWEEN&lt;/span&gt; @Low &lt;span style="color: darkgrey;"&gt;AND&lt;/span&gt; @High  We would probably create  function to return the values for @Low and @High from supplied IP addresses.&lt;br /&gt;
Our WHERE clause is much simpler and it is now easy to write a query that can handle multiple ranges of ip addresses.&lt;br /&gt;
&lt;br /&gt;
&lt;h3 class="section"&gt;Wrapping it all up&lt;/h3&gt;The final step is to example &lt;span style="color: maroon;"&gt;sp_spaceused&lt;/span&gt; results for our two tables.  We are interested  in the amount of data and index space taken up by each item.&lt;br /&gt;
&lt;table border="1" cellpadding="3" cellspacing="0" summary="Initial sp_spaceused"&gt;&lt;thead&gt;
&lt;tr&gt;             &lt;th align="left"&gt;TableName&lt;/th&gt;             &lt;th align="left"&gt;Rows&lt;/th&gt;             &lt;th align="left"&gt;Reserved&lt;/th&gt;             &lt;th align="left"&gt;Data&lt;/th&gt;             &lt;th align="left"&gt;IndexSize&lt;/th&gt;             &lt;th align="left"&gt;Unused&lt;/th&gt;         &lt;/tr&gt;
&lt;/thead&gt;     &lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;dbo.IPAddressCalc&lt;/td&gt;&lt;td&gt;1,000,000&lt;/td&gt;&lt;td&gt;30,864&lt;/td&gt;&lt;td&gt;16,808&lt;/td&gt;&lt;td&gt;13,952&lt;/td&gt;&lt;td&gt;104&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;dbo.IPAddressSource&lt;/td&gt;&lt;td&gt;1,000,000&lt;/td&gt;&lt;td&gt;27,856&lt;/td&gt;&lt;td&gt;16,808&lt;/td&gt;&lt;td&gt;10,960&lt;/td&gt;&lt;td&gt;88&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;Adding a calculated column has no affect on the data held within the table, however, over 1 million rows the index takes  up just under 3MB more space.&lt;br /&gt;
The final note to add is that our analysis tables would be populated using some form of bulk loading process of the previous  day's results.  Placing an index on a calculated field of a highly dynamic table could potentially cause performance problems.&lt;br /&gt;
&lt;h3 class="section"&gt;Addendum&lt;/h3&gt;To develop this solution I generated 1 million random IP addresses.&lt;br /&gt;
For those of us who have cursed the SQL Server RAND function my population query was as follows&lt;br /&gt;
&lt;hr /&gt; &lt;pre&gt;&lt;span style="color: blue;"&gt;IF&lt;/span&gt; &lt;span style="color: darkgrey;"&gt;NOT EXISTS&lt;/span&gt;(&lt;span style="color: blue;"&gt;SELECT&lt;/span&gt; 1 &lt;span style="color: blue;"&gt;FROM&lt;/span&gt; dbo.IPAddressSource)
 &lt;span style="color: blue;"&gt;BEGIN
  SET ROWCOUNT&lt;/span&gt; 1000000
  &lt;span style="color: blue;"&gt;INSERT INTO&lt;/span&gt; dbo.IPAddressSource(IP1, IP2, IP3, IP4)
  &lt;span style="color: blue;"&gt;SELECT&lt;/span&gt; 
   &lt;span style="color: magenta;"&gt;FLOOR&lt;/span&gt;(256*&lt;span style="color: magenta;"&gt;RAND&lt;/span&gt;(&lt;span style="color: magenta;"&gt;cast&lt;/span&gt;(&lt;span style="color: magenta;"&gt;cast&lt;/span&gt;(&lt;span style="color: darkgrey;"&gt;left&lt;/span&gt;(&lt;span style="color: magenta;"&gt;newid&lt;/span&gt;(),8) &lt;span style="color: blue;"&gt;as varbinary&lt;/span&gt;  ) &lt;span style="color: blue;"&gt;as int&lt;/span&gt; )) ),
   &lt;span style="color: magenta;"&gt;FLOOR&lt;/span&gt;(256*&lt;span style="color: magenta;"&gt;RAND&lt;/span&gt;(&lt;span style="color: magenta;"&gt;cast&lt;/span&gt;(&lt;span style="color: magenta;"&gt;cast&lt;/span&gt;(&lt;span style="color: darkgrey;"&gt;left&lt;/span&gt;(&lt;span style="color: magenta;"&gt;newid&lt;/span&gt;(),8) &lt;span style="color: blue;"&gt;as varbinary&lt;/span&gt;  ) &lt;span style="color: blue;"&gt;as int&lt;/span&gt; )) ),
   &lt;span style="color: magenta;"&gt;FLOOR&lt;/span&gt;(256*&lt;span style="color: magenta;"&gt;RAND&lt;/span&gt;(&lt;span style="color: magenta;"&gt;cast&lt;/span&gt;(&lt;span style="color: magenta;"&gt;cast&lt;/span&gt;(&lt;span style="color: darkgrey;"&gt;left&lt;/span&gt;(&lt;span style="color: magenta;"&gt;newid&lt;/span&gt;(),8) &lt;span style="color: blue;"&gt;as varbinary&lt;/span&gt;  ) &lt;span style="color: blue;"&gt;as int&lt;/span&gt; )) ),
   &lt;span style="color: magenta;"&gt;FLOOR&lt;/span&gt;(256*&lt;span style="color: magenta;"&gt;RAND&lt;/span&gt;(&lt;span style="color: magenta;"&gt;cast&lt;/span&gt;(&lt;span style="color: magenta;"&gt;cast&lt;/span&gt;(&lt;span style="color: darkgrey;"&gt;left&lt;/span&gt;(&lt;span style="color: magenta;"&gt;newid&lt;/span&gt;(),8) &lt;span style="color: blue;"&gt;as varbinary&lt;/span&gt;  ) &lt;span style="color: blue;"&gt;as int&lt;/span&gt; )) )

  &lt;span style="color: blue;"&gt;FROM&lt;/span&gt; master.dbo.sysobjects &lt;span style="color: blue;"&gt;AS&lt;/span&gt; O1 ,
  master.dbo.sysobjects &lt;span style="color: blue;"&gt;AS&lt;/span&gt; O2 

  &lt;span style="color: blue;"&gt;SET ROWCOUNT&lt;/span&gt; 0
 &lt;span style="color: blue;"&gt;END&lt;/span&gt;
&lt;/pre&gt;&lt;hr /&gt; The method works because NEWID() returns a number that is supposed to be unique in time and space and consists fo a consistant format containing hexadecimal strings.&lt;br /&gt;
What I am doing is taking the first block of 8 characters, converting the resulting string to a VARBINARY value and this resulting value is then converted into an integer value.&lt;br /&gt;
I don't actually care what the integer value is as I am simply feeding it into the RAND function as a seed.&lt;br /&gt;
&lt;br /&gt;
&lt;pre&gt;&lt;/pre&gt;&lt;hr /&gt; As we can see the WHERE clause is a bit convoluted and this is just searching for one range of ip addresses.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-2715821475631710175?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/kij53aCgpbo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/2715821475631710175/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2009/12/how-to-store-ip-addresses-in-sql-server.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/2715821475631710175?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/2715821475631710175?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/kij53aCgpbo/how-to-store-ip-addresses-in-sql-server.html" title="How To Store IP Addresses in SQL Server" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2009/12/how-to-store-ip-addresses-in-sql-server.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0QERns_eip7ImA9WxBTE0g.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-5941496904500080154</id><published>2009-12-09T01:35:00.000-08:00</published><updated>2009-12-09T01:35:07.542-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-09T01:35:07.542-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="IDENTITY columns in SQL Server" /><category scheme="http://www.blogger.com/atom/ns#" term="IDENTITY columns" /><category scheme="http://www.blogger.com/atom/ns#" term="Arithmetic overflow" /><category scheme="http://www.blogger.com/atom/ns#" term="auto number" /><title>Keep a check on your IDENTITY columns in SQL Server</title><content type="html">&lt;span style="font-family: Verdana; font-size: x-small;"&gt; The IDENTITY columns, or 'auto number' columns as some people call them, are auto incrementing columns provided by SQL Server. There can only be one IDENTITY column per table. You just have to provide a base value, and an increment value, and SQL Server will take care of incrementing this column automatically. Some people like these, and some don't, but the truth is, IDENTITY columns are gaining popularity, and many production systems, including critical ones are using IDENTITY columns these days. So, it is important to keep an eye on these columns, to make sure they are not reaching the limit of their base data type. For example, if you created an IDENTITY column of smallint datatype, its values can go upto 32767. If you try to insert anymore rows, you will get the following error:&lt;br /&gt;
&lt;br /&gt;
&lt;span style="color: red; font-family: verdana;"&gt;Server: Msg 8115, Level 16, State 1, Line 1&lt;/span&gt;&lt;br /&gt;
Arithmetic overflow error converting IDENTITY to data type smallint.&lt;br /&gt;
Arithmetic overflow occurred.&lt;br /&gt;
&lt;br /&gt;
If this table happens to be a part of a critical production system, then you are in trouble. You will have to do something about it to resolve it. If the data from this table can be deleted, then delete the data using TRUNCATE TABLE command. TRUNCATE TABLE resets the IDENTITY column to its base value. The DELETE command doesn't do this. But then, if this table is referenced by a foreign key, then TRUNCATE TABLE is not allowed on this table. Your other option is to run DBCC CHECKIDENT on your table with RESEED option.&lt;br /&gt;
&lt;br /&gt;
An IDENTITY column of tinyint datatype can go upto 255, smallint can go upto 32767, int can go upto 2147483647 and bigint can go upto 9223372036854775807.&lt;br /&gt;
&lt;br /&gt;
You can proactively monitor these IDENTITY columns, to avoid getting into such problems. If you can see in advance, that an IDENTITY column is reaching its limit, then you could do something about it, before it reaches the limit. The other day, one of my friends was trying to automate a process that checks all the IDENTITY columns in a database and reports on how far away those columns are from the limit. He was using a cursor to go through all the tables in the database, and running a "SELECT MAX(IdentityCol) FROM TableName" on all the tables that have an IDENTITY column. It would take ages to run on a database with many big tables. It can be simplified into one simple query using IDENT_CURRENT function. That's what I did, and thought it will be useful for other DBAs as well. So, here I am writing about it.&lt;br /&gt;
&lt;br /&gt;
There are three different versions of this procedure. First one is for SQL Server 2005. The second and third versions work in SQL Server 2000. You will have to create this procedure in the database of your interest. And run it as shown below:&lt;br /&gt;
&lt;br /&gt;
&lt;span style="color: blue; font-family: verdana; font-size: x-small;"&gt; EXEC dbo.CheckIdentities&lt;br /&gt;
GO&lt;br /&gt;
&lt;/span&gt;&lt;br /&gt;
This procedure below, displays information about all IDENTITY columns in the database, and shows you the percentage of IDENTITY values already used. If you are seeing any IDENTITY columns that have used up 80% or more values, then you need to start thinking about it. You could customise this procedure to automatically email you or log an error if there are any IDENTITY columns that are nearing the limit. You could also schedule this procedure as an SQL Agent job, so that it checks these columns regularly. Any new IDENTITY columns added to the database will automatically get picked up by this query.&lt;br /&gt;
&lt;br /&gt;
A quick note about 64 bit SQL Server. Even though SQL Server 64 bit editions can access a lot more memory inherently, than the 32 bit systems could - the IDENTITY columns are still limited to the limits imposed by the base datatypes. I'm writing this because, someone recently asked me if int data type can store a higher number in 64 bit server, compared to a 32 bit server.&lt;br /&gt;
&lt;br /&gt;
Here's a screen shot of output from the AdventureWorks sample database in SQL Server 2005.&lt;br /&gt;
&lt;br /&gt;
&lt;img src="http://vyaskn.tripod.com/Images/identities.gif" /&gt; &lt;/span&gt; &lt;br /&gt;
&lt;pre&gt;&lt;span style="color: blue; font-family: verdana; font-size: x-small;"&gt;
&lt;span style="color: grey; font-family: verdana; font-size: x-small;"&gt;/* The SQL Server 2005 version of the stored procedure. It uses new catalog views */&lt;/span&gt;


CREATE PROC dbo.CheckIdentities
AS
BEGIN
 SET NOCOUNT ON

 SELECT QUOTENAME(SCHEMA_NAME(t.schema_id)) + '.' +  QUOTENAME(t.name) AS TableName, 
  c.name AS ColumnName,
  CASE c.system_type_id
   WHEN 127 THEN 'bigint'
   WHEN 56 THEN 'int'
   WHEN 52 THEN 'smallint'
   WHEN 48 THEN 'tinyint'
  END AS 'DataType',
  IDENT_CURRENT(SCHEMA_NAME(t.schema_id)  + '.' + t.name) AS CurrentIdentityValue,
  CASE c.system_type_id
   WHEN 127 THEN (IDENT_CURRENT(SCHEMA_NAME(t.schema_id)  + '.' + t.name) * 100.) / 9223372036854775807
   WHEN 56 THEN (IDENT_CURRENT(SCHEMA_NAME(t.schema_id)  + '.' + t.name) * 100.) / 2147483647
   WHEN 52 THEN (IDENT_CURRENT(SCHEMA_NAME(t.schema_id)  + '.' + t.name) * 100.) / 32767
   WHEN 48 THEN (IDENT_CURRENT(SCHEMA_NAME(t.schema_id)  + '.' + t.name) * 100.) / 255
  END AS 'PercentageUsed' 
 FROM sys.columns AS c 
  INNER JOIN
  sys.tables AS t 
  ON t.[object_id] = c.[object_id]
 WHERE c.is_identity = 1
 ORDER BY PercentageUsed DESC
END



&lt;span style="color: black; font-family: verdana; font-size: x-small;"&gt;If you try to create the above stored procedure in SQL Server 2000, you will get the following error:

&lt;/span&gt;
&lt;span style="color: red; font-family: verdana; font-size: x-small;"&gt;Server: Msg 195, Level 15, State 10, Procedure a, Line 4&lt;/span&gt;
&lt;span style="color: black; font-family: verdana; font-size: x-small;"&gt;'SCHEMA_NAME' is not a recognized function name.

So, here are some SQL Server 2000 compatible versions.&lt;/span&gt;


&lt;span style="color: grey; font-family: verdana; font-size: x-small;"&gt;/* The SQL Server 2000 version of the stored procedure. Uses system tables. This should work in SQL Server 7.0 too */&lt;/span&gt;


CREATE PROC dbo.CheckIdentities
AS
BEGIN
 SET NOCOUNT ON

 SELECT QUOTENAME(USER_NAME(t.uid))+ '.' +  QUOTENAME(t.name) AS TableName, 
  c.name AS ColumnName,
  CASE c.xtype
   WHEN 127 THEN 'bigint'
   WHEN 56 THEN 'int'
   WHEN 52 THEN 'smallint'
   WHEN 48 THEN 'tinyint'
  END AS 'DataType',
  IDENT_CURRENT(USER_NAME(t.uid)  + '.' + t.name) AS CurrentIdentityValue,
  CASE c.xtype
   WHEN 127 THEN (IDENT_CURRENT(USER_NAME(t.uid)  + '.' + t.name) * 100.) / 9223372036854775807
   WHEN 56 THEN (IDENT_CURRENT(USER_NAME(t.uid)  + '.' + t.name) * 100.) / 2147483647
   WHEN 52 THEN (IDENT_CURRENT(USER_NAME(t.uid)  + '.' + t.name) * 100.) / 32767
   WHEN 48 THEN (IDENT_CURRENT(USER_NAME(t.uid)  + '.' + t.name) * 100.) / 255
  END AS 'PercentageUsed' 
 FROM syscolumns AS c 
  INNER JOIN
  sysobjects AS t 
  ON t.id = c.id
 WHERE COLUMNPROPERTY(t.id, c.name, 'isIdentity') = 1
 AND OBJECTPROPERTY(t.id, 'isTable') = 1
 ORDER BY PercentageUsed DESC
END



&lt;span style="color: grey; font-family: verdana; font-size: x-small;"&gt;/* The SQL Server 2000 version of the stored procedure. Uses INFORMATION_SCHEMA views. */&lt;/span&gt;


CREATE PROC dbo.CheckIdentities
AS
BEGIN
 SET NOCOUNT ON

 SELECT QUOTENAME(t.TABLE_SCHEMA) + '.' + QUOTENAME(t.TABLE_NAME)  AS TableName, 
  c.COLUMN_NAME AS ColumnName,
  c.DATA_TYPE AS 'DataType',
  IDENT_CURRENT(t.TABLE_SCHEMA  + '.' + t.TABLE_NAME) AS CurrentIdentityValue,
  CASE c.DATA_TYPE
   WHEN 'bigint' THEN (IDENT_CURRENT(t.TABLE_SCHEMA  + '.' + t.TABLE_NAME) * 100.) / 9223372036854775807
   WHEN 'int' THEN (IDENT_CURRENT(t.TABLE_SCHEMA  + '.' + t.TABLE_NAME) * 100.) / 2147483647
   WHEN 'smallint' THEN (IDENT_CURRENT(t.TABLE_SCHEMA  + '.' + t.TABLE_NAME) * 100.) / 32767
   WHEN 'tinyint' THEN (IDENT_CURRENT(t.TABLE_SCHEMA  + '.' + t.TABLE_NAME) * 100.) / 255
  END AS 'PercentageUsed' 
 FROM INFORMATION_SCHEMA.COLUMNS AS c 
  INNER JOIN
  INFORMATION_SCHEMA.TABLES AS t 
  ON c.TABLE_SCHEMA = t.TABLE_SCHEMA AND c.TABLE_NAME = t.TABLE_NAME
 WHERE COLUMNPROPERTY(OBJECT_ID(t.TABLE_SCHEMA + '.' + t.TABLE_NAME), c.COLUMN_NAME, 'isIdentity') = 1
 AND c.DATA_TYPE IN ('bigint', 'int', 'smallint', 'tinyint')
 AND t.TABLE_TYPE = 'BASE TABLE'
 ORDER BY PercentageUsed DESC
END

&lt;/span&gt;
&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-5941496904500080154?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/mXHDpdnBHIc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/5941496904500080154/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2009/12/keep-check-on-your-identity-columns-in.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/5941496904500080154?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/5941496904500080154?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/mXHDpdnBHIc/keep-check-on-your-identity-columns-in.html" title="Keep a check on your IDENTITY columns in SQL Server" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2009/12/keep-check-on-your-identity-columns-in.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0YHRHo-eip7ImA9WxBTEkU.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-7944050207829085201</id><published>2009-12-08T07:12:00.000-08:00</published><updated>2009-12-08T07:12:15.452-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-08T07:12:15.452-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="How to Use GROUP BY in SQL Server" /><category scheme="http://www.blogger.com/atom/ns#" term="Using COUNT(Distinct)" /><category scheme="http://www.blogger.com/atom/ns#" term="parent/child relationship" /><category scheme="http://www.blogger.com/atom/ns#" term="one-to-many" /><category scheme="http://www.blogger.com/atom/ns#" term="GROUP BY" /><category scheme="http://www.blogger.com/atom/ns#" term="GROUP BY in SQL Server" /><title>How to Use GROUP BY in SQL Server</title><content type="html">&lt;strong&gt;This article has been updated through SQL Server 2005.&lt;/strong&gt;)    &lt;!-- &lt;hr  /&gt;&lt;br /&gt;
  --&gt;  &lt;!-- *** Start of Article *** --&gt; &lt;br /&gt;
Here's the schema we'll be using along with some sample data:&lt;br /&gt;
&lt;pre&gt;create table Orders 
(
    OrderID int primary key, 
    Customer varchar(10),
    OrderDate datetime, 
    ShippingCost money
)

create table OrderDetails
(
    DetailID int primary key,
    OrderID int references Orders(OrderID),
    Item varchar(10),
    Amount money
)

go

insert into Orders
select 1,'ABC', '2007-01-01', 40 union all
select 2,'ABC', '2007-01-02', 30 union all
select 3,'ABC', '2007-01-03', 25 union all
select 4,'DEF', '2007-01-02', 10

insert into OrderDetails
select 1, 1, 'Item A', 100 union all
select 2, 1, 'Item B', 150 union all
select 3, 2, 'Item C', 125 union all
select 4, 2, 'Item B', 50 union all
select 5, 2, 'Item H', 200 union all
select 6, 3, 'Item X', 100 union all
select 7, 4, 'Item Y', 50 union all
select 8, 4, 'Item Z', 300

&lt;/pre&gt;&lt;h2&gt;Determining the Virtual Primary Key of a Result Set&lt;/h2&gt;Let's examine our sample data by joining these tables together to return Orders along with the OrderDetails: &lt;br /&gt;
&lt;pre&gt;select 
  o.orderID, o.customer, o.orderdate, o.shippingCost, od.DetailID, od.Item, od.Amount
from 
  orders o
inner join 
  OrderDetails od on o.OrderID = od.OrderID

orderID     customer   orderdate      shippingCost  DetailID    Item       Amount 
----------- ---------- -------------- ------------- ---------   ---------- ----------
1           ABC        2007-01-01     40.0000       1           Item A     100.0000
1           ABC        2007-01-01     40.0000       2           Item B     150.0000
2           ABC        2007-01-02     30.0000       3           Item C     125.0000
2           ABC        2007-01-02     30.0000       4           Item B     50.0000
2           ABC        2007-01-02     30.0000       5           Item H     200.0000
3           ABC        2007-01-03     25.0000       6           Item X     100.0000
4           DEF        2007-01-02     10.0000       7           Item Y     50.0000
4           DEF        2007-01-02     10.0000       8           Item Z     300.0000

(8 row(s) affected)

&lt;/pre&gt;Remember that Orders has a &lt;strong&gt;one-to-many&lt;/strong&gt; or &lt;strong&gt;parent/child&lt;/strong&gt; relationship with OrderDetails:  thus, &lt;strong&gt;one&lt;/strong&gt; order can have &lt;strong&gt;many&lt;/strong&gt; details. When we join them together, the Order columns are repeated over and over for each OrderDetail. This is normal, standard SQL behavior and what happens when you join tables in a one-to-many relation. Our result contains one row per OrderDetail, and those OrderDetail rows are never repeated, since it is not being joined to any further tables that will produce duplicate rows. &lt;br /&gt;
Thus, we could say that our result set has a &lt;em&gt;virtual primary key&lt;/em&gt; of DetailID; there will never be a duplicate OrderDetail row in the data. We can count and add up all OrderDetail columns and never worry about double counting values. However, we can not do the same for the Orders table, since its rows are duplicated in the results. Remember this as we move forward. &lt;br /&gt;
&lt;h2&gt;A Typical Summary Report Example&lt;/h2&gt;Here is a good example using Orders and OrderDetails that demonstrates various aggregate functions and typical things to look for: &lt;br /&gt;
&lt;em&gt;For each Customer, we want to return the total number of orders, the number of items ordered, the total order amount, and the total shipping cost.&lt;/em&gt; &lt;br /&gt;
All of this information lives in the Orders and the OrderDetails tables, and we know how to join them together, we just need to summarize those results now. We don't want to return all 8 rows and see all of the details; we just want to return 2 rows: one per customer, with the corresponding summary calculations. &lt;br /&gt;
&lt;h2&gt;Grouping and Summarizing Primary Rows&lt;/h2&gt;Since we want to return 1 row for each Customer, we can simply add &lt;strong&gt;GROUP BY Customer&lt;/strong&gt; to the end of the SELECT. We can return the Customer column since we are grouping on it, and we can add any other columns as long as they are summarized in an aggregate function. We can also use the &lt;em&gt;COUNT(*)&lt;/em&gt; aggregate function to return the number of rows per group. Since each row in our data corresponds to an order item, grouping by Customer and selecting Customer and &lt;em&gt;COUNT(*)&lt;/em&gt; will return the number of OrderDetails per Customer: &lt;br /&gt;
&lt;pre&gt;select 
  o.Customer, &lt;strong&gt;count(*) as ItemCount&lt;/strong&gt;

from 
  Orders o
inner join 
  OrderDetails od on o.OrderID = od.OrderID
&lt;strong&gt;group by 
  o.Customer&lt;/strong&gt;

Customer   ItemCount   
---------- ----------- 
ABC        6
DEF        2

(2 row(s) affected)

&lt;/pre&gt;Next, let’s add the total order amount per customer. Since our join resulted in one row per item detail (remember: when joining Orders to OrderDetails, the result set has a &lt;em&gt;virtual primary key&lt;/em&gt; of DetailID) we know that no rows in the OrderDetails table were duplicated in our results.  Therefore, a simple &lt;em&gt;SUM()&lt;/em&gt; of the Amount column from the OrderDetails table is all we need to return the total order amount per customer: &lt;br /&gt;
&lt;pre&gt;select 
  o.Customer, count(*) as ItemCount, &lt;strong&gt;sum(od.Amount) as OrderAmount&lt;/strong&gt;

from 
  Orders o
inner join 
  OrderDetails od on o.OrderID = od.OrderID
group by 
  o.Customer

Customer   ItemCount   OrderAmount           
---------- ----------- --------------------- 
ABC        6           725.0000
DEF        2           350.0000

(2 row(s) affected)

&lt;/pre&gt;So far, so good! Only two more calculations to go: total orders per customer, and total shipping cost. Both of these values come from the Orders table.&lt;br /&gt;
If you recall, our join resulted in one row per OrderDetail, and that meant that the Orders table had duplicate rows. We need to return two calculations from our Orders table -- total number of Orders, and total Shipping cost. We already know that &lt;em&gt;COUNT(*)&lt;/em&gt; returns the number of OrderDetails,  so that won’t work for us.  Perhaps &lt;strong&gt;COUNT(OrderID)&lt;/strong&gt; will return the total number of orders?   Let's try it: &lt;br /&gt;
&lt;pre&gt;select 
  o.Customer, count(*) as ItemCount, sum(od.Amount) as OrderAmount, 
  &lt;strong&gt;count(o.OrderID) as OrderCount&lt;/strong&gt;
from 
  Orders o
inner join 
  OrderDetails od on o.OrderID = od.OrderID
group by 
  o.Customer

Customer   ItemCount   OrderAmount           OrderCount  
---------- ----------- --------------------- ----------- 
ABC        6           725.0000              6           
DEF        2           350.0000              2           

(2 row(s) affected)


&lt;/pre&gt;Notice that the OrderCount column returned is the same as the ItemCount, and definitely not the number of orders per customer. That is because, by definition in SQL, &lt;em&gt;COUNT(expression)&lt;/em&gt; just returns the total number of rows in which that expression is not null. Since OrderID is never null, it returns the total row count per Customer, and since each row corresponds with an OrderDetail item, we get the number of OrderDetail items per customer, &lt;strong&gt;not&lt;/strong&gt; the number of Orders per customer. &lt;br /&gt;
&lt;h2&gt;Using COUNT(Distinct)&lt;/h2&gt;Luckily, there is a &lt;em&gt;DISTINCT &lt;/em&gt;feature that we can use in our &lt;em&gt;COUNT() &lt;/em&gt;      aggregate function that will help us here.  &lt;em&gt;Count(Distinct expression)&lt;/em&gt; means: " return the total number of distinct values for the specified expression."   So, if we write &lt;strong&gt;COUNT(Distinct OrderID)&lt;/strong&gt;, it will return the distinct number of OrderID values per Customer. Since each OrderID value corresponds to an Order (it is the primary key of the Orders table), we can use this to calculate our Order count: &lt;br /&gt;
&lt;pre&gt;select 
  o.Customer, count(*) as ItemCount, sum(od.Amount) as OrderAmount, 
  &lt;strong&gt;count(distinct o.OrderID) as OrderCount&lt;/strong&gt;
from 
   Orders o
inner join 
  OrderDetails od on o.OrderID = od.OrderID
group by 
  o.Customer

Customer   ItemCount   OrderAmount           OrderCount  
---------- ----------- --------------------- ----------- 
ABC        6           725.0000              3
DEF        2           350.0000              1

(2 row(s) affected)

&lt;/pre&gt;Great!  Looks good, makes sense, now we are getting somewhere.   &lt;br /&gt;
&lt;h2&gt;Beware of SUMMING Duplicate Values&lt;/h2&gt;Moving on, let’s add an expression to calculate total shipping cost per customer. Well, we know we have a ShippingCost column in our data from the Orders table, so let's try just adding &lt;strong&gt;SUM(ShippingCost)&lt;/strong&gt; to our SELECT: &lt;br /&gt;
&lt;pre&gt;select 
  o.Customer, count(*) as ItemCount, sum(od.Amount) as OrderAmount, 
  count(distinct o.OrderID) as OrderCount, &lt;strong&gt;sum(o.ShippingCost) as TotalShipping&lt;/strong&gt;
from 
  Orders o
inner join 
  OrderDetails od on o.OrderID = od.OrderID
group by 
  o.Customer

Customer   ItemCount   OrderAmount           OrderCount  TotalShipping         
---------- ----------- --------------------- ----------- --------------------- 
ABC        6           725.0000              3           195.0000
DEF        2           350.0000              1           20.0000

(2 row(s) affected)

&lt;/pre&gt;Looks like we are good to go, right? Well -- not really, look at our TotalShipping column. Customer ABC has a total shipping of 195. Yet, in our Orders table, we see that the customer has 3 orders, with shipping values of 40,30 and 25. 40+30+25=95, not 195! What is going on here? Well, like the &lt;em&gt;COUNT(*)&lt;/em&gt; expression, remember that the &lt;em&gt;SUM()&lt;/em&gt; is acting not upon our tables themselves, but the result of the JOIN that we expressed in our SELECT. The JOIN from Orders to OrderDetails meant that rows from the Orders table were duplicated, remember? So, if we &lt;em&gt;SUM()&lt;/em&gt; a column in our Orders table when it is joined to the Details, we are summing up duplicate values and our result will be too high. You can verify this by reviewing the results returned by the join from Orders to OrderDetails and manually adding them up by hand. &lt;br /&gt;
It is very important to understand this; when writing any JOINS, you need to identify which tables can and cannot have duplicate rows returned. The basic rule is very simple: &lt;br /&gt;
&lt;em&gt;If Table A has one-to-many relation with Table B, and Table A is JOINED to Table B, then Table A will have duplicate rows in the result. The final result will have a virtual primary key equal to that of Table B's.&lt;/em&gt;&lt;br /&gt;
So, we know we can &lt;em&gt;SUM() &lt;/em&gt;and &lt;em&gt;COUNT() &lt;/em&gt;columns from Table B with no problem, but we cannot do that with columns from Table A because the duplicate values will skew our results.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-7944050207829085201?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/gjIE9W_0A4g" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/7944050207829085201/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2009/12/how-to-use-group-by-in-sql-server.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/7944050207829085201?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/7944050207829085201?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/gjIE9W_0A4g/how-to-use-group-by-in-sql-server.html" title="How to Use GROUP BY in SQL Server" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2009/12/how-to-use-group-by-in-sql-server.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0MASXoycSp7ImA9WxBTEkg.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-5804427312965220579</id><published>2009-12-07T21:34:00.000-08:00</published><updated>2009-12-07T21:50:48.499-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-07T21:50:48.499-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="SQL Server 2008 introduces MERGE" /><category scheme="http://www.blogger.com/atom/ns#" term="Merge in SQL" /><category scheme="http://www.blogger.com/atom/ns#" term="MERGE keyword" /><category scheme="http://www.blogger.com/atom/ns#" term="New in Sql Server 2008" /><title>SQL Server 2008 - The Power of Merge</title><content type="html">Introduction&lt;br /&gt;
&lt;br /&gt;
Here,i had presented a stored procedure which saves a sales order information into Order Header and Order Detail tables. If you have ever worked on an order processing application, you would realize that saving a modified sales order is little tricky. There may be new rows added to the sales order. There may be rows which are updated and there may be rows that should be deleted. If you have the freedom to delete all the rows from order details table and re-insert everything, you are lucky. But many of the times you cannot simply delete the order details table because there may be additional information like Quantity-picked etc, which is updated from other parts of the application. In those scenarios, you need to perform a DELETE-UPDATE-INSERT operation to save the information correctly. The following is the pseudo code that I had used in my stored procedure.&lt;br /&gt;
&lt;br /&gt;
1 /*&lt;br /&gt;
&lt;br /&gt;
2     Pseudo code used for saving sales order information with SQL Server 2005&lt;br /&gt;
&lt;br /&gt;
3 &lt;br /&gt;
&lt;br /&gt;
4     -- save order header information&lt;br /&gt;
&lt;br /&gt;
5     If OrderNumber found in OrderHeader&lt;br /&gt;
&lt;br /&gt;
6         Update the information&lt;br /&gt;
&lt;br /&gt;
7     Else&lt;br /&gt;
&lt;br /&gt;
8         Insert the information&lt;br /&gt;
&lt;br /&gt;
9     end&lt;br /&gt;
&lt;br /&gt;
10 &lt;br /&gt;
&lt;br /&gt;
11     -- save order detail information&lt;br /&gt;
&lt;br /&gt;
12     Delete from Order detail table all items not in the order info&lt;br /&gt;
&lt;br /&gt;
13     Update Order detail for all items present in the order info&lt;br /&gt;
&lt;br /&gt;
14     Insert into order details all new items in the order info&lt;br /&gt;
&lt;br /&gt;
15 */&lt;br /&gt;
&lt;br /&gt;
SQL Server 2008 introduces MERGE, a new keyword which performs INSERT, UPDATE and DELETE operations at one go. With SQL Server 2008, you can perform the above operation as simple as the following pseudo code.&lt;br /&gt;
&lt;br /&gt;
1 /*&lt;br /&gt;
&lt;br /&gt;
2     Pseudo code for saving the same order with the MERGE statement of SQL Server 2008&lt;br /&gt;
&lt;br /&gt;
3 &lt;br /&gt;
&lt;br /&gt;
4     -- save order header information&lt;br /&gt;
&lt;br /&gt;
5     MERGE order info to Order Header table&lt;br /&gt;
&lt;br /&gt;
6 &lt;br /&gt;
&lt;br /&gt;
7     -- save order detail information&lt;br /&gt;
&lt;br /&gt;
8     MERGE order info to order detail table&lt;br /&gt;
&lt;br /&gt;
9 */&lt;br /&gt;
&lt;br /&gt;
No, I did not miss anything. You can write the code in just 2 lines. The rest of this article presents a SQL Server 2008 stored procedure which demonstrates this.&lt;br /&gt;
The Data&lt;br /&gt;
&lt;br /&gt;
Here is the structure of the order data that we have. This XML has the order header and detail information. Our stored procedure needs to store the information in both tables. Some rows need to be updated, some inserted and some deleted.&lt;br /&gt;
&lt;br /&gt;
We need two tables to store the order information. Here is the script to create the tables.&lt;br /&gt;
&lt;br /&gt;
1 CREATE TABLE [dbo].[OrderHeader](&lt;br /&gt;
&lt;br /&gt;
2     [OrderNumber] [varchar](20) NULL,&lt;br /&gt;
&lt;br /&gt;
3     [CustomerNumber] [varchar](20) NULL,&lt;br /&gt;
&lt;br /&gt;
4     [OrderDate] [datetime] NULL&lt;br /&gt;
&lt;br /&gt;
5 ) ON [PRIMARY]&lt;br /&gt;
&lt;br /&gt;
6 &lt;br /&gt;
&lt;br /&gt;
7 GO&lt;br /&gt;
&lt;br /&gt;
8 &lt;br /&gt;
&lt;br /&gt;
9 CREATE TABLE [dbo].[OrderDetails](&lt;br /&gt;
&lt;br /&gt;
10     [OrderNumber] [varchar](20) NULL,&lt;br /&gt;
&lt;br /&gt;
11     [ItemNumber] [varchar](20) NULL,&lt;br /&gt;
&lt;br /&gt;
12     [Qty] [int] NULL,&lt;br /&gt;
&lt;br /&gt;
13     [Rate] [money] NULL&lt;br /&gt;
&lt;br /&gt;
14 ) ON [PRIMARY]&lt;br /&gt;
&lt;br /&gt;
15 &lt;br /&gt;
&lt;br /&gt;
16 GO&lt;br /&gt;
Enter the Dragon&lt;br /&gt;
&lt;br /&gt;
Let us see the Stored Procedure which uses the MERGE keyword.&lt;br /&gt;
&lt;br /&gt;
1 CREATE PROCEDURE [dbo].[MergeSalesOrder]&lt;br /&gt;
&lt;br /&gt;
2 (&lt;br /&gt;
&lt;br /&gt;
3     @OrderInfo XML&lt;br /&gt;
&lt;br /&gt;
4 )&lt;br /&gt;
&lt;br /&gt;
5 AS&lt;br /&gt;
&lt;br /&gt;
6 &lt;br /&gt;
&lt;br /&gt;
7 SET NOCOUNT ON&lt;br /&gt;
&lt;br /&gt;
8 &lt;br /&gt;
&lt;br /&gt;
9 /*&lt;br /&gt;
&lt;br /&gt;
10 I am not using a TRY..CATCH or BEGIN TRAN to simplify the code.&lt;br /&gt;
&lt;br /&gt;
11 */&lt;br /&gt;
&lt;br /&gt;
12 &lt;br /&gt;
&lt;br /&gt;
13 /*&lt;br /&gt;
&lt;br /&gt;
14 Code to save order header. I am creating a CTE over the XML data to simplify&lt;br /&gt;
&lt;br /&gt;
15 the code.&lt;br /&gt;
&lt;br /&gt;
16 */&lt;br /&gt;
&lt;br /&gt;
17 &lt;br /&gt;
&lt;br /&gt;
18 ;WITH OrderInfo AS (&lt;br /&gt;
&lt;br /&gt;
19     SELECT&lt;br /&gt;
&lt;br /&gt;
20         x.h.value('@OrderNumber', 'VARCHAR(20)') AS OrderNumber,&lt;br /&gt;
&lt;br /&gt;
21         x.h.value('@CustomerNumber', 'VARCHAR(20)') AS CustomerNumber,&lt;br /&gt;
&lt;br /&gt;
22         x.h.value('@OrderDate', 'VARCHAR(20)') AS OrderDate&lt;br /&gt;
&lt;br /&gt;
23     FROM @OrderInfo.nodes('/OrderInfo/OrderHeader') AS x(h)&lt;br /&gt;
&lt;br /&gt;
24 )&lt;br /&gt;
&lt;br /&gt;
25 MERGE OrderHeader AS h&lt;br /&gt;
&lt;br /&gt;
26 USING OrderInfo AS o&lt;br /&gt;
&lt;br /&gt;
27 ON (h.OrderNumber = o.OrderNumber)&lt;br /&gt;
&lt;br /&gt;
28 WHEN MATCHED THEN&lt;br /&gt;
&lt;br /&gt;
29     UPDATE SET h.CustomerNumber = o.CustomerNumber, h.OrderDate = o.OrderDate&lt;br /&gt;
&lt;br /&gt;
30 WHEN NOT MATCHED THEN&lt;br /&gt;
&lt;br /&gt;
31     INSERT (OrderNumber, CustomerNumber, OrderDate)&lt;br /&gt;
&lt;br /&gt;
32     VALUES (o.OrderNumber, o.CustomerNumber, o.OrderDate)&lt;br /&gt;
&lt;br /&gt;
33 ;&lt;br /&gt;
&lt;br /&gt;
34 &lt;br /&gt;
&lt;br /&gt;
35 /*&lt;br /&gt;
&lt;br /&gt;
36 Save Order Detail Information&lt;br /&gt;
&lt;br /&gt;
37 */&lt;br /&gt;
&lt;br /&gt;
38 &lt;br /&gt;
&lt;br /&gt;
39 ;WITH OrderInfo AS (&lt;br /&gt;
&lt;br /&gt;
40     SELECT&lt;br /&gt;
&lt;br /&gt;
41         x.h.value('(../../OrderHeader/@OrderNumber)[1]', 'VARCHAR(20)') AS OrderNumber,&lt;br /&gt;
&lt;br /&gt;
42         x.h.value('@ItemNumber', 'VARCHAR(20)') AS ItemNumber,&lt;br /&gt;
&lt;br /&gt;
43         x.h.value('@Qty', 'INT') AS Qty,&lt;br /&gt;
&lt;br /&gt;
44         x.h.value('@Rate', 'MONEY') AS Rate&lt;br /&gt;
&lt;br /&gt;
45     FROM @OrderInfo.nodes('/OrderInfo/ItemInfo/Item') AS x(h)&lt;br /&gt;
&lt;br /&gt;
46 )&lt;br /&gt;
&lt;br /&gt;
47 MERGE OrderDetails AS d&lt;br /&gt;
&lt;br /&gt;
48 USING OrderInfo AS o&lt;br /&gt;
&lt;br /&gt;
49 ON (d.OrderNumber = o.OrderNumber AND d.ItemNumber = o.ItemNumber)&lt;br /&gt;
&lt;br /&gt;
50 WHEN MATCHED THEN&lt;br /&gt;
&lt;br /&gt;
51     UPDATE SET&lt;br /&gt;
&lt;br /&gt;
52         d.ItemNumber = o.ItemNumber,&lt;br /&gt;
&lt;br /&gt;
53         d.Qty = o.Qty,&lt;br /&gt;
&lt;br /&gt;
54         d.Rate = o.Rate&lt;br /&gt;
&lt;br /&gt;
55 WHEN NOT MATCHED THEN&lt;br /&gt;
&lt;br /&gt;
56     INSERT (OrderNumber, ItemNumber, Qty, Rate)&lt;br /&gt;
&lt;br /&gt;
57     VALUES (o.OrderNumber, o.ItemNumber, o.Qty, o.Rate)&lt;br /&gt;
&lt;br /&gt;
58 WHEN SOURCE NOT MATCHED THEN&lt;br /&gt;
&lt;br /&gt;
59     DELETE&lt;br /&gt;
&lt;br /&gt;
60 ;&lt;br /&gt;
&lt;br /&gt;
61 &lt;br /&gt;
&lt;br /&gt;
62 /*&lt;br /&gt;
&lt;br /&gt;
63 Points to note:&lt;br /&gt;
&lt;br /&gt;
64 1. MERGE statement should be terminated with a semi colon&lt;br /&gt;
&lt;br /&gt;
65 2. The JOIN (USING...ON) should not result in duplicate records.&lt;br /&gt;
&lt;br /&gt;
66 3. When the records in the SOURCE and TARGET matches, MATCHED becomes true&lt;br /&gt;
&lt;br /&gt;
67 4. When the record is not in the TARGET, NOT MATCHED becomes true&lt;br /&gt;
&lt;br /&gt;
68 5. When the record is not in the SOURCE, SOURCE NOT MATCHED becomes true.&lt;br /&gt;
&lt;br /&gt;
69 */&lt;br /&gt;
Execute the code&lt;br /&gt;
&lt;br /&gt;
It is time to execute the code. Use the following code to execute the stored procedure.&lt;br /&gt;
&lt;br /&gt;
1 EXECUTE [MergeSalesOrder] '&lt;br /&gt;
&lt;br /&gt;
15 Let us check the results&lt;br /&gt;
&lt;br /&gt;
16 */&lt;br /&gt;
&lt;br /&gt;
17 SELECT * FROM OrderHeader&lt;br /&gt;
&lt;br /&gt;
18 SELECT * FROM OrderDetails&lt;br /&gt;
&lt;br /&gt;
19 &lt;br /&gt;
&lt;br /&gt;
20 /*&lt;br /&gt;
&lt;br /&gt;
21 OrderNumber         CustomerNumber       OrderDate&lt;br /&gt;
&lt;br /&gt;
22 -------------------- -------------------- -----------------------&lt;br /&gt;
&lt;br /&gt;
23 20070101             J0001                2007-07-08 00:00:00.000&lt;br /&gt;
&lt;br /&gt;
24 &lt;br /&gt;
&lt;br /&gt;
25 (1 row(s) affected)&lt;br /&gt;
&lt;br /&gt;
26 &lt;br /&gt;
&lt;br /&gt;
27 OrderNumber         ItemNumber           Qty         Rate&lt;br /&gt;
&lt;br /&gt;
28 -------------------- -------------------- ----------- ---------------------&lt;br /&gt;
&lt;br /&gt;
29 20070101             A001                 10         100.00&lt;br /&gt;
&lt;br /&gt;
30 20070101             A002                 11         200.00&lt;br /&gt;
&lt;br /&gt;
31 20070101             A003                 12         300.00&lt;br /&gt;
&lt;br /&gt;
32 20070101             A004                 13         400.00&lt;br /&gt;
&lt;br /&gt;
33 20070101             A005                 14         500.00&lt;br /&gt;
&lt;br /&gt;
34 &lt;br /&gt;
&lt;br /&gt;
35 (5 row(s) affected)&lt;br /&gt;
&lt;br /&gt;
36 */&lt;br /&gt;
&lt;br /&gt;
The above code shows that the order is saved correctly. Now lets us modify the order info. Let us delete a row, add a new row and modify an existing row. Here is the code. Note that Item A005 is deleted. Item A006 is added and item A001 is modified. Let us execute the code and see the results.&lt;br /&gt;
&lt;br /&gt;
1 EXECUTE [MergeSalesOrder] '&lt;br /&gt;
&lt;br /&gt;
2 &lt;orderinfo _moz-userdefined=""&gt;&lt;br /&gt;
&lt;br /&gt;
3   &lt;orderheader _moz-userdefined="" customernumber="J0001" orderdate="2007-07-08" ordernumber="20070101"&gt;&lt;br /&gt;
&lt;br /&gt;
4   &lt;iteminfo _moz-userdefined=""&gt;&lt;br /&gt;
&lt;br /&gt;
5     &lt;item _moz-userdefined="" itemnumber="A001" qty="15" rate="150"&gt;&lt;br /&gt;
&lt;br /&gt;
6     &lt;item _moz-userdefined="" itemnumber="A002" qty="11" rate="200"&gt;&lt;br /&gt;
&lt;br /&gt;
7     &lt;item _moz-userdefined="" itemnumber="A003" qty="12" rate="300"&gt;&lt;br /&gt;
&lt;br /&gt;
8     &lt;item _moz-userdefined="" itemnumber="A004" qty="13" rate="400"&gt;&lt;br /&gt;
&lt;br /&gt;
9     &lt;item _moz-userdefined="" itemnumber="A006" qty="16" rate="600"&gt;&lt;br /&gt;
&lt;br /&gt;
10   &lt;/item&gt;&lt;br /&gt;
&lt;br /&gt;
11 &lt;/item&gt;&lt;br /&gt;
&lt;br /&gt;
12 '&lt;br /&gt;
&lt;br /&gt;
13 &lt;br /&gt;
&lt;br /&gt;
14 SELECT * FROM OrderHeader&lt;br /&gt;
&lt;br /&gt;
15 SELECT * FROM OrderDetails&lt;br /&gt;
&lt;br /&gt;
16 &lt;br /&gt;
&lt;br /&gt;
17 /*&lt;br /&gt;
&lt;br /&gt;
18 OUTPUT:&lt;br /&gt;
&lt;br /&gt;
19 &lt;br /&gt;
&lt;br /&gt;
20 OrderNumber         CustomerNumber       OrderDate&lt;br /&gt;
&lt;br /&gt;
21 -------------------- -------------------- -----------------------&lt;br /&gt;
&lt;br /&gt;
22 20070101             J0001                2007-07-08 00:00:00.000&lt;br /&gt;
&lt;br /&gt;
23 &lt;br /&gt;
&lt;br /&gt;
24 (1 row(s) affected)&lt;br /&gt;
&lt;br /&gt;
25 &lt;br /&gt;
&lt;br /&gt;
26 OrderNumber         ItemNumber           Qty         Rate&lt;br /&gt;
&lt;br /&gt;
27 -------------------- -------------------- ----------- ---------------------&lt;br /&gt;
&lt;br /&gt;
28 20070101             A001                 15         150.00&lt;br /&gt;
&lt;br /&gt;
29 20070101             A002                 11         200.00&lt;br /&gt;
&lt;br /&gt;
30 20070101             A003                 12         300.00&lt;br /&gt;
&lt;br /&gt;
31 20070101             A004                 13         400.00&lt;br /&gt;
&lt;br /&gt;
32 20070101             A006                 16         600.00&lt;br /&gt;
&lt;br /&gt;
33 &lt;br /&gt;
&lt;br /&gt;
34 (5 row(s) affected)&lt;br /&gt;
&lt;br /&gt;
35 &lt;br /&gt;
&lt;br /&gt;
36 */&lt;br /&gt;
Conclusions&lt;br /&gt;
&lt;br /&gt;
I found the MERGE keyword very powerful and friendly. It reduces the complexity of the code and provides a simple interface to perform a complex operation. I like it and I suppose many of you around there would like it too.&lt;br /&gt;
&lt;/item&gt;&lt;/item&gt;&lt;/item&gt;&lt;/iteminfo&gt;&lt;/orderheader&gt;&lt;/orderinfo&gt;&lt;/item&gt;&lt;/item&gt;&lt;/item&gt;&lt;/iteminfo&gt;&lt;/orderheader&gt;&lt;/orderinfo&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-5804427312965220579?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/bIBaM2OTqPU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/5804427312965220579/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2009/12/sql-server-2008-power-of-merge.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/5804427312965220579?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/5804427312965220579?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/bIBaM2OTqPU/sql-server-2008-power-of-merge.html" title="SQL Server 2008 - The Power of Merge" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2009/12/sql-server-2008-power-of-merge.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0UNRHY4fCp7ImA9WxBTEUU.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-1789491050525420489</id><published>2009-12-07T02:21:00.000-08:00</published><updated>2009-12-07T02:21:35.834-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-07T02:21:35.834-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="smalldatetime" /><category scheme="http://www.blogger.com/atom/ns#" term="UDF For DateTime" /><category scheme="http://www.blogger.com/atom/ns#" term="Date Time DataType" /><category scheme="http://www.blogger.com/atom/ns#" term="examples of working with datetime" /><category scheme="http://www.blogger.com/atom/ns#" term="Datetime" /><category scheme="http://www.blogger.com/atom/ns#" term="Working with Datetime" /><title>Working with Datetime</title><content type="html">Working with Datetime&lt;br /&gt;
By Leo Peysakhovich, 2007/09/21 (first published: 2004/12/22) &lt;br /&gt;
Total article views: 28489 | Views in the last 30 days: 13 &lt;br /&gt;
  Rate this |   Join the discussion |   Briefcase |   Print &lt;br /&gt;
Some handy examples of working with datetime &lt;br /&gt;
&lt;br /&gt;
Date / Time data types are probably amongst the most used data ones. Based on the Microsoft definition, date and time data types are for representing date and time of day. Values with the datetime data type are stored internally by Microsoft SQL Server as two 4-byte integers. The first 4 bytes store the number of days before or after the base date, January 1, 1900. The base date is the system reference date. Values for datetime earlier than January 1, 1753, are not permitted. The other 4 bytes store the time of day represented as the number of milliseconds after midnight. &lt;br /&gt;
&lt;br /&gt;
The smalldatetime data type stores dates and times of day with less precision than datetime. SQL Server stores smalldatetime values as two 2-byte integers. The first 2 bytes store the number of days after January 1, 1900. The other 2 bytes store the number of minutes since midnight. Dates range from January 1, 1900, through June 6, 2079, with accuracy to the minute. &lt;br /&gt;
 &lt;br /&gt;
 Let’s consider a case when we need to format values stored in datetime columns . In many cases (especially for the data transfer processes) the user will specify the output format. For example, our users prefer dates to be displayed as 2 digits day, 2 digits month, i.e. ‘01/09/2004’. Usually after the conversion this date displayed as ‘1/9/2004’.As you can see the difference is to be able not suppress leading zeroes in a given field. &lt;br /&gt;
&lt;br /&gt;
A simple trick to achieve this effect is to make your output independent from length of the actual value and to always return the necessary number of characters. &lt;br /&gt;
&lt;br /&gt;
declare @cust table (customer_id int, customer_nm varchar(50))&lt;br /&gt;
&lt;br /&gt;
insert into @cust (customer_id,customer_nm) values (11111,'TestNM 1')&lt;br /&gt;
insert into @cust (customer_id,customer_nm) values (6511111,'TestNM 1')&lt;br /&gt;
insert into @cust (customer_id,customer_nm) values (92311111,'TestNM 1')&lt;br /&gt;
&lt;br /&gt;
select &lt;br /&gt;
   Right('0000000000' + Cast(customer_id as varchar),10) as customer_id &lt;br /&gt;
   , customer_nm&lt;br /&gt;
  from @cust&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
customer_id customer_nm&lt;br /&gt;
----------- -------------------------------------------------- &lt;br /&gt;
0000011111  TestNM 1&lt;br /&gt;
0006511111  TestNM 1&lt;br /&gt;
0092311111  TestNM 1&lt;br /&gt;
The same trick can be used for the datetime data &lt;br /&gt;
&lt;br /&gt;
select right( '00' + Cast(datepart(dd, getdate()) as varchar), 2)&lt;br /&gt;
&lt;br /&gt;
select right( ''00' + Cast(datepart(mm, getdate()) as varchar), 2)&lt;br /&gt;
Here are some examples of using datetime data type. A transaction/modification very often requires some unique identifier. In light-load transactional systems (1-2 transactions per second) you can produce a unique id based on the timing characteristics of the transaction itself.. I use this method for years and have no problems. Theoretically it is possible that two modifications will get the same id, but the chance of it happening is not significant and can be mitigated with appropriate exception handling. &lt;br /&gt;
&lt;br /&gt;
CREATE FUNCTION dbo.UDF_ID (@dt datetime) &lt;br /&gt;
  Returns bigint &lt;br /&gt;
as&lt;br /&gt;
Begin &lt;br /&gt;
&lt;br /&gt;
declare @modid bigint&lt;br /&gt;
&lt;br /&gt;
select @modid = cast(year(@dt) as char(4))+ right(('0' +&lt;br /&gt;
       cast(month(@dt) as varchar(3) )),2) +&lt;br /&gt;
       right(('0' + cast( DATEPART(dd,@dt) as varchar(3))),2) +&lt;br /&gt;
       right(('0' + cast( DATEPART(hh,@dt) as varchar(3))),2) +&lt;br /&gt;
       right(('0' + cast( DATEPART(mi,@dt) as varchar(3))),2) +&lt;br /&gt;
       right(('0' + cast( DATEPART(ss,@dt) as varchar(3))),2) +&lt;br /&gt;
       right(('00' + cast( DATEPART(ms,@dt) as varchar(6))),3)&lt;br /&gt;
&lt;br /&gt;
return @modid&lt;br /&gt;
&lt;br /&gt;
End&lt;br /&gt;
There are many clients in the company I am working for. And each client required different date format for the reports and data transfers. You can facilitate the presentation of date-time value in different formats by utilizing a user defined function accepting two parameters: date-time value itself and a format type variable.&lt;br /&gt;
Sometimes it is required to use a UTC date instead of your company’s customary format. The next code fragment shows a user defined function that converts the saved time into UTC time. &lt;br /&gt;
&lt;br /&gt;
CREATE FUNCTION dbo.UDF_UTCDATE (@dt datetime, @now datetime, @utcnow datetime) &lt;br /&gt;
  Returns datetime as&lt;br /&gt;
Begin &lt;br /&gt;
&lt;br /&gt;
declare @utcdate datetime&lt;br /&gt;
&lt;br /&gt;
select @utcdate = DATEADD(ss, DATEDIFF(ss, @now, @utcnow) , @dt )&lt;br /&gt;
&lt;br /&gt;
return @utcdate&lt;br /&gt;
End&lt;br /&gt;
There are three parameters to this function. The second and the third are always the same, and the first one is your datetime field or variable. The second one is always getdate(), and the third one is always getutcdate(). Parameter 2 and 3 are required to be able to bypass the user defined function restrictions stating that non-deterministic functions can’t be used inside of the user defined function. &lt;br /&gt;
&lt;br /&gt;
Select dbcentral.dbo.udf_utcdate(order_dt, getdate(), getutcdate() ), order_id&lt;br /&gt;
 From customer_orders&lt;br /&gt;
 Where customer_id = 10&lt;br /&gt;
Conclusion&lt;br /&gt;
There are many ways to use datetime data type. This article illustrated some of them without venturing into a discussion on their performance characteristics, ability to produce unique values or any other aspects, which may be subjects of other publications.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-1789491050525420489?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/IUb_AyJkQAM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/1789491050525420489/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2009/12/working-with-datetime.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/1789491050525420489?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/1789491050525420489?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/IUb_AyJkQAM/working-with-datetime.html" title="Working with Datetime" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2009/12/working-with-datetime.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0INSH89eyp7ImA9WxBTEUo.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-5092089943574766743</id><published>2009-12-07T01:53:00.000-08:00</published><updated>2009-12-07T01:53:19.163-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-07T01:53:19.163-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Indexed Columns" /><category scheme="http://www.blogger.com/atom/ns#" term="WHERE Clauses" /><category scheme="http://www.blogger.com/atom/ns#" term="Function in the WHERE clause" /><title>Avoid enclosing Indexed Columns in a Function in the WHERE clause</title><content type="html">When a function is wrapped around an indexed column in the WHERE clause it will usually prevent the proper usage of that index. This article gives an example and discusses what other options are available.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I still remember the first time I ever saw this truly crush a server.  I was doing performance tuning at a client site.  We still hadn't convinced their developers that the DBA's needed to review their T-SQL code before it went into production.  One morning we noticed performance problems on the server.  Disk reads would go way, way up.  SPIDs would begin to block.  Response time on their web site slowed to a crawl -- if it responded at all.&lt;br /&gt;
&lt;br /&gt;
After first insisting that no new database code was rolled out they finally admitted they'd changed one line of one WHERE clause in a single stored procedure.  That change brought the database server to a stop.  They had added a YEAR function around an indexed date column so that a report would only pull back a single year.  That changed the query plan enough to start generating table scans.&lt;br /&gt;
&lt;br /&gt;
A script to simulate this looks like this:&lt;br /&gt;
&lt;br /&gt;
USE AdventureWorks&lt;br /&gt;
GO&lt;br /&gt;
IF  EXISTS (SELECT * FROM sys.indexes &lt;br /&gt;
   WHERE object_id = OBJECT_ID(N'[Sales].[SalesOrderHeader]') &lt;br /&gt;
   AND name = N'IX_SalesOrderHeader_OrderDate')&lt;br /&gt;
 DROP INDEX [IX_SalesOrderHeader_OrderDate] ON [Sales].[SalesOrderHeader] &lt;br /&gt;
GO&lt;br /&gt;
CREATE INDEX IX_SalesOrderHeader_OrderDate ON Sales.SalesOrderHeader(OrderDate)&lt;br /&gt;
GO&lt;br /&gt;
&lt;br /&gt;
SELECT SalesOrderID,&lt;br /&gt;
 OrderDate&lt;br /&gt;
FROM Sales.SalesOrderHeader&lt;br /&gt;
WHERE YEAR(OrderDate) = 2003&lt;br /&gt;
AND MONTH(OrderDate) = 7&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
SELECT SalesOrderID,&lt;br /&gt;
 OrderDate&lt;br /&gt;
FROM Sales.SalesOrderHeader&lt;br /&gt;
WHERE OrderDate BETWEEN '7/1/2003' AND '7/31/2003'&lt;br /&gt;
GO&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
IF  EXISTS (SELECT * FROM sys.indexes &lt;br /&gt;
   WHERE object_id = OBJECT_ID(N'[Sales].[SalesOrderHeader]') &lt;br /&gt;
   AND name = N'IX_SalesOrderHeader_OrderDate')&lt;br /&gt;
 DROP INDEX [IX_SalesOrderHeader_OrderDate] ON [Sales].[SalesOrderHeader] &lt;br /&gt;
GOThis script creates an index on OrderDate.  In my sample queries I'm only pulling back a single month.  The first query has a cost of 0.080 and does a scan of the index.  The second query has a cost of 0.004 and does a seek on the index.  Yes, the costs are very low because I don't have a large set of data.  The first query still has a cost twenty times higher than the second.&lt;br /&gt;
&lt;br /&gt;
When you wrap a function around an indexed column SQL Server must compute the value of the function for each row in the table.  When you just compare the indexed column to a scalar value or the result of a function then SQL Server can use that value to seek into the index.  The only time you can use a function around an indexed column is if you use an indexed computed column that matches the function you're using.&lt;br /&gt;
&lt;br /&gt;
This type of coding is mostly used for date fields when computing ranges such as querying for the previous week.  Hopefully this little tip will save you from making the same mistake those developers did.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-5092089943574766743?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/s3NTwlDc5_I" height="1" width="1"/&gt;</content><link rel="related" href="http://www.sqlteam.com/article/avoid-enclosing-indexed-columns-in-a-function-in-the-where-clause" title="Avoid enclosing Indexed Columns in a Function in the WHERE clause" /><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/5092089943574766743/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2009/12/avoid-enclosing-indexed-columns-in.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/5092089943574766743?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/5092089943574766743?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/s3NTwlDc5_I/avoid-enclosing-indexed-columns-in.html" title="Avoid enclosing Indexed Columns in a Function in the WHERE clause" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2009/12/avoid-enclosing-indexed-columns-in.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkQHSXY5cCp7ImA9WxBTEUo.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-784991665253400834</id><published>2009-12-07T01:30:00.001-08:00</published><updated>2009-12-07T01:32:18.828-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-07T01:32:18.828-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="CASE statements" /><category scheme="http://www.blogger.com/atom/ns#" term="conditional WHERE clauses" /><category scheme="http://www.blogger.com/atom/ns#" term="WHERE Clauses" /><category scheme="http://www.blogger.com/atom/ns#" term="Conditional Statements" /><title>Conditional Statements in WHERE Clauses</title><content type="html">Ever had a query where you wished you could be able to specify the operator (equal, not equal, greater than, etc.) for each column contained in your WHERE clause, without having to use ugly string concatenations and the infamous EXEC keyword? Here we'll see an example of how this can be achieved with the use of a plain SQL query together with some CASE statements. &lt;br /&gt;
&lt;br /&gt;
The Scenario&lt;br /&gt;
We will use the Customers table from the sample Northwind database for our example. Let's suppose you want to query the Customers table for the following: &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
All records that contain the word "the" in the company name &lt;br /&gt;
All records for companies located in Germany, excluding companies starting with the letter "A" &lt;br /&gt;
&lt;br /&gt;
Normally, you could create a dynamic statement consisting of multiple IF statements and strings to be concatenated to a final variable which should be executed with the use of the EXEC keyword. But in some cases this is not desirable, and you would like something more versatile (although maybe slightly less efficient). &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The code&lt;br /&gt;
&lt;br /&gt;
Let's see how we can form a query to serve the above specs. Take a look at this code. Be sure to read the comments included in the code for an explanation of what is happening. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Declare some local variables. Actually, we are creating a pair of variables&lt;br /&gt;
-- for each column included in our WHERE clause.&lt;br /&gt;
-- The first variable represents the value we are filtering and the second&lt;br /&gt;
-- represents the "operator" for the filter.&lt;br /&gt;
&lt;br /&gt;
declare @companyName varchar(255)&lt;br /&gt;
declare @companyNameOp varchar(2)&lt;br /&gt;
declare @country varchar(255)&lt;br /&gt;
declare @countryOp varchar(2)&lt;br /&gt;
&lt;br /&gt;
-- Let's set some sample values now. The values you see here represent the second&lt;br /&gt;
-- of the two scenarios described above, i.e. all records for companies located in Germany,&lt;br /&gt;
-- excluding companies starting with the letter A&lt;br /&gt;
&lt;br /&gt;
-- Operators are defined here with arbitrary, two-letter values. &lt;br /&gt;
-- Of course you could define your own set of operators, with different&lt;br /&gt;
-- naming conventions. For our example, here's the meaning of each possible&lt;br /&gt;
-- value:&lt;br /&gt;
&lt;br /&gt;
-- ne = not equal&lt;br /&gt;
-- eq = equal&lt;br /&gt;
-- bg = begins with&lt;br /&gt;
-- ed = ends with&lt;br /&gt;
-- ct = contains&lt;br /&gt;
&lt;br /&gt;
-- For our example, we are using only varchar fields in our WHERE clause.&lt;br /&gt;
-- It is very easy, though, to define operators for other data types as well.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
set @companyname = 'A%'&lt;br /&gt;
set @companynameOp = 'ne'&lt;br /&gt;
set @country = 'Germany'&lt;br /&gt;
set @countryOp = 'eq'&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Ok, now let's form our query. &lt;br /&gt;
&lt;br /&gt;
select&lt;br /&gt;
customerid, companyname, country&lt;br /&gt;
from &lt;br /&gt;
customers&lt;br /&gt;
where &lt;br /&gt;
case @companyNameOp&lt;br /&gt;
when '' then 1 -- Operator not defined, get everything&lt;br /&gt;
when 'eq' then -- Operator is "equals"&lt;br /&gt;
case when companyname like @companyName then 1 else 0 end&lt;br /&gt;
when 'bg' then -- Operator is "begins with"&lt;br /&gt;
case when companyname like @companyName +'%' then 1 else 0 end&lt;br /&gt;
when 'ed' then -- Operator is "ends with"&lt;br /&gt;
case when companyname like '%' + @companyName  then 1 else 0 end&lt;br /&gt;
when 'ct' then -- Operator is "contains"&lt;br /&gt;
case when companyname like '%' + @companyName  +'%' then&lt;br /&gt;
1 else 0 end&lt;br /&gt;
when 'ne' then -- Operator is "not equal"&lt;br /&gt;
case when companyname not like @companyName then 1 else 0 end end =1&lt;br /&gt;
&lt;br /&gt;
AND&lt;br /&gt;
&lt;br /&gt;
-- Same approach for the second field&lt;br /&gt;
&lt;br /&gt;
case @countryOp&lt;br /&gt;
when '' then 1 &lt;br /&gt;
when 'eq' then &lt;br /&gt;
case when country like @country then 1 else 0 end&lt;br /&gt;
when 'bg' then &lt;br /&gt;
case when country like @country +'%' then 1 else 0 end&lt;br /&gt;
when 'ed' then&lt;br /&gt;
case when country like '%' + @country  then 1 else 0 end&lt;br /&gt;
when 'ct' then&lt;br /&gt;
case when country like '%' + @country  +'%' then 1 else 0 end&lt;br /&gt;
when 'ne' then&lt;br /&gt;
case when country not like @country then 1 else 0 end&lt;br /&gt;
end =1&lt;br /&gt;
&lt;br /&gt;
Conclusion&lt;br /&gt;
The conditional WHERE clauses are based on the simple principle defined by the query "SELECT something FROM sometable WHERE 1=1" As you can see, all CASE statements evaluate to either 1 or 0, so the comparison with 1 can either be false or true for each row. &lt;br /&gt;
&lt;br /&gt;
Of course, you can define your own set of operators (like operators for numeric values) and you can extend your queries to include more fields. The query, as defined here, lets you also NOT define an operator, meaning that nothing will be filtered by the specific field connected to the operator. &lt;br /&gt;
&lt;br /&gt;
Please note that this article serves only as a starting point. You may need to put extra effort (and add extra functionality) in case something like this is required in a production environment, but in my personal opinion, this approach is particularly useful when dealing with customizable query wizards or similar stuff in applications, especially when some kind of custom reporting is involved. Such queries can be easily transformed to stored procedures with all parameters optional and, having some additional checks, return resultsets filtered only by the parameters (and operators) given each time.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-784991665253400834?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/ZGEgpW9dPJo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/784991665253400834/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2009/12/conditional-statements-in-where-clauses.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/784991665253400834?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/784991665253400834?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/ZGEgpW9dPJo/conditional-statements-in-where-clauses.html" title="Conditional Statements in WHERE Clauses" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2009/12/conditional-statements-in-where-clauses.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0MDRXg-fip7ImA9WxBTEUo.&quot;"><id>tag:blogger.com,1999:blog-4595124568707031723.post-3906893654132654439</id><published>2009-12-06T23:37:00.000-08:00</published><updated>2009-12-06T23:37:54.656-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-12-06T23:37:54.656-08:00</app:edited><title>NULL Versus NULL?</title><content type="html">No Two NULLs Are Created Equal...&lt;br /&gt;
If you recall from the original Four Rules article, the basis of ANSI SQL three-valued logic (3VL) is that NULL is not equal to anything else. It is not less than, greater than, or even unequal to anything else either. Because NULL is not an actual value, but rather a placeholder for an unknown value, all comparisons with NULL result in UNKNOWN. Even comparing a NULL to another NULL is just comparing two placeholders for unknown values, so the result again is UNKNOWN.&lt;br /&gt;
&lt;br /&gt;
Tip: In reference to NULL comparisons, be sure to keep Rule #3 in mind. Microsoft has deprecated SET ANSI_NULLS, and according to Books Online it will be removed in a future version of SQL Server. If you currently have code that relies on SET ANSI_NULLS OFF, it might be a good time to start considering what it will take to make that code ANSI SQL-92 NULL-compliant.  &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
We even generated some samples to demonstrate this. One of these samples is reproduced here in Listing 1.&lt;br /&gt;
&lt;br /&gt;
Listing 1. Demonstrating that NULL is not equal to NULL&lt;br /&gt;
&lt;br /&gt;
SET ANSI_NULLS ON&lt;br /&gt;
DECLARE @val CHAR(4)&lt;br /&gt;
SET @val = NULL&lt;br /&gt;
SET ANSI_NULLS ON&lt;br /&gt;
IF @val = NULL&lt;br /&gt;
     PRINT 'TRUE'&lt;br /&gt;
ELSE IF NOT(@val = NULL)&lt;br /&gt;
     PRINT 'FALSE'&lt;br /&gt;
ELSE&lt;br /&gt;
     PRINT 'UNKNOWN'&lt;br /&gt;
NULL: Confusing the Smartest People in the World Since (at least) 1986&lt;br /&gt;
If all this doesn't hit home immediately, don't take it too hard. Even Microsoft seems to have difficulty sorting through it. Point in fact: SQL Server 2005 Books Online (BOL) still has bad information concerning NULL comparisons. In fact, as of the time of this writing I counted no less than ten pages in BOL that stated the result of a comparison with NULL is either FALSE or NULL. Only two pages that I found (the pages describing "IS [NOT] NULL" and "SET ANSI_NULLS") actually got it right. Fortunately we know better: the result of comparing NULL with anything is UNKNOWN.&lt;br /&gt;
&lt;br /&gt;
So why all the confusion? Most likely it's because in queries only rows for which the WHERE clause condition evaluates to TRUE are returned. Rows that evaluate to FALSE or UNKNOWN are not returned. For some folks this might seem to indicate FALSE and UNKNOWN are equivalent. They're not, as we'll see in Listings 2 and 3.&lt;br /&gt;
&lt;br /&gt;
Listing 2. Sample SELECT with NULL comparison in the WHERE clause&lt;br /&gt;
&lt;br /&gt;
SELECT TOP 100 *&lt;br /&gt;
FROM sys.syscomments&lt;br /&gt;
WHERE id = NULL&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The result above of course returns no rows. According to Books Online this is because "id = NULL" evaluates to FALSE. If this is true, however, Listing 3 below should return all rows.&lt;br /&gt;
&lt;br /&gt;
Listing 3. The "opposite" of Listing 2&lt;br /&gt;
&lt;br /&gt;
SELECT TOP 100 *&lt;br /&gt;
FROM sys.syscomments&lt;br /&gt;
WHERE NOT(id = NULL)&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
If "id = NULL" really evaluates to FALSE for every row, then "NOT(id = NULL)" should evaluate to TRUE for every row. Of course it doesn't, and again no rows are returned. And we already know the reason: it's because "id = NULL" evaluates to UNKNOWN, and "NOT(id = NULL)" also evaluates to UNKNOWN.&lt;br /&gt;
&lt;br /&gt;
Microsoft has already been notified of this problem in BOL and hopefully it will be fixed soon.&lt;br /&gt;
&lt;br /&gt;
...All NULLs Are Created Not Distinct&lt;br /&gt;
So now we've firmly established that comparisons with NULL never evaluate to TRUE or FALSE, that NULL is never equal to NULL, and that NULL comparisons always result in UNKNOWN... Now it's time to list the exceptions. (You didn't think it would be that simple did you?)&lt;br /&gt;
&lt;br /&gt;
I really wanted to call this section All NULLs Are Created Equal, but that just happens to be wrong. In order to simulate NULL equality, and to keep from contradicting themselves in the process, the ANSI SQL-92 standard decreed that two NULL values should be considered "not distinct". The definition of not distinct in the ANSI standard includes any two values that return TRUE for an equality test (e.g., 3 = 3, 4 = 4, etc.), or any two NULLs.&lt;br /&gt;
&lt;br /&gt;
This simulated NULL equality is probably most used in the GROUP BY clause, which groups all NULL values into a single partition. SQL-92 defines a partition as a grouping of not distinct values. Listing 4 below shows GROUP BY handling of NULL.&lt;br /&gt;
&lt;br /&gt;
Listing 4. GROUP BY and NULL&lt;br /&gt;
&lt;br /&gt;
CREATE TABLE #test (val INT);&lt;br /&gt;
&lt;br /&gt;
INSERT INTO #test (val) VALUES (NULL);&lt;br /&gt;
INSERT INTO #test (val) VALUES (NULL);&lt;br /&gt;
INSERT INTO #test (val) VALUES (NULL);&lt;br /&gt;
INSERT INTO #test (val) VALUES (NULL);&lt;br /&gt;
INSERT INTO #test (val) VALUES (1);&lt;br /&gt;
INSERT INTO #test (val) VALUES (2);&lt;br /&gt;
INSERT INTO #test (val) VALUES (3);&lt;br /&gt;
INSERT INTO #test (val) VALUES (3);&lt;br /&gt;
&lt;br /&gt;
SELECT COUNT(*) AS num, val&lt;br /&gt;
FROM #test&lt;br /&gt;
GROUP BY val;&lt;br /&gt;
&lt;br /&gt;
DROP TABLE #test;&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Figure 2 shows the result. &lt;br /&gt;
Figure 2. Result of GROUP BY with NULLs&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Notice the NULL values are all treated as not distinct by GROUP BY, and are all grouped together. Unique constraints also use the ANSI definition of not distinct as opposed to equal since you can only insert one NULL in a column with a unique constraint. Consider Listing 5 which shows this.&lt;br /&gt;
&lt;br /&gt;
Listing 5. Unique Constraint and NULL&lt;br /&gt;
&lt;br /&gt;
CREATE TABLE #test (val INT CONSTRAINT unq_val UNIQUE);&lt;br /&gt;
&lt;br /&gt;
INSERT INTO #test (val) VALUES (NULL);&lt;br /&gt;
INSERT INTO #test (val) VALUES (NULL);&lt;br /&gt;
&lt;br /&gt;
DROP TABLE #test;&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This example throws an exception when it tries to insert the second NULL in the val column:&lt;br /&gt;
&lt;br /&gt;
(1 row(s) affected)&lt;br /&gt;
Msg 2627, Level 14, State 1, Line 4&lt;br /&gt;
Violation of UNIQUE KEY constraint 'unq_val'. Cannot insert duplicate key in&lt;br /&gt;
object 'dbo.#test'.&lt;br /&gt;
The statement has been terminated.&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Other statements and operators that use the concept of not distinct to simulate NULL equality include:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PARTITION BY clause of OVER() &lt;br /&gt;
UNION operator &lt;br /&gt;
DISTINCT keyword &lt;br /&gt;
INTERSECT operator &lt;br /&gt;
EXCEPT operator &lt;br /&gt;
&lt;br /&gt;
NULLs Flock Together&lt;br /&gt;
The ORDER BY clause in SELECT queries places all NULL values together when it orders your results. SQL Server treats NULLs as the "lowest possible values" in your results. What this means is NULL will always come before your non-NULL results when you sort in ascending order, and after your non-NULL results when you sort in descending order. Listing 6 shows ORDER BY and NULL in action.&lt;br /&gt;
&lt;br /&gt;
Listing 6. ORDER BY and NULL&lt;br /&gt;
&lt;br /&gt;
CREATE TABLE #test (val INT);&lt;br /&gt;
&lt;br /&gt;
INSERT INTO #test (val) VALUES (NULL);&lt;br /&gt;
INSERT INTO #test (val) VALUES (NULL);&lt;br /&gt;
INSERT INTO #test (val) VALUES (1);&lt;br /&gt;
INSERT INTO #test (val) VALUES (2);&lt;br /&gt;
&lt;br /&gt;
SELECT val&lt;br /&gt;
FROM #test&lt;br /&gt;
ORDER BY val;&lt;br /&gt;
&lt;br /&gt;
DROP TABLE #test;&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The results are shown in Figure 3.&lt;br /&gt;
&lt;br /&gt;
Figure 3. Result of ORDER BY with NULL&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The same holds true for the ORDER BY clause of OVER, which is used to order your results when used with ranking functions like ROW_NUMBER and aggregate functions like SUM.&lt;br /&gt;
&lt;br /&gt;
And Now For Something Entirely Different&lt;br /&gt;
Now that we've established the "exceptions" for NULL comparisons, let's look at something entirely different. When a NULL value is inserted into a nullable column with a check constraint that doesn't check for IS NOT NULL, something strange seems to happen. Consider Listing 7.&lt;br /&gt;
&lt;br /&gt;
Listing 7. Check constraints and NULL&lt;br /&gt;
&lt;br /&gt;
CREATE TABLE #test (val INT CONSTRAINT ck_val CHECK(val &lt; 0 AND val = 0 AND val &gt; 0));&lt;br /&gt;
&lt;br /&gt;
INSERT INTO #test (val) VALUES (NULL);&lt;br /&gt;
INSERT INTO #test (val) VALUES (NULL);&lt;br /&gt;
&lt;br /&gt;
SELECT val&lt;br /&gt;
FROM #test&lt;br /&gt;
ORDER BY val;&lt;br /&gt;
&lt;br /&gt;
DROP TABLE #test;&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
In this example we've added a check constraint to the sample table that enforces the following rule:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The value inserted must be less than zero &lt;br /&gt;
*and* the value inserted must be equal to zero &lt;br /&gt;
*and* the value inserted must be greater than zero &lt;br /&gt;
&lt;br /&gt;
You and I know from 4th grade math (remember number lines?) that there is no value that can ever fulfill these requirements. No value can be less than zero, equal to zero, and greater than zero all at the same time. Also based on what we've already talked about, any comparisons with NULL result in UNKNOWN. You might expect an attempt to insert any value into the table would fail.&lt;br /&gt;
&lt;br /&gt;
However, check constraints operate under a different set of rules from the SELECT, INSERT, UPDATE, and DELETE DML statements. The DML statements, when combined with a WHERE clause, perform their action only on rows for which the WHERE clause condition evaluates to TRUE. The DML statements will exclude rows that evaluate to FALSE or UNKNOWN.&lt;br /&gt;
&lt;br /&gt;
Check constraints, on the other hand, cause INSERT and UPDATE statements to fail only if the check constraint condition evaluates to FALSE. This means that the checks will succeed if the condition evaluates to either UNKNOWN or TRUE.&lt;br /&gt;
&lt;br /&gt;
Of course you'd probably never create a check constraint as restrictive as the one in the example, and if you want to prevent NULLs from being inserted into a column, either declare the column NOT NULL or add "val IS NOT NULL" as a check constraint condition. Don't expect a check constraint that evaluates to UNKNOWN to cause an INSERT or UPDATE to fail.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4595124568707031723-3906893654132654439?l=piyushagarwal.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~4/nWlp6SwfZZ8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://piyushagarwal.blogspot.com/feeds/3906893654132654439/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://piyushagarwal.blogspot.com/2009/12/null-versus-null.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/3906893654132654439?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4595124568707031723/posts/default/3906893654132654439?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/SqlServerEncyclopedia-SqlServerTipsSugegstions-BuggfixingTroubleShooting/~3/nWlp6SwfZZ8/null-versus-null.html" title="NULL Versus NULL?" /><author><name>Piyush Agarwal</name><uri>http://www.blogger.com/profile/15839212212581256488</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="24" height="32" src="http://1.bp.blogspot.com/-4jtXu6Wq2SM/TmJ-2jmuN3I/AAAAAAAAAIE/-FmznC_RrSE/s220/My%2BPhoto_PiyushKumarAgarwal.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://piyushagarwal.blogspot.com/2009/12/null-versus-null.html</feedburner:origLink></entry></feed>

