<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://blogs.sqlservercentral.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Ken Kaufman</title><link>http://blogs.sqlservercentral.com/ken_kaufman/default.aspx</link><description /><dc:language>en</dc:language><generator>CommunityServer 2007.1 (Build: 20917.1142)</generator><item><title>SQL Server Time-Dates</title><link>http://blogs.sqlservercentral.com/ken_kaufman/archive/2008/06/23/sql-server-time-dates.aspx</link><pubDate>Tue, 24 Jun 2008 01:09:00 GMT</pubDate><guid isPermaLink="false">70975365-724d-4ce8-8d1c-45c963ab81ff:8907</guid><dc:creator>Ken Kaufman</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://blogs.sqlservercentral.com/ken_kaufman/rsscomments.aspx?PostID=8907</wfw:commentRss><comments>http://blogs.sqlservercentral.com/ken_kaufman/archive/2008/06/23/sql-server-time-dates.aspx#comments</comments><description>&lt;p&gt;&amp;nbsp;

&lt;/p&gt;&lt;p class="MsoNormal"&gt;The other day in our architecture group meeting the question
was raised on how SQL Server handled future dates in relation to lights savings
time.&lt;span&gt;&amp;nbsp; &lt;/span&gt;For example if a play was
scheduled in January for a date in July at 1:00 PM would reflect this time or
2:00 PM because of the jump in a hour for day light savings time.&lt;span&gt;&amp;nbsp; &lt;/span&gt;This is a common issue with many applications,
but not SQL Server.&lt;span&gt;&amp;nbsp; &lt;/span&gt;It all relates to
the time source the application is getting the time from.&lt;span&gt;&amp;nbsp; &lt;/span&gt;Applications use one of two sources the CPU
which works on UTC or it uses an OS api which reflects the time zone setup in
the OS.&lt;span&gt;&amp;nbsp; &lt;/span&gt;Of course you’re simply
transferring the burden from one program to the next (OS), but it has its
advantage to work off the OS time rather then the CPU, but it also has its disadvantages
as well.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&amp;nbsp;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;Before going into how SQL uses its source, you need to
understand how SQL maintains its date.&lt;span&gt;&amp;nbsp;
&lt;/span&gt;The “datetime” datatype is eight bytes, or 2 ints, divided between date
and time.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;The first int represents the date where the
base date is 1900 and increments are set at one day.&lt;span&gt;&amp;nbsp; &lt;/span&gt;The second int or four bytes is the time with
a base of 12:00 AM, increments are 1/300 of a second.&lt;span&gt;&amp;nbsp; &lt;/span&gt;If you want to see how this works run the
following queries:&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&amp;nbsp;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:green;"&gt;--Convert date to a binary&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:blue;"&gt;select&lt;/span&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt; &lt;span style="color:fuchsia;"&gt;convert&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:blue;"&gt;varbinary&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;100&lt;span style="color:gray;"&gt;),&lt;/span&gt; &lt;span style="color:fuchsia;"&gt;getdate&lt;/span&gt;&lt;span style="color:gray;"&gt;())&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:gray;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:green;"&gt;--Take the first 8 hex values and convert ensure the &amp;quot;0x&amp;quot; is
prefix&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:blue;"&gt;select&lt;/span&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt; &lt;span style="color:fuchsia;"&gt;convert&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:blue;"&gt;int&lt;/span&gt;&lt;span style="color:gray;"&gt;,&lt;/span&gt; 0x00009AC4&lt;span style="color:gray;"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:gray;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:green;"&gt;--Do a date diff with 1/1/1900 The dates should be the same&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:blue;"&gt;select&lt;/span&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt; &lt;span style="color:fuchsia;"&gt;datediff&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:fuchsia;"&gt;day&lt;/span&gt;&lt;span style="color:gray;"&gt;,&lt;/span&gt; &lt;span style="color:red;"&gt;&amp;#39;1/1/1900&amp;#39;&lt;/span&gt;&lt;span style="color:gray;"&gt;,&lt;/span&gt; &lt;span style="color:fuchsia;"&gt;getdate&lt;/span&gt;&lt;span style="color:gray;"&gt;())&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:gray;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span&gt;Now that we know how data is
stored there is the critical nature of what source does sql use.&lt;span&gt;&amp;nbsp; &lt;/span&gt;As I said earlier applications can use the
converted OS time or do the coversion itself against the cpu’s time that’s
using UTC.&lt;span&gt;&amp;nbsp; &lt;/span&gt;SQL uses both known as “High
Resolution Timer” and “Low Resolution Timer”.&lt;span&gt;&amp;nbsp;
&lt;/span&gt;The High resolution timer works off the cpu timer or UTC, which SQL
queries for internal processes such as workers, locks and data cleanup.&lt;span&gt;&amp;nbsp; &lt;/span&gt;The Low resolution timer calls the windows
api GetTickCount which returns the OS time.&lt;span&gt;&amp;nbsp;
&lt;/span&gt;This is used by external functions such as Getdate().&lt;span&gt;&amp;nbsp;&amp;nbsp; &lt;/span&gt;Since these external needs are based on the
OS time, there unaffected by changes in day light savings time, all time is still
based on delta of midnight as the OS represents the time zone.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span&gt;This type of tradeoff between
High and Low resolution seems to fit most needs, unless you get into time
comparisons, such as Dateadd or Datediff functions.&lt;span&gt;&amp;nbsp; &lt;/span&gt;These are unaware &lt;span&gt;&amp;nbsp;&lt;/span&gt;of day light savings time or UTC, there given
a value,&lt;span&gt;&amp;nbsp; &lt;/span&gt;and compute the deltas and assume
no day light time. &lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;As with SQL your
application will probably require different time values based on regional time
and UTC.&lt;span&gt;&amp;nbsp; &lt;/span&gt;SQL provides this functionality
in the form of the function getutcdate.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;img src="http://blogs.sqlservercentral.com/aggbug.aspx?PostID=8907" width="1" height="1"&gt;</description><category domain="http://blogs.sqlservercentral.com/ken_kaufman/archive/tags/MetaData/default.aspx">MetaData</category><category domain="http://blogs.sqlservercentral.com/ken_kaufman/archive/tags/SQL+Server+Administration/default.aspx">SQL Server Administration</category></item><item><title>Maintenance Plan Quirks</title><link>http://blogs.sqlservercentral.com/ken_kaufman/archive/2008/06/12/maintenance-plan-quirks.aspx</link><pubDate>Thu, 12 Jun 2008 14:21:00 GMT</pubDate><guid isPermaLink="false">70975365-724d-4ce8-8d1c-45c963ab81ff:8881</guid><dc:creator>Ken Kaufman</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://blogs.sqlservercentral.com/ken_kaufman/rsscomments.aspx?PostID=8881</wfw:commentRss><comments>http://blogs.sqlservercentral.com/ken_kaufman/archive/2008/06/12/maintenance-plan-quirks.aspx#comments</comments><description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;The move to maintenance plans based on SSIS was a very
impressive step.&amp;nbsp; As an administrator it
provides the ease and flexibility of creating and maintaining plans through an
object based interface.&amp;nbsp; There are
several differences in the Maintenance plans implementation of SSIS compared to
the full blown product, primarily in its extensibility, as it doesn&amp;#39;t expose
all the features.&amp;nbsp; However where it beats
SSIS is on the management of DBA specific tasks and ease of use.&amp;nbsp; Not to say SSIS is difficult to use, but
changing and troubleshooting can be cumbersome.&amp;nbsp;
If you need to change a SSIS package it requires opening up Visual
Studio, making the changes then saving to the appropriate location where a job
(Assuming your using jobs to schedule) expects it.&amp;nbsp; Under maintenance plans all setup and changes
occur within Management Studio.&amp;nbsp; If you&amp;#39;re
trying to determine what a co-worker setup it&amp;#39;s much easier then tracking down
the package, moving to a dev environment then reviewing. &lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;There are some quirks with SSIS which I&amp;#39;m not sure will be
addressed with 2008, predominately in the way the Maint jobs are tied to
Jobs.&amp;nbsp; If you don&amp;#39;t understand how this
works, these quirks could cause headaches.&amp;nbsp;
First unlike SSIS maintenance plans are tied to sql jobs, or to be more specific
sub plans.&amp;nbsp; Every Maint plan has at least
one sub-plan, which is represented as a job after it&amp;#39;s created.&amp;nbsp; If you try to delete the job, you&amp;#39;ll get a
foreign key violation, which ties the sysmaintplan_subplans
and the sysjobs table.&amp;nbsp; The main oddity
comes in the form of saving plans.&amp;nbsp; When
you create a sub-plan it creates a new job, however when you edit a plan then
save it overrides the job, by wiping out all the steps then rewriting only the
single step that represents the sub plan.&amp;nbsp;
In essence this doesn&amp;#39;t allow you to append to that job, if you need
other processes to occur after the maint plan runs, because every edit wipes
out your appended steps.&amp;nbsp; An example of
this is after backing up locally, you might want a second step to copy your
data to a backup server.&amp;nbsp; &lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;There are a few ways to get
around this, the first is the obvious, which is put your serialized processes
into the package.&amp;nbsp; The issue here is that
maintenace plans is a scaled down version of SSIS, and doesn&amp;#39;t provide the same
functionality, such scripting or running executables without hacky
workarounds.&amp;nbsp; The second is to put your
serialized processes into a seperate job that performs these other processes,
and as part of the maint plan run the execute job task that points to this new
job as the last step. The issue here is it takes away your ability to separate
processes.&amp;nbsp; For example in the
backup/copy example, this prevents you from backing up without copying and
vice-versa.&amp;nbsp; &lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;My preferred solution is to
call the maint plan with a hand rolled job.&amp;nbsp;
The key is to remember the plan is simply an SSIS package stored in
msdb, in a psuedo directory &amp;quot;Maintenance Plans&amp;quot;.&amp;nbsp; To call the plan simply select the &amp;quot;SQL
Server Intergration Service Package&amp;quot; type, navigate to the Maintenance Plans
directory, and select your package.&amp;nbsp;
Since sub plans are actually individual SSIS packages under the umbrella
of the Maint plan you need to specify this package.&amp;nbsp; This is accomplished by navigating to the
&amp;quot;Set Values&amp;quot; tab.&amp;nbsp; Under &amp;quot;Property Path&amp;quot; type
&amp;quot;\package\[name of sub plan].disabled&amp;quot;, then under the corresponding &amp;quot;value&amp;quot;
type &amp;quot;false&amp;quot;. &amp;nbsp;For Example if your sub
plan is called sub_plan2 the path would be \package\sub_plan2.disabled.&amp;nbsp; Once in place any saving and/or editing will
not effect this job since it references the plan and not the job.&amp;nbsp; The downside here is you have two jobs, the
one created through Maint plans and the one hand rolled.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Before ending there&amp;#39;s one
more point to be made about scheduling through a separate job, which is the
ability to run multiple sub plans through using the set values.&amp;nbsp; If you have multiple sub plans you want to
run serially, under the default behavior you need to call one sub plan job then
the other.&amp;nbsp; However using the Set values
allows you to run serially within one job step by simply adding the two sub
plans into the set values tab.&amp;nbsp; &lt;/p&gt;

&lt;p&gt;&amp;nbsp; &amp;nbsp;&lt;/p&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;img src="http://blogs.sqlservercentral.com/aggbug.aspx?PostID=8881" width="1" height="1"&gt;</description><category domain="http://blogs.sqlservercentral.com/ken_kaufman/archive/tags/SQL+Server+Administration/default.aspx">SQL Server Administration</category><category domain="http://blogs.sqlservercentral.com/ken_kaufman/archive/tags/Maintenance+Plan/default.aspx">Maintenance Plan</category></item><item><title>Performance Effects on Column Type Changes Another Point</title><link>http://blogs.sqlservercentral.com/ken_kaufman/archive/2008/06/10/performance-effects-on-column-type-changes-another-point.aspx</link><pubDate>Tue, 10 Jun 2008 18:28:00 GMT</pubDate><guid isPermaLink="false">70975365-724d-4ce8-8d1c-45c963ab81ff:8876</guid><dc:creator>Ken Kaufman</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://blogs.sqlservercentral.com/ken_kaufman/rsscomments.aspx?PostID=8876</wfw:commentRss><comments>http://blogs.sqlservercentral.com/ken_kaufman/archive/2008/06/10/performance-effects-on-column-type-changes-another-point.aspx#comments</comments><description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;In a previous blog &lt;a href="http://blogs.sqlservercentral.com/ken_kaufman/archive/2008/05/14/performance-effects-on-column-type-changes.aspx"&gt;http://blogs.sqlservercentral.com/ken_kaufman/archive/2008/05/14/performance-effects-on-column-type-changes.aspx&lt;/a&gt;
&amp;nbsp;I talked about how SQL handled switching
data types and lengths of the same data type, in particular increasing a
varchar length, as well as the cost of moving a varchar to an int.&amp;nbsp; One of the points left out is moving from a
varchar to a char and vice-versa.&amp;nbsp;&amp;nbsp; Even
though these look similar from a high level to be the same data type, there
stored completely different at the record level.&amp;nbsp; Storage of data in a row involves two primary
locations for data.&amp;nbsp; The first in where
your fixed data types resides, this is all data that is fixed in size resides,
including the char data type.&amp;nbsp; The second
is your variable length section.&amp;nbsp; This
second section contains some critical meta-data about your variable data, there&amp;#39;s
a global 2 byte section that tracks all the variable columns in your record,
and there is an additional 2 bytes for each fields that has the offset of each
variable field.&amp;nbsp; What this all means for
converting char into varchar and the reverse is that it requires moving data
within the record, even if your going from a char(5) to a varchar(10).&amp;nbsp; Depending on the type of conversion and
underlying data could also mean significant page splits &lt;/p&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;img src="http://blogs.sqlservercentral.com/aggbug.aspx?PostID=8876" width="1" height="1"&gt;</description><category domain="http://blogs.sqlservercentral.com/ken_kaufman/archive/tags/Performance/default.aspx">Performance</category><category domain="http://blogs.sqlservercentral.com/ken_kaufman/archive/tags/DDL/default.aspx">DDL</category></item><item><title>Leveraging Clustered Indexes in a Covered Index</title><link>http://blogs.sqlservercentral.com/ken_kaufman/archive/2008/06/06/leveraging-clustered-indexes-in-a-covered-index.aspx</link><pubDate>Sat, 07 Jun 2008 01:28:00 GMT</pubDate><guid isPermaLink="false">70975365-724d-4ce8-8d1c-45c963ab81ff:8865</guid><dc:creator>Ken Kaufman</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://blogs.sqlservercentral.com/ken_kaufman/rsscomments.aspx?PostID=8865</wfw:commentRss><comments>http://blogs.sqlservercentral.com/ken_kaufman/archive/2008/06/06/leveraging-clustered-indexes-in-a-covered-index.aspx#comments</comments><description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;In my last blog I talked about covering indexes.&amp;nbsp; In doing so I slightly touched up
non-clustered indexes containing a pointer to the clustered index of the table.
&amp;nbsp;&amp;nbsp;&amp;nbsp;There are often times when you want to cover a
portion of the a composite clustered index.&amp;nbsp;
When doing so SQL Server is smart enough to realize this, and makes
adjustment.&amp;nbsp; &lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;When creating a non clustered index on a table that is built
on a clustered index (Not a heap) the leaf level of the index has a pointer to
the back to the clustered index.&amp;nbsp; For
example if you have an index &amp;nbsp;clustered
index of col1, col2 and you put a non-clustered index on col3, this index would
contain all three fields within the non clustered index, col3 for sorting and
col1/2 to point back to the clustered index, again this is only at the leaf
level and not the root or intermediate levels.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Where you can leverage this is when your query looks
something like this:&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Select col1, col2, col3 from table1&lt;/p&gt;

&lt;p&gt;Where col3 &amp;gt; 5.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;In this case assuming selectivity is high, SQL would use a
partial scan against col3, and pick up the rest of the data without having to
go back to clustered index, hence covering it.&amp;nbsp;
Where SQL gets smart is when you want to use a field inside your
composite index.&amp;nbsp; If you need to satisfy
the below query&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Select col1, col2, col3 from table1&lt;/p&gt;

&lt;p&gt;Where col3 &amp;gt; 5 and col2 &amp;lt; 3&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The following index might be optimal assuming col3 has a
higher selectivity then col2&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Create index nonclusteredtest on table1(col3, col2)&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;In this case at the root and intermediate levels col3 and
col2 are included in that sort order.&amp;nbsp;
When you get down to the leaf level SQL knows not to add a double entry
for col2 one for the index and one for the clustered index.&amp;nbsp; Rather it&amp;#39;s only in there one time and
represents both indexes.&amp;nbsp; This is a
significant space savings particularly if you have a wide field.&amp;nbsp; &lt;/p&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;img src="http://blogs.sqlservercentral.com/aggbug.aspx?PostID=8865" width="1" height="1"&gt;</description><category domain="http://blogs.sqlservercentral.com/ken_kaufman/archive/tags/Performance/default.aspx">Performance</category><category domain="http://blogs.sqlservercentral.com/ken_kaufman/archive/tags/Index/default.aspx">Index</category><category domain="http://blogs.sqlservercentral.com/ken_kaufman/archive/tags/Covered+Indexes/default.aspx">Covered Indexes</category></item><item><title>Covering Indexes</title><link>http://blogs.sqlservercentral.com/ken_kaufman/archive/2008/06/05/covering-indexes.aspx</link><pubDate>Thu, 05 Jun 2008 23:26:00 GMT</pubDate><guid isPermaLink="false">70975365-724d-4ce8-8d1c-45c963ab81ff:8861</guid><dc:creator>Ken Kaufman</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://blogs.sqlservercentral.com/ken_kaufman/rsscomments.aspx?PostID=8861</wfw:commentRss><comments>http://blogs.sqlservercentral.com/ken_kaufman/archive/2008/06/05/covering-indexes.aspx#comments</comments><description>

&lt;p class="MsoNormal"&gt;Covering an index allow you to use a subset of data in an
underlying table to answer queries without having to go to the underlying
table, whether the underlying table is based on a clustered index or a
heap.&lt;span&gt;&amp;nbsp; &lt;/span&gt;Using the “include” feature allows
you to add to the non-clustered index without having to worry about sorting as
it comes in. &lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;It also provides the
benefit of not storing unneeded columns in the intermediate level, as well as
going above the 900 byte limit placed on indexes.&lt;span&gt;&amp;nbsp; &lt;/span&gt;Much of following refers to working with
b-trees and assumes the reader has a general understanding of how they work. &lt;span&gt;&amp;nbsp;&lt;/span&gt;The best way to visualize this is through
example by working inside the Adventureworks DB on the SalesOrderDetail which
has the below table definition.&lt;span&gt;&amp;nbsp; &lt;/span&gt;The clustered
index (b-tree) is sorted on Salesorderid, salesorderdetailid.&lt;span&gt;&amp;nbsp; &lt;/span&gt;What this means that each row at the leaf
level (bottom level) of the index contains all the data within each
record.&lt;span&gt;&amp;nbsp; &lt;/span&gt;However the data on this leaf
level is sorted in ascending order by SalesOrderID, and
SalesOrderDetailID.&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;The intermediate
levels of the clustered index contain only the fields used in the clustered
index, and serve the sole purpose to allow SQL to traverse (walk down) the tree
based on the index sorted fields.&lt;span&gt;&amp;nbsp; &lt;/span&gt;One note
before moving on is the &lt;span&gt;CarrierTrackingNumber
field has been modified from a nvarchar 25 to a char 200, as well as additional
records have been added to the table.&lt;span&gt;&amp;nbsp;
&lt;/span&gt;This was done to make a point.&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&amp;nbsp;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:blue;"&gt;CREATE&lt;/span&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt; &lt;span style="color:blue;"&gt;TABLE&lt;/span&gt; [Sales]&lt;span style="color:gray;"&gt;.&lt;/span&gt;[SalesOrderDetail]&lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;[SalesOrderID] [int] &lt;span style="color:gray;"&gt;NOT&lt;/span&gt; &lt;span style="color:gray;"&gt;NULL,&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;[SalesOrderDetailID] [int] &lt;span style="color:blue;"&gt;IDENTITY&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;1&lt;span style="color:gray;"&gt;,&lt;/span&gt;1&lt;span style="color:gray;"&gt;)&lt;/span&gt; &lt;span style="color:gray;"&gt;NOT&lt;/span&gt; &lt;span style="color:gray;"&gt;NULL,&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;[CarrierTrackingNumber] [char]&lt;span style="color:gray;"&gt;(&lt;/span&gt;200&lt;span style="color:gray;"&gt;)&lt;/span&gt; &lt;span style="color:gray;"&gt;NULL,&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;[OrderQty] [smallint] &lt;span style="color:gray;"&gt;NOT&lt;/span&gt; &lt;span style="color:gray;"&gt;NULL,&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;[ProductID] [int] &lt;span style="color:gray;"&gt;NOT&lt;/span&gt;
&lt;span style="color:gray;"&gt;NULL,&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;[SpecialOfferID] [int] &lt;span style="color:gray;"&gt;NOT&lt;/span&gt; &lt;span style="color:gray;"&gt;NULL,&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;[UnitPrice] [money] &lt;span style="color:gray;"&gt;NOT&lt;/span&gt; &lt;span style="color:gray;"&gt;NULL,&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal" style="margin-left:0.5in;"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;[UnitPriceDiscount] [money] &lt;span style="color:gray;"&gt;NOT&lt;/span&gt;
&lt;span style="color:gray;"&gt;NULL&lt;/span&gt; &lt;span style="color:blue;"&gt;CONSTRAINT&lt;/span&gt;
[DF_SalesOrderDetail_UnitPriceDiscount]&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color:blue;"&gt;DEFAULT&lt;/span&gt; &lt;span style="color:gray;"&gt;((&lt;/span&gt;0.0&lt;span style="color:gray;"&gt;)),&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal" style="margin-left:0.5in;"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;[LineTotal]&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color:blue;"&gt;AS&lt;/span&gt; &lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:fuchsia;"&gt;isnull&lt;/span&gt;&lt;span style="color:gray;"&gt;((&lt;/span&gt;[UnitPrice]&lt;span style="color:gray;"&gt;*((&lt;/span&gt;1.0&lt;span style="color:gray;"&gt;)-&lt;/span&gt;[UnitPriceDiscount]&lt;span style="color:gray;"&gt;))*&lt;/span&gt;[OrderQty]&lt;span style="color:gray;"&gt;,(&lt;/span&gt;0.0&lt;span style="color:gray;"&gt;))),&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal" style="margin-left:0.5in;"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;[rowguid] [uniqueidentifier] &lt;span style="color:blue;"&gt;ROWGUIDCOL&lt;/span&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color:gray;"&gt;NOT&lt;/span&gt; &lt;span style="color:gray;"&gt;NULL&lt;/span&gt; &lt;span style="color:blue;"&gt;CONSTRAINT&lt;/span&gt;
[DF_SalesOrderDetail_rowguid]&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color:blue;"&gt;DEFAULT&lt;/span&gt; &lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:fuchsia;"&gt;newid&lt;/span&gt;&lt;span style="color:gray;"&gt;()),&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;[ModifiedDate] [datetime] &lt;span style="color:gray;"&gt;NOT&lt;/span&gt; &lt;span style="color:gray;"&gt;NULL&lt;/span&gt; &lt;span style="color:blue;"&gt;CONSTRAINT&lt;/span&gt; [DF_SalesOrderDetail_ModifiedDate]&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color:blue;"&gt;DEFAULT&lt;/span&gt; &lt;span style="color:gray;"&gt;(&lt;/span&gt;&lt;span style="color:fuchsia;"&gt;getdate&lt;/span&gt;&lt;span style="color:gray;"&gt;()),&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color:blue;"&gt;CONSTRAINT&lt;/span&gt;
[PK_SalesOrderDetail_SalesOrderID_SalesOrderDetailID] &lt;span style="color:blue;"&gt;PRIMARY&lt;/span&gt;
&lt;span style="color:blue;"&gt;KEY&lt;/span&gt; &lt;span style="color:blue;"&gt;CLUSTERED&lt;/span&gt; &lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:gray;"&gt;(&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;[SalesOrderID] &lt;span style="color:blue;"&gt;ASC&lt;/span&gt;&lt;span style="color:gray;"&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;[SalesOrderDetailID] &lt;span style="color:blue;"&gt;ASC&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:gray;"&gt;)&lt;/span&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span&gt;This structure is optimally
sorted if you want to grab a subset of data based on salesorderid.&lt;span&gt;&amp;nbsp; &lt;/span&gt;If you look at the query below, the physical path
would be:&lt;/span&gt;&lt;/p&gt;

&lt;ul style="margin-top:0in;"&gt;&lt;li class="MsoNormal"&gt;&lt;span&gt;Traverse the tree vertically and find where
     salesorderid = 46321&lt;/span&gt;&lt;/li&gt;&lt;li class="MsoNormal"&gt;&lt;span&gt;Once at the root level go horizantal until until
     you hit salesorderid 47321&lt;/span&gt;&lt;/li&gt;&lt;li class="MsoNormal"&gt;&lt;span&gt;While moving horizontal grab the
     “CarrierTrackingNumber” value from each row it passes over.&lt;span&gt;&amp;nbsp; &lt;/span&gt;This is accomplished by the fact that in
     a clustered index all the data is contained at the root level&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:blue;"&gt;select&lt;/span&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt; CarrierTrackingNumber
&lt;span style="color:blue;"&gt;from&lt;/span&gt; Sales&lt;span style="color:gray;"&gt;.&lt;/span&gt;SalesOrderDetail&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:blue;"&gt;where&lt;/span&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt; salesorderid &lt;span style="color:gray;"&gt;between&lt;/span&gt; 46321 &lt;span style="color:gray;"&gt;and&lt;/span&gt;
47321 &lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span&gt;This query retrieves 6228
rows using on 212 reads.&lt;span&gt;&amp;nbsp; &lt;/span&gt;The rows to
reads ratio is 29 to 1.&lt;span&gt;&amp;nbsp; &lt;/span&gt;Meaning every IO
to memory is grabbing 29 rows.&lt;span&gt;&amp;nbsp; &lt;/span&gt;To prove out
the effiency, the size of the row is approximately 258 bytes which translates
into 31 rows per page. If we’re picking up 29 rows for every write almost all
the IO is working out the leaf level and not traversing the tree on the
intermediate levels.&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span&gt;Tables are often used for
multiple requests.&lt;span&gt;&amp;nbsp; &lt;/span&gt;For example if you
want to look up all orders between Aug 1&lt;sup&gt;st&lt;/sup&gt; and Sept 1&lt;sup&gt;st&lt;/sup&gt;
in 2001 that had an orderqty greater then three, and was only concerned with
gathering the CarrierTrackingNumber You would run the following query:&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:blue;"&gt;select&lt;/span&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;
CarrierTrackingNumber&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color:blue;"&gt;from&lt;/span&gt; Sales&lt;span style="color:gray;"&gt;.&lt;/span&gt;SalesOrderDetail&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:blue;"&gt;where&lt;/span&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;
modifieddate &lt;span style="color:gray;"&gt;between&lt;/span&gt; &lt;span style="color:red;"&gt;&amp;#39;2001-08-01&amp;#39;&lt;/span&gt;
&lt;span style="color:gray;"&gt;and&lt;/span&gt; &lt;span style="color:red;"&gt;&amp;#39;2001-09-01&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:gray;"&gt;and&lt;/span&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt; orderqty &lt;span style="color:gray;"&gt;&amp;gt;&lt;/span&gt;
3&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span&gt;The problem here is there is
no index, and therefore would scan the entire table.&lt;span&gt;&amp;nbsp; &lt;/span&gt;On this small table of only 120K rows a scan
would do 4055 reads, to retrieve only 301 rows.&lt;span&gt;&amp;nbsp;
&lt;/span&gt;This is almost 15 reads per row.&lt;span&gt;&amp;nbsp; &lt;/span&gt;Quite
a difference from the partial scan above.&lt;span&gt;&amp;nbsp;
&lt;/span&gt;The quick fix here is to place a non-clustered index on the predicates
(modifieddate&lt;span style="color:gray;"&gt;,&lt;/span&gt; orderqty).&lt;span&gt;&amp;nbsp; &lt;/span&gt;By creating this index your now moving from a
scan to a seek.&lt;span&gt;&amp;nbsp; &lt;/span&gt;This performs 937 reads,
and get’s your ratio down to 3 reads per row.&lt;span&gt;&amp;nbsp;
&lt;/span&gt;This is a much better then the scan, but doesn’t come close to the first
query.&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:blue;"&gt;create&lt;/span&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt; &lt;span style="color:blue;"&gt;index&lt;/span&gt; testnonclst&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:blue;"&gt;on&lt;/span&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt; Sales&lt;span style="color:gray;"&gt;.&lt;/span&gt;SalesOrderDetail&lt;span style="color:gray;"&gt;(&lt;/span&gt;modifieddate&lt;span style="color:gray;"&gt;,&lt;/span&gt;
orderqty&lt;span style="color:gray;"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:gray;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span&gt;Before going any further you
need to understand the structure of a non-cluster index so we can see the
benefits of covering and additionally leveraging the “include” clause.&lt;span&gt;&amp;nbsp; &lt;/span&gt;The last query performed a seek, and a key
lookup. &lt;span&gt;&amp;nbsp;&lt;/span&gt;For each value found on the
index meeting the predicates requirements it would traverse back to the
clustered index to retrieve the actual data, hence the 3:1 ratio of reads to
rows.&lt;span&gt;&amp;nbsp; &lt;/span&gt;The 3 reads represent the the
levels of the index in the b-tree of the clustered index, and an additional
read of the the non-clustered leaf data.&lt;span&gt;&amp;nbsp;
&lt;/span&gt;Every value found in the non-clustered index requires traversing
the&lt;span&gt;&amp;nbsp; &lt;/span&gt;levels of the clustered index to get
the data (CarrierTrackingNumber).&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;A
non-clustered index has the same b-tree structure as a clustered index.&lt;span&gt;&amp;nbsp; &lt;/span&gt;The primary difference is within the
record.&lt;span&gt;&amp;nbsp; &lt;/span&gt;Each record has an additional field
or fields that point back to the clustered index.&lt;span&gt;&amp;nbsp; &lt;/span&gt;For example inside the &lt;span&gt;testnonclst index it contains both “modifieddate&lt;span style="color:gray;"&gt;,&lt;/span&gt; orderqty” the data being indexed and the clustered
index “SalesOrderID, SalesOrderDetailID” fields that it references.&lt;span&gt;&amp;nbsp; &lt;/span&gt;Understanding this is a b-tree structure you
can put all your data inside this non-clustered index that satisfiys your
query.&lt;span&gt;&amp;nbsp; &lt;/span&gt;Another way of saying this is any
fields used by your query will reside within your index.&lt;span&gt;&amp;nbsp; &lt;/span&gt;The outcome is the loss of the two IO’s back
to the clustered index, for each record since all your data is covered inside
your non-clustered index.&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:blue;"&gt;drop&lt;/span&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt; &lt;span style="color:blue;"&gt;index&lt;/span&gt; testnonclst &lt;span style="color:blue;"&gt;on&lt;/span&gt;
Sales&lt;span style="color:gray;"&gt;.&lt;/span&gt;SalesOrderDetail&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:blue;"&gt;create&lt;/span&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt; &lt;span style="color:blue;"&gt;index&lt;/span&gt; testnonclst_Covered&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:blue;"&gt;on&lt;/span&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt; Sales&lt;span style="color:gray;"&gt;.&lt;/span&gt;SalesOrderDetail&lt;span style="color:gray;"&gt;(&lt;/span&gt;modifieddate&lt;span style="color:gray;"&gt;,&lt;/span&gt; orderqty&lt;span style="color:gray;"&gt;,&lt;/span&gt;
CarrierTrackingNumber&lt;span style="color:gray;"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span&gt;If you examine the query below and compare it to the
above index creation, your covering all three fields in the query, CarrierTrackNumber
which is being selected, and your two predicates, Modifieddate and
orderqty.&lt;span&gt;&amp;nbsp; &lt;/span&gt;Determining which field goes
on the left should be based on selectivity of each field. The highest
selectivity of the field used in your predicates should go to the left.&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:gray;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:blue;"&gt;select&lt;/span&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;
CarrierTrackingNumber&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color:blue;"&gt;from&lt;/span&gt; Sales&lt;span style="color:gray;"&gt;.&lt;/span&gt;SalesOrderDetail&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:blue;"&gt;where&lt;/span&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;
modifieddate &lt;span style="color:gray;"&gt;between&lt;/span&gt; &lt;span style="color:red;"&gt;&amp;#39;2001-08-01&amp;#39;&lt;/span&gt;
&lt;span style="color:gray;"&gt;and&lt;/span&gt; &lt;span style="color:red;"&gt;&amp;#39;2001-09-01&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:gray;"&gt;and&lt;/span&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt; orderqty &lt;span style="color:gray;"&gt;&amp;gt;&lt;/span&gt;
3&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span&gt;When we run the query above our IO’s drop to 31 IO’s
to return 301 records, almost 10 records per IO.&lt;span&gt;&amp;nbsp; &lt;/span&gt;This is because it does a partial scan on the
leaf level of the index rather then needing to due a seek.&lt;span&gt;&amp;nbsp; &lt;/span&gt;The numbers are close to the 29:1 ratio of
the first query run against the clustered index.&lt;span&gt;&amp;nbsp; &lt;/span&gt;However because your carrying the extra
fields of the pointer back to the clustered index, you’ve added 8 bytes to each
record.&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span&gt;The final improvement to be made is remove the
carrierTrackingNumber field from the index and using the “include” clause as
part of the index.&lt;span&gt;&amp;nbsp; &lt;/span&gt;By doing so you’re
appending this field to the non-clustered index at the leaf level, and ignoring
any sorting of this field.&lt;span&gt;&amp;nbsp; &lt;/span&gt;This serves
two purposes the first is space savings on the intermediate levels of the
b-tree which will give a small performance gain, and the second is fact that
you don’t have to sort your data as it comes in, and avoid page splits at every
level.&lt;span&gt;&amp;nbsp; &lt;/span&gt;In the following example
splitting and sorting are not an issue, but if modifieddate and orderqty had a
significant amount of duplicates it would have a good size impact during
inserts and updates.&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:blue;"&gt;drop&lt;/span&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt; &lt;span style="color:blue;"&gt;index&lt;/span&gt; testnonclst_Covered &lt;span style="color:blue;"&gt;on&lt;/span&gt;
Sales&lt;span style="color:gray;"&gt;.&lt;/span&gt;SalesOrderDetail&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:blue;"&gt;create&lt;/span&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt; &lt;span style="color:blue;"&gt;index&lt;/span&gt; testnonclst_Covered_include&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:blue;"&gt;on&lt;/span&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt; Sales&lt;span style="color:gray;"&gt;.&lt;/span&gt;SalesOrderDetail&lt;span style="color:gray;"&gt;(&lt;/span&gt;modifieddate&lt;span style="color:gray;"&gt;,&lt;/span&gt; orderqty&lt;span style="color:gray;"&gt;)&lt;/span&gt;&lt;span style="color:blue;"&gt;include&lt;/span&gt;&lt;span style="color:gray;"&gt;(&lt;/span&gt;
CarrierTrackingNumber&lt;span style="color:gray;"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;color:gray;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span&gt;After running the same query with this structure
there’s a 7 percent gain dropping to 29 IO’s.&lt;span&gt;&amp;nbsp;
&lt;/span&gt;The benefits gained through using include are dependent on sorting as
mentioned earlier, and the size of the included field.&lt;span&gt;&amp;nbsp; &lt;/span&gt;The larger this field the greater the
benefits. &lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&lt;span style="font-size:10pt;font-family:&amp;#39;Courier New&amp;#39;;"&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color:blue;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p class="MsoNormal"&gt;&amp;nbsp;&lt;/p&gt;

&lt;img src="http://blogs.sqlservercentral.com/aggbug.aspx?PostID=8861" width="1" height="1"&gt;</description><category domain="http://blogs.sqlservercentral.com/ken_kaufman/archive/tags/SQL+Server+Query+Performance/default.aspx">SQL Server Query Performance</category><category domain="http://blogs.sqlservercentral.com/ken_kaufman/archive/tags/Index/default.aspx">Index</category><category domain="http://blogs.sqlservercentral.com/ken_kaufman/archive/tags/Covered+Indexes/default.aspx">Covered Indexes</category></item><item><title>Performance Effects on Column Type Changes</title><link>http://blogs.sqlservercentral.com/ken_kaufman/archive/2008/05/14/performance-effects-on-column-type-changes.aspx</link><pubDate>Thu, 15 May 2008 01:47:00 GMT</pubDate><guid isPermaLink="false">70975365-724d-4ce8-8d1c-45c963ab81ff:8814</guid><dc:creator>Ken Kaufman</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://blogs.sqlservercentral.com/ken_kaufman/rsscomments.aspx?PostID=8814</wfw:commentRss><comments>http://blogs.sqlservercentral.com/ken_kaufman/archive/2008/05/14/performance-effects-on-column-type-changes.aspx#comments</comments><description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;The other day I was given a request to change a field from
varchar(6) to varchar(10).&amp;nbsp; Not thinking
it was a big deal I said just give me the checked in code and I&amp;#39;ll make the
change.&amp;nbsp; One of our sharper dev&amp;#39;s (Bill Carlson) was concerned with the lock caused by
this statement.&amp;nbsp; I&amp;#39;d never actually took
a deep dive on how this would occur so I decided to run a quick test to see
what would happen.&amp;nbsp; Making this change on
a 2.5 million row table took 10 ms.&amp;nbsp; What
this means is SQL is smart enough to understand that since it&amp;#39;s growing a
varchar field from 6 to 10 it didn&amp;#39;t need to go to the data to verify this
statement would succeed.&amp;nbsp; It simply had
to make the meta data changes in the system tables.&amp;nbsp; However when I reverted this back from 10 to
6 it took 32 seconds.&amp;nbsp; In this case SQL
had to verify each value of each record to ensure it was less then 7 characters.&amp;nbsp; &lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;This brought on the realization of how SQL Server verifies
data during inserts and updates.&amp;nbsp; If you
look at the structure of a record with variable lengths there is no meta data
on the length of variable fields it simply knows to create offsets within the row.
Checking to determine whether a field is too long occurs at a higher level as
data comes in.&amp;nbsp; Once it passes this check
data streams to the data page without any additional verification.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;After wetting my interest on varchars the next logical
choice was to look at int&amp;#39;s.&amp;nbsp; Starting
out with an unfragmented table I changed a field from int to bigint, then back
again from bigint to int.&amp;nbsp; Moving from an
int to a bigint was almost 7x longer then the reverse.&amp;nbsp; This is was due to page splitting and
allocation which reared it&amp;#39;s head in perfmon.&amp;nbsp;
The simple explanation here is that unlike varchar items, every value
takes a defined space 4 bytes/8 bytes for int/bigint respectively.&amp;nbsp; Going from int to bigint added 4 bytes to
each record, and forced pages to be allocated and split.&amp;nbsp; In my case the initial int was 4 bytes of a 13
byte row almost 30%,&amp;nbsp; growing it to 8
bytes made it almost 50%.&amp;nbsp; In terms of
page size it went from one page holding 620 rows to 474.&amp;nbsp; I guess the moral of this is whenever you&amp;#39;re
looking at running ddl statements you need to consider what the real effects on
the table are, and have someone bright looking over your shoulder.&lt;/p&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;img src="http://blogs.sqlservercentral.com/aggbug.aspx?PostID=8814" width="1" height="1"&gt;</description><category domain="http://blogs.sqlservercentral.com/ken_kaufman/archive/tags/Performance/default.aspx">Performance</category><category domain="http://blogs.sqlservercentral.com/ken_kaufman/archive/tags/DDL/default.aspx">DDL</category><category domain="http://blogs.sqlservercentral.com/ken_kaufman/archive/tags/MetaData/default.aspx">MetaData</category></item><item><title>Log Shipping with Security </title><link>http://blogs.sqlservercentral.com/ken_kaufman/archive/2008/05/13/log-shipping-with-security.aspx</link><pubDate>Wed, 14 May 2008 00:18:00 GMT</pubDate><guid isPermaLink="false">70975365-724d-4ce8-8d1c-45c963ab81ff:8813</guid><dc:creator>Ken Kaufman</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://blogs.sqlservercentral.com/ken_kaufman/rsscomments.aspx?PostID=8813</wfw:commentRss><comments>http://blogs.sqlservercentral.com/ken_kaufman/archive/2008/05/13/log-shipping-with-security.aspx#comments</comments><description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;Many DBA&amp;#39;s struggle with maintaining their security
infrastructure while providing the ability for dev to access the delivery
environment.&amp;nbsp;&amp;nbsp; A common approach is to
setup a NRT (Near Real Time) environment on a warm server.&amp;nbsp; There are several approaches to this.&amp;nbsp; If you&amp;#39;re looking just to push data SSIS or replication
seems to fit the needs.&amp;nbsp; The problem with
these approaches is that it&amp;#39;s not a true representation of delivery.&amp;nbsp; Since you&amp;#39;re only shipping data it often
leaves out schema items such as stored sprocs and views that might have changed
in the last release.&amp;nbsp; In order to ensure
development gets an actual representation of the DB the best method is to ship
the entire database.&amp;nbsp; This requires
replicating all transactions both dml and ddl in the db.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;This need leaves you with one of two choices, log shipping
or mirroring with a snapshot.&amp;nbsp; Either
approach requires maintaining a read-only copy of the db, with occasional
refreshes.&amp;nbsp; For example in log shipping you
might restore every hour in norecovery standby mode.&amp;nbsp; Or in mirroring every hour you peal off
another snapshot.&amp;nbsp; &lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Although moving the database provides users with most of
their needs, there is the issue of security.&amp;nbsp;
SQL Server has two levels of security the first is in the login where
the user authenticates at the server level.&amp;nbsp;
When a user authenticates against the server there corresponding SID in
the syslogins table is cached.&amp;nbsp; It is
this SID that gives the user access to the DB&amp;#39;s on the server.&amp;nbsp; If a user&amp;#39;s login SID at the server level, is
identical to a user SID in the database, the user has the permissions of that
user inside the DB.&amp;nbsp; If you create a user
called &amp;quot;DEV&amp;quot;, that user is assigned a random SID, on the server it was created
on.&amp;nbsp; &amp;nbsp;When you give that user access to a database
the login SID is added to the sysusers table in the database.&amp;nbsp; It&amp;#39;s the SID that&amp;#39;s tracked not the user
name, hence why you can have a different user name then login name.&amp;nbsp;&amp;nbsp;&amp;nbsp; If you log ship or mirror to another server
the database in which the user Dev was created, requires an identical SID on
that server.&amp;nbsp; This is because the user
(DB) SID, resides inside the DB.&amp;nbsp; Having
a database with users not corresponding to Login SIDs or users with a different
SIDs &amp;nbsp;is referred to as orphaning a user.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;There are a few ways to sync the user and login SID.&amp;nbsp; The first is to execute the
sp_change_users_login system stored procedure.&amp;nbsp;
This is well documented in Books Online, however the issue here is that
it changes the SID inside the DB, and since log shipping or snapshots are
read-only, you can&amp;#39;t write, even to a system table.&amp;nbsp; A different approach is to create the user on
both servers with the same SID.&amp;nbsp; For
example if on your source server you create a user called &amp;quot;orphan&amp;quot;, the server
will generate a SID at the server and database level that match.&amp;nbsp; To see this run the following commands.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;CREATE LOGIN orphan WITH PASSWORD = &amp;#39;Password123456&amp;#39;;&lt;/p&gt;

&lt;p&gt;go&lt;/p&gt;

&lt;p&gt;use
Adventureworks&lt;/p&gt;

&lt;p&gt;go&lt;/p&gt;

&lt;p&gt;CREATE USER orphan FOR LOGIN orphan;&lt;/p&gt;

&lt;p&gt;select sid from sys.sysusers where name = &amp;#39;orphan&amp;#39;;&lt;/p&gt;

&lt;p&gt;select sid from sys.syslogins where name = &amp;#39;orphan&amp;#39;;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Once created on the source server take the SID you queried
for and create the same user on your destination server.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;CREATE LOGIN orphan WITH PASSWORD = &amp;#39;Password123456&amp;#39;,&lt;/p&gt;

&lt;p&gt;sid =
0x61BECB8A1CB0394FB5FC7393F6C0C3A2&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Now any database moved from
the source server to secondary server that contains this user can be accessed
by this account.&amp;nbsp; However this isn&amp;#39;t the
full fix quite yet.&amp;nbsp; If the user&amp;#39;s SID
and password is the same on both servers, then anyone that knows the password
can access both servers.&amp;nbsp; The simple
change is to have different passwords on each server.&amp;nbsp; This is the nice part of this two phase
authentication, you&amp;#39;re not tied to a password but a sid.&amp;nbsp; So on the second server run the following
command.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;CREATE LOGIN orphan WITH PASSWORD = &amp;#39;NewPassword123456&amp;#39;,&lt;/p&gt;

&lt;p&gt;sid =
0x61BECB8A1CB0394FB5FC7393F6C0C3A2&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;With this in place data can be logged shipped or
mirror/snapshot without any security issues in place.&amp;nbsp; One other note this is based on sql server
authentication.&amp;nbsp; Using windows authentication
presents a different problem, in that the SID is the NT SID, and hence no
matter what server you&amp;#39;re on changing the password is not a possibility.&amp;nbsp; &lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;



&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;img src="http://blogs.sqlservercentral.com/aggbug.aspx?PostID=8813" width="1" height="1"&gt;</description><category domain="http://blogs.sqlservercentral.com/ken_kaufman/archive/tags/Security/default.aspx">Security</category><category domain="http://blogs.sqlservercentral.com/ken_kaufman/archive/tags/Mirroring/default.aspx">Mirroring</category><category domain="http://blogs.sqlservercentral.com/ken_kaufman/archive/tags/Log+Shipping/default.aspx">Log Shipping</category></item><item><title>Transferring Data In Memory using SQLBulkCopy Class</title><link>http://blogs.sqlservercentral.com/ken_kaufman/archive/2008/05/08/transferring-data-in-memory-using-sqlbulkcopy-class.aspx</link><pubDate>Fri, 09 May 2008 00:21:00 GMT</pubDate><guid isPermaLink="false">70975365-724d-4ce8-8d1c-45c963ab81ff:8808</guid><dc:creator>Ken Kaufman</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://blogs.sqlservercentral.com/ken_kaufman/rsscomments.aspx?PostID=8808</wfw:commentRss><comments>http://blogs.sqlservercentral.com/ken_kaufman/archive/2008/05/08/transferring-data-in-memory-using-sqlbulkcopy-class.aspx#comments</comments><description>
&lt;p&gt;In a SQL Server enterprise there are several ways to move
data, choosing the best method, is often more of a challenge then actually
implementing.&amp;nbsp; The key factors involved are
performance, Dev time, and ease of implementation.&amp;nbsp; A nice utility in the .Net framework is the
SqlBulkCopy class.&amp;nbsp; This leverages the
sturdy dll&amp;#39;s of the command line tool BCP, with one major advantage, it&amp;#39;s done
in memory.&amp;nbsp; Using BCP to import data
requires a disk location.&amp;nbsp; If you want to
use BCP to move data from one server to the next, you must drop to disk from
the source, then import from disk into the destination.&amp;nbsp; Until solid state drives become a reality,
this method comes with a heavy overhead tax. &amp;nbsp;This is where the bulk copy class comes into
play.&amp;nbsp; It eliminates this bottleneck, by
working within memory.&amp;nbsp; This satisfies
the performance requirement.&amp;nbsp; Leveraging
Sql Server&amp;#39;s support for assemblies, gives the ability to execute from within a
SQL Stored Sproc, and satisfies the ease of implementation. &amp;nbsp;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The core under workings of using this class is attaching to
the source using ADO
with a command reader to the data, then transferring this data to the
SqlBulkCopy class which pushes to the destination.&amp;nbsp; At its simplest implementation you could have
a working assembly in less the ten lines of code, which satisfies Dev Time
requirements.&amp;nbsp; In the technical world
every alternative solution has its own cost.&amp;nbsp;
Using this method transfers your data buffering to a more expensive
storage in the form of memory.&amp;nbsp; This is
not as big an issue as one would expect.&amp;nbsp;
Using the BCP command line tool requires transferring the entire data
set to disk, then picking it up again.&amp;nbsp;
Using a data reader allows for a smaller foot print, were data is
fetched and inserted in a pipelined operation. Because it&amp;#39;s pipelined you can
easily transfer a GB of data with less then 20 MB of memory.&amp;nbsp; &lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;There are several samples of this on the net, and in Visual
Studio documentation of how to do this.&amp;nbsp;
I&amp;#39;ve included a recent code snippet I used.&amp;nbsp; As with most code it has it&amp;#39;s own flair,
essentially focusing on data verification, by counting rows at the source and
destination and allowing for the ability to either sync (truncate the
destination) or append data.&amp;nbsp; &lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Managed Code&lt;/p&gt;

&lt;p&gt;public static void pushData(string sourceTable, string
destServer, &lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; string
destDB, string destTable, int batchSize, int
tearDown)&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //Declare
Variables&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; string
_destServer;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; string
_destDB;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; string
_query;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;string
_destTable;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; int
_batchSize;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; string
destConnString;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; string
_cntDestTable;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; string
_cntSourceTable;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; int
_cntDestResult;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; int
_cntPriorDestResult;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; int
_cntDestReturn;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; int
_cntSourceResult;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; string
_cntError;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //Set
Variables&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _destDB = destDB;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _destServer = destServer;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _destTable = destTable;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _batchSize = batchSize;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //Source
Query&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _query = &amp;quot;select * from &amp;quot; + sourceTable;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //Verify
Queries&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _cntDestTable = &amp;quot;select count(*) from &amp;quot; + destTable;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_cntSourceTable = &amp;quot;select count(*) from &amp;quot; + sourceTable;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _cntError = &amp;quot;RAISERROR (&amp;#39;Source and Dest Counts Dont Match&amp;#39;, 16,
1)&amp;quot;;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //Build
Destination ConnString&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; destConnString = &amp;quot;Data Source=&amp;quot; + _destServer + &amp;quot;;Initial Catalog=&amp;quot; +&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _destDB + &amp;quot;;Integrated Security=True&amp;quot;;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //connect
Locally get data&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SqlConnection
oConn = new SqlConnection(&amp;quot;context connection=true&amp;quot;);&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; oConn.Open();&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //Gets
Count of Source used to verify at end of assembly&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SqlCommand
cmdCntSource = new SqlCommand(_cntSourceTable,
oConn);&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SqlDataReader
sourceCntReaderPrior = cmdCntSource.ExecuteReader();&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; sourceCntReaderPrior.Read();&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _cntSourceResult = (int)sourceCntReaderPrior[0];&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; sourceCntReaderPrior.Close();&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //Get
Rows into reader to push&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SqlCommand
cmd = new SqlCommand(_query,
oConn);&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //pull
into reader&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SqlDataReader
reader = cmd.ExecuteReader();&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //Open
Connection to Destination&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SqlConnection
destConn = new SqlConnection(destConnString);&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; destConn.Open();&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //Truncate
Destination if needed else Get Existing row count&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if
(tearDown == 1)&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; string
tearQuery = &amp;quot;truncate table &amp;quot; +
_destTable;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SqlCommand
cmdTear = new SqlCommand(tearQuery,destConn);&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; cmdTear.ExecuteNonQuery();&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //Set
seed count to 0&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _cntPriorDestResult = 0;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; else&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //Get
Seed Count in Dest table&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SqlCommand
cmdCntDest =new SqlCommand(_cntDestTable,
destConn);&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SqlDataReader
destCntReaderPrior = cmdCntDest.ExecuteReader();&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; destCntReaderPrior.Read();&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _cntPriorDestResult = (int)destCntReaderPrior[0];&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; destCntReaderPrior.Close();&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //Move
Data to desting&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SqlBulkCopy
sbc = new SqlBulkCopy(destConn);&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; sbc.DestinationTableName =
_destTable;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; sbc.BatchSize = _batchSize;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; sbc.WriteToServer(reader);&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; reader.Close();&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //Get
count of Destination after insert&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SqlCommand
dstCmd = new SqlCommand(_cntDestTable,
destConn);&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SqlDataReader
destCntReader = dstCmd.ExecuteReader();&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; destCntReader.Read();&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _cntDestResult = (int)destCntReader[0];&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; destCntReader.Close();&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//Calculate
Rows Copied&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; _cntDestReturn = _cntDestResult
- _cntPriorDestResult;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; //Compare
Source/Dest for Accuracy If they don&amp;#39;t match send error&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if
(_cntSourceResult != _cntDestReturn)&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp;{&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SqlCommand
cmdCntError = new SqlCommand(_cntError,
oConn);&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SqlContext.Pipe.ExecuteAndSend(cmdCntError);&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; else&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//Send
results and Cleanup&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; SqlContext.Pipe.Send(_cntSourceResult.ToString()
+ &amp;quot; Rows sent :)&amp;quot;);&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; oConn.Close();&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; destConn.Close();&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;}&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The stored sproc to call this would look like this:&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;CREATE PROCEDURE [dbo].[sp_BCP]&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; @sourceTable
[nvarchar](50),&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; @destServer
[nvarchar](50),&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; @destDB
[nvarchar](50),&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; @destTable
[nvarchar](50),&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; @batchSize [int],&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; @tearDown [int]&lt;/p&gt;

&lt;p&gt;WITH EXECUTE AS CALLER&lt;/p&gt;

&lt;p&gt;AS&lt;/p&gt;

&lt;p&gt;EXTERNAL NAME [bcpTest].[BCPData].[pushData]&lt;/p&gt;



&lt;img src="http://blogs.sqlservercentral.com/aggbug.aspx?PostID=8808" width="1" height="1"&gt;</description><category domain="http://blogs.sqlservercentral.com/ken_kaufman/archive/tags/ETL/default.aspx">ETL</category><category domain="http://blogs.sqlservercentral.com/ken_kaufman/archive/tags/clr/default.aspx">clr</category></item><item><title>Table Variables, Temp Tables, Recompilations </title><link>http://blogs.sqlservercentral.com/ken_kaufman/archive/2007/12/20/table-variables-temp-tables-recompilations.aspx</link><pubDate>Fri, 21 Dec 2007 01:58:00 GMT</pubDate><guid isPermaLink="false">70975365-724d-4ce8-8d1c-45c963ab81ff:3320</guid><dc:creator>Ken Kaufman</dc:creator><slash:comments>1</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://blogs.sqlservercentral.com/ken_kaufman/rsscomments.aspx?PostID=3320</wfw:commentRss><comments>http://blogs.sqlservercentral.com/ken_kaufman/archive/2007/12/20/table-variables-temp-tables-recompilations.aspx#comments</comments><description>

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The general rule of thumb on &amp;quot;variable tables&amp;quot; VS &amp;quot;temp
tables&amp;quot; is if your working with a small datasets or complex query plans use a
temp table.&amp;nbsp; When working with simple
queries or small datasets use variable tables.&amp;nbsp;
This was built by design.&amp;nbsp;
Variable tables are light in memory structures, locking is minimal,
statistics aren&amp;#39;t created, and plans don&amp;#39;t recompile with changes to the
table.&amp;nbsp; Temp tables are real table
structures, and behave almost identical to normal tables, with a shorter life
span.&amp;nbsp; Therefore there is an overhead, in
creating and tearing down, with temp tables. &amp;nbsp;The question is why use temp tables at all to
hold temporary storage.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The trick is in recompilation.&amp;nbsp; Recompilation occurs mid stream during a
batch, when circumstances change, from the initial plan, where SQL determines
to recompile a more optimal plan.&amp;nbsp; This is
often observed when a subplan is dependent on a previous subplan.&amp;nbsp; For example if a query inserts data into a
work table, then selects off the work table.&amp;nbsp;
If the precompiled plan expects 100 rows in the table, but gets 100K,
the plan might need to be changed.&amp;nbsp;&amp;nbsp; This
is where the difference lays between the two data structures.&amp;nbsp; Since SQL keeps statistics on a temp table,
it can recognize major changes and make the appropriate changes to the plan through
a recompile.&amp;nbsp; However with that said a
recompile is expensive and gains from the new plan might not justify the cost
of the recompile.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Recompiles on temp tables occur when the cardinality becomes
greater then 6, and then again greater then 500.&amp;nbsp; I always look at 1000 being a threshold on
whether to use a temp table over a table variable.&amp;nbsp; This is a personal rule of thumb, and you
should always look at the complexity of the query itself which in the end has
the final say on whether a query plan will be recompiled.&amp;nbsp; The following example uses a union join to
force this situation.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;There are three portions to this script, the first creates
the tables and populates them.&amp;nbsp; The
second and third create stored procedures based on a temp table and a table
variable.&amp;nbsp; The @rowcount variable in part
one will be used to throttle the query into different states.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;--Part 1 Create table&lt;/p&gt;

&lt;p&gt;Declare @rowcount int&lt;/p&gt;

&lt;p&gt;set @rowcount = 5&lt;/p&gt;

&lt;p&gt;if exists(select name from sys.objects where name = &amp;#39;table1&amp;#39;)&lt;/p&gt;

&lt;p&gt;Begin&lt;/p&gt;

&lt;p&gt;drop table table1&lt;/p&gt;

&lt;p&gt;end&lt;/p&gt;

&lt;p&gt;if exists(select name from sys.objects where name = &amp;#39;table2&amp;#39;)&lt;/p&gt;

&lt;p&gt;Begin&lt;/p&gt;

&lt;p&gt;drop table table2&lt;/p&gt;

&lt;p&gt;end&lt;/p&gt;

&lt;p&gt;if exists(select name from sys.objects where name = &amp;#39;table3&amp;#39;)&lt;/p&gt;

&lt;p&gt;Begin&lt;/p&gt;

&lt;p&gt;drop table table3&lt;/p&gt;

&lt;p&gt;end&lt;/p&gt;

&lt;p&gt;--Create tables&lt;/p&gt;

&lt;p&gt;create table table1&lt;/p&gt;

&lt;p&gt;(PKInt int primary key,&lt;/p&gt;

&lt;p&gt;charfield varchar(20))&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;create table table2&lt;/p&gt;

&lt;p&gt;(PKInt int primary key,&lt;/p&gt;

&lt;p&gt;charfield varchar(20))&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;create table table3&lt;/p&gt;

&lt;p&gt;(PKInt int primary key,&lt;/p&gt;

&lt;p&gt;charfield varchar(20))&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Declare @Interval int&lt;/p&gt;

&lt;p&gt;set @interval = 1&lt;/p&gt;

&lt;p&gt;while @interval &amp;lt; @rowcount&lt;/p&gt;

&lt;p&gt;Begin&lt;/p&gt;

&lt;p&gt;Insert into table1&lt;/p&gt;

&lt;p&gt;values(@interval, &amp;#39;Table1&amp;#39;)&lt;/p&gt;

&lt;p&gt;set @interval = @interval + 2&lt;/p&gt;

&lt;p&gt;end&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;set @interval =2&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;while @interval &amp;lt; @rowcount&lt;/p&gt;

&lt;p&gt;Begin&lt;/p&gt;

&lt;p&gt;Insert into table2&lt;/p&gt;

&lt;p&gt;values(@interval, &amp;#39;Table2&amp;#39;)&lt;/p&gt;

&lt;p&gt;set @interval = @interval + 2&lt;/p&gt;

&lt;p&gt;end&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;set @interval = 1&lt;/p&gt;

&lt;p&gt;while @interval &amp;lt; @rowcount&lt;/p&gt;

&lt;p&gt;Begin&lt;/p&gt;

&lt;p&gt;Insert into table3&lt;/p&gt;

&lt;p&gt;values(@interval, &amp;#39;Table3&amp;#39;)&lt;/p&gt;

&lt;p&gt;set @interval = @interval + 3&lt;/p&gt;

&lt;p&gt;end&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;--Part2 create SP with table variable&lt;/p&gt;

&lt;p&gt;Create Procedure tableVariable&lt;/p&gt;

&lt;p&gt;as&lt;/p&gt;

&lt;p&gt;Declare @tableVar table&lt;/p&gt;

&lt;p&gt;(PKInt int primary key,&lt;/p&gt;

&lt;p&gt;charfield varchar(20))&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;insert into @tableVar&lt;/p&gt;

&lt;p&gt;select * from&amp;nbsp;&amp;nbsp; table1&lt;/p&gt;

&lt;p&gt;union&lt;/p&gt;

&lt;p&gt;select * from table2&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;select t.Pkint, t.charfield, tv.charfield&lt;/p&gt;

&lt;p&gt;from table3 t inner join&lt;/p&gt;

&lt;p&gt;@tableVar
tv on&lt;/p&gt;

&lt;p&gt;t.pkint = tv.pkint&lt;/p&gt;

&lt;p&gt;go&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;--Part2 create SP with table variable&lt;/p&gt;

&lt;p&gt;Create Procedure temptable&lt;/p&gt;

&lt;p&gt;as&lt;/p&gt;

&lt;p&gt;create table #temptable&lt;/p&gt;

&lt;p&gt;(PKInt int primary key,&lt;/p&gt;

&lt;p&gt;charfield varchar(20))&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;insert into #temptable&lt;/p&gt;

&lt;p&gt;select * from table1&lt;/p&gt;

&lt;p&gt;union&lt;/p&gt;

&lt;p&gt;select * from table2&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;select t.Pkint, t.charfield, tt.charfield&lt;/p&gt;

&lt;p&gt;from table3 t inner join&lt;/p&gt;

&lt;p&gt;#temptable
tt on&lt;/p&gt;

&lt;p&gt;t.pkint = tt.pkint&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Once these are in place run
the following commands.&amp;nbsp; Display the
execution plan for the newly created stored procedures.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;exec
tablevariable&lt;/p&gt;

&lt;p&gt;go&lt;/p&gt;

&lt;p&gt;exec temptable&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;You&amp;#39;ll notice that both SP&amp;#39;s are
using a nested loop to join the temporary storage with table3, with a scan of
table3 and a seek on the temp structure. Now go ahead and execute them and turn
on the display actual execution plan.&amp;nbsp;
This will allow you to see if the plan is recompiled with a new
plan.&amp;nbsp; In addition execute &amp;quot;set statistics
io on&amp;quot; so we can see how many IO&amp;#39;s this is performing.&amp;nbsp; The freeproccache is for good measure so
everything is clean when compiled&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;dbcc
freeproccache&lt;/p&gt;

&lt;p&gt;go&lt;/p&gt;

&lt;p&gt;set statistics io on&lt;/p&gt;

&lt;p&gt;go&lt;/p&gt;

&lt;p&gt;exec
tablevariable&lt;/p&gt;

&lt;p&gt;go&lt;/p&gt;

&lt;p&gt;exec temptable&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Both held there plan, and if
you look at the &amp;quot;select join&amp;quot; portion of the statement for reads both have an
identical cost of 6.&amp;nbsp; This is inline with
the statement above that if under 6 rows change then no recompile will take
place. &amp;nbsp;Since we only had 5 no recompile
occurred.&amp;nbsp; &amp;nbsp;So lets prove out that theory by running the
first set of statements to recreate the tables and set the @rowcount variable
to 6. &amp;nbsp;Once complete repeat the above
exercise of executing the SP&amp;#39;s.&amp;nbsp; &lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Declare @rowcount int&lt;/p&gt;

&lt;p&gt;set @rowcount =
6&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;You&amp;#39;ll notice the estimated
plan is again identical nested loop with the temp storage doing a scan and
table3 using a seek.&amp;nbsp; When you look at
the actual plan the tablevariable SP kept the precompiled plan while the
temptable SP recompiled and chose a different plan.&amp;nbsp; Still using a nested loop but this time using
a seek against the temptable and a scan against table3.&amp;nbsp; The IO&amp;#39;s also show the recompile improved
IO&amp;#39;s on the select join with the variable having 12 IO&amp;#39;s while the temptable
used only 6.&amp;nbsp; &lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;As stated before 500 is also
a benchmark where a recompile will take place.&amp;nbsp;
Lets take a look at what the difference is here. &amp;nbsp;Recreate the tables by setting @rowcount variable
to 500. &amp;nbsp;By this time you should be
convinced that the precompiled plan for both SP&amp;#39;s will stay the same, so lets
only focus on the output.&amp;nbsp; This time the
temptable SP is no longer using a nested loop, but rather a merge join.&amp;nbsp; The comparitable difference is 1002 Reads for
the tablevariable and 6 for the temptable.&amp;nbsp;
I played this out a little further to get some more numbers.&amp;nbsp; Notice the expotential growth difference
below.&amp;nbsp; As you can see the larger the
changes to temp storage the more disparity occurs, which equals resources for
the server, and user time waiting.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;table cellpadding="0" cellspacing="0"&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p&gt;Insert
  Rows&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;Temp
  Reads&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p&gt;Variable
  Reads&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p align="right"&gt;5&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p align="right"&gt;6&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p align="right"&gt;6&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p align="right"&gt;6&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p align="right"&gt;6&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p align="right"&gt;12&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p align="right"&gt;500&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p align="right"&gt;6&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p align="right"&gt;1002&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p align="right"&gt;1000&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p align="right"&gt;7&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p align="right"&gt;2003&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
  &lt;td&gt;
  &lt;p align="right"&gt;10000&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p align="right"&gt;43&lt;/p&gt;
  &lt;/td&gt;
  &lt;td&gt;
  &lt;p align="right"&gt;20029&lt;/p&gt;
  &lt;/td&gt;
 &lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Before you go out and change all your variable tables into
temp tables, there are a few things to consider.&amp;nbsp; As mentioned above temp tables are real
tables and have the associated overhead with them.&amp;nbsp; There&amp;#39;s the metadata creation and
maintenance.&amp;nbsp; There&amp;#39;s heavier locking,
and transactions are logged.&amp;nbsp; Of course
the biggest overhead comes in the form of recompilations.&amp;nbsp; The reason execution plans are cached is
because they&amp;#39;re costly to recreate.&amp;nbsp; The
gains made in IO&amp;#39;s can easily be swallowed up by the cost of the recompilation.
&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;



&lt;img src="http://blogs.sqlservercentral.com/aggbug.aspx?PostID=3320" width="1" height="1"&gt;</description><category domain="http://blogs.sqlservercentral.com/ken_kaufman/archive/tags/Recompilation/default.aspx">Recompilation</category><category domain="http://blogs.sqlservercentral.com/ken_kaufman/archive/tags/SQL+Server+Query+Performance/default.aspx">SQL Server Query Performance</category><category domain="http://blogs.sqlservercentral.com/ken_kaufman/archive/tags/Temp+Tables/default.aspx">Temp Tables</category></item><item><title>Workaround for Monitoring Replication</title><link>http://blogs.sqlservercentral.com/ken_kaufman/archive/2007/12/17/3310.aspx</link><pubDate>Tue, 18 Dec 2007 03:00:00 GMT</pubDate><guid isPermaLink="false">70975365-724d-4ce8-8d1c-45c963ab81ff:3310</guid><dc:creator>Ken Kaufman</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://blogs.sqlservercentral.com/ken_kaufman/rsscomments.aspx?PostID=3310</wfw:commentRss><comments>http://blogs.sqlservercentral.com/ken_kaufman/archive/2007/12/17/3310.aspx#comments</comments><description>&lt;P&gt;I’ve put in too many hours trying to get monitoring working from transactional replication latency.&amp;nbsp; I was unable to find anything online that mimicked my issue.&amp;nbsp; The issue was that in replication monitoring I was able to generate warnings for latency, but they weren’t writing to the application log to kick the alert.&amp;nbsp; As a work around I used a sql alert based on Perfmon sql counters, that looked for latency over 60 secs.&amp;nbsp; The counters I used were:&lt;BR&gt;sql server:Replication Dist/Dist Latency &lt;BR&gt;sql server:Replication Logreader/Logreader:Delivery Latency&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;I know this is a hack but it brought me down an untraveled path in the vast landscape of sql server which is leveraging the alerts.&amp;nbsp; My only issue now is that they need to include all the Perfmon counters as it will save me a bundle in sitescope licenses.&amp;nbsp; I know this can be done using the system monitor alerts, but too much maintenance and email infrastructure to work.&amp;nbsp; &lt;BR&gt;&lt;/P&gt;&lt;img src="http://blogs.sqlservercentral.com/aggbug.aspx?PostID=3310" width="1" height="1"&gt;</description></item><item><title>Formulating the Costs of Index Views</title><link>http://blogs.sqlservercentral.com/ken_kaufman/archive/2007/12/06/3298.aspx</link><pubDate>Fri, 07 Dec 2007 04:29:00 GMT</pubDate><guid isPermaLink="false">70975365-724d-4ce8-8d1c-45c963ab81ff:3298</guid><dc:creator>Ken Kaufman</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://blogs.sqlservercentral.com/ken_kaufman/rsscomments.aspx?PostID=3298</wfw:commentRss><comments>http://blogs.sqlservercentral.com/ken_kaufman/archive/2007/12/06/3298.aspx#comments</comments><description>&lt;P&gt;Using an index view or materialized view (Oracle) allows for the denormalization of data, by storing joins, aggregates, and other runtime queries in permanent storage.&amp;nbsp; The benefit of this is that rather then performing these operations on demand, they’re already stored in a single flat data structure for retrieval.&amp;nbsp; The result is much faster return times.&amp;nbsp; There are plenty of articles out there for example &lt;A href="http://www.microsoft.com/technet/prodtechnol/sql/2005/impprfiv.mspx"&gt;http://www.microsoft.com/technet/prodtechnol/sql/2005/impprfiv.mspx&lt;/A&gt;&amp;nbsp; on the benefits of indexed views.&amp;nbsp; This blog will talk to determining if this approach is right for you by explaining how to perform quantitative and qualitative analysis of the costs. &lt;/P&gt;
&lt;P&gt;First off there’s no free lunch, it takes work to build this flat structure, as well as work to maintain it.&amp;nbsp; Every time a DML statement is run against an underlying table, data is pushed to the indexed view.&amp;nbsp; This process not only involves the IO of the write but CPU costs of any joins or computations.&amp;nbsp; There’s no better way to examine these costs then using the “showplan_all” command.&amp;nbsp; The following example uses the Northwind database, consisting of a two table join with an aggregation.&amp;nbsp; The purpose is to display the last three orders and the associated costs totals.&amp;nbsp; Lets start with the original query:&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&lt;FONT color=#0000ff&gt;select top 3&lt;BR&gt;o.orderDate, sum(od.UnitPrice * od.Quantity) as OrderCost&lt;BR&gt;from Orders o inner Join [Order Details] OD on&lt;BR&gt;o.OrderID = Od.OrderID&lt;BR&gt;where customerid = 'vinet'&lt;BR&gt;group by orderdate&lt;BR&gt;order by orderdate desc&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;Now lets get the cost of this by running a showplan_all.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&lt;FONT color=#0000ff&gt;set showplan_all on&lt;BR&gt;go&lt;BR&gt;select top 3&lt;BR&gt;o.orderDate, sum(od.UnitPrice * od.Quantity) as OrderCost&lt;BR&gt;from Orders o&lt;BR&gt;inner Join&lt;BR&gt;[Order Details] OD on&lt;BR&gt;o.OrderID = Od.OrderID&lt;BR&gt;where customerid = 'vinet'&lt;BR&gt;group by orderdate&lt;BR&gt;order by orderdate desc&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;Explaining this command (showplan_all) &amp;nbsp;is out of scope, so I’ll stay focused on the primary result field we’re concerned with “TotalSubtreeCost”.&amp;nbsp; Every statement is made up of different operators to perform it’s task.&amp;nbsp; The total subtreeCost is the estimation SQL Server makes on how expensive the operator will be.&amp;nbsp; This number is derived as a time value in seconds each operation is estimated to take.&amp;nbsp; An important note here is that it doesn’t relate to the time on your machine, but time run on standard equipment at Microsoft.&amp;nbsp; It’s simply an unchanging baseline to work against.&amp;nbsp; Scroll through the result sets to find this field. In the following output the sum of all the subtree’s is a cost of 0.29155515 seconds.&lt;/P&gt;
&lt;P&gt;&lt;FONT color=#ff0000&gt;TotalSubtreeCost&lt;BR&gt;0.04338021&lt;BR&gt;0.04338021&lt;BR&gt;0.04337971&lt;BR&gt;0.04336942&lt;BR&gt;0.04336812&lt;BR&gt;0.02707165&lt;BR&gt;0.01569226&lt;BR&gt;0.0032875&lt;BR&gt;0.01238386&lt;BR&gt;0.01624221&lt;BR&gt;0.29155515 - Total&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;With a normalized baseline in place the following statement applies an index view.&amp;nbsp; Notice no predicate is given, since this will normally be applied as a parameter. &lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&lt;FONT color=#0000ff&gt;Create view order_summary_view&lt;BR&gt;with schemabinding&lt;BR&gt;as&lt;BR&gt;select &lt;BR&gt;&amp;nbsp;count_big(*) as Cnt, o.customerid, o.orderDate, sum(od.UnitPrice * od.Quantity) as OrderCost&lt;BR&gt;from dbo.Orders o&lt;BR&gt;inner Join&lt;BR&gt;dbo.[Order Details] OD on&lt;BR&gt;o.OrderID = Od.OrderID&lt;BR&gt;group by orderdate, customerid&lt;BR&gt;go&lt;BR&gt;create unique clustered index order_summary_view_index&lt;BR&gt;on order_summary_view(customerid asc, orderDate desc)&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;Now execute a query to return the same results as the previous select statement, using the showplan_all.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&lt;FONT color=#0000ff&gt;&lt;FONT color=#0000ff size=2&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT color=#0000ff size=3&gt;select&lt;/FONT&gt;&lt;/FONT&gt;&lt;FONT color=#0000ff&gt; top 3 OrderDate, OrderCost from order_summary_view with (noexpand)&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT color=#0000ff&gt;where customerid =&lt;/FONT&gt;&lt;/FONT&gt;&lt;FONT color=#0000ff&gt; &lt;FONT size=2&gt;&lt;FONT size=3&gt;'vinet'&lt;/FONT&gt;&lt;/P&gt;&lt;/FONT&gt;&lt;/FONT&gt;
&lt;P&gt;&lt;BR&gt;You’ll notice only a few operations are used with a total on the “TotalSubtreeCost” results, of 0.009864.&amp;nbsp; Compared to the normalized result it is only 3% of the cost.&amp;nbsp; It’s easy to see the advantages of an indexed view for retrieving data.&amp;nbsp; However the point of this blog is to determine the overall cost, not just on retrieving.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;We need to determine the costs of data modifications so let’s first use the index view by examining the cost of an insert into an underlying table.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&lt;FONT color=#0000ff&gt;set showplan_all on&lt;BR&gt;go&lt;BR&gt;insert into [order details]&lt;BR&gt;(orderid, productid, unitprice, quantity, discount)&lt;BR&gt;values(10248, 17,&amp;nbsp; 19.24, 10, 0)&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;The plan looks much more inline with the first select statement, and so do the total cost results coming in at 0.746337 which is over double the cost of the original select statement.&amp;nbsp; Now I’ll drop the index view and rerun the above query.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&lt;FONT color=#0000ff&gt;set showplan_all off&lt;BR&gt;go&lt;BR&gt;Drop view order_summary_view&lt;BR&gt;go&lt;BR&gt;set showplan_all on&lt;BR&gt;go&lt;BR&gt;insert into [order details]&lt;BR&gt;(orderid, productid, unitprice, quantity, discount)&lt;BR&gt;values(10248, 18,&amp;nbsp; 19.24, 10, 0) &lt;BR&gt;select top 1 * from [order details]&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;The cost of inserting without an indexed view is 0.339465 more then 50% less then an insert with a clustered index.&amp;nbsp; With these numbers in hand we can now make a quantitative analysis of the cost of having an indexed view.&amp;nbsp; The way to do this is to take a sampling of your current activity and gather how many calls are made to retrieve the data and how many calls are made for data modification.&amp;nbsp; Once armed with this information use the following formula on each set, then compare the results:&lt;/P&gt;
&lt;P&gt;&lt;FONT color=#ff0000&gt;(Select_Calls * Select_totalcost) + (DML_Calls * DML_totalcost) = TrueCost&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;In our case lets assume in a hour period we see 1000 select calls&amp;nbsp; and 100 DML calls&amp;nbsp; the comparison formula should look like so&lt;/P&gt;
&lt;P&gt;&lt;FONT color=#ff0000&gt;No Indexed View&lt;BR&gt;(1000 * .29155515) + (100 * 0.339465) = 325.50165&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT color=#ff0000&gt;Indexed View&lt;BR&gt;(1000 * 0.009864) + (100 * 0.746337) = 84.4977&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;In this situation the quantitative analysis shows better overall performance by using an indexed view.&amp;nbsp; This exercise was fairly vanilla, one insert statement and one select statement.&amp;nbsp; Unfortunately the world doesn’t work this way, often you have several dml statements that will affect the underlining tables and several queries that will benefit from an indexed view.&amp;nbsp; In this case the formula would just incorporate all the calls, by adding on.&amp;nbsp; For example:&lt;/P&gt;
&lt;P&gt;&lt;FONT color=#ff0000&gt;(Select_Calls1 * Select_totalcost) + (Select_Calls’n’ * Select_totalcost) +&amp;nbsp; (DML1_Calls * DML_totalcost)&amp;nbsp; +&amp;nbsp; (DML1_Calls’n’ * DML_totalcost) = TrueCost&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;Before moving on to the qualitative analysis I must call out few items to be fair.&amp;nbsp; The above is for demonstration only.&amp;nbsp; For example the first query without an indexed view would benefit significantly from a covered index, and a 10 to 1 ratio might not be realistic.&amp;nbsp; This last point is an excellent segway into qualitative analysis.&lt;/P&gt;
&lt;P&gt;Quantifying costs is a great measure but should not be your final determinant on which direction to go.&amp;nbsp; In the end run it’s all about user experience.&amp;nbsp; If you can slash 100 ms on a select statement with an index view, but means timeouts when a user inserts data, it provides a poor user experience.&amp;nbsp; Along those lines submitting data to a web interface is expected to take longer, users have just been trained that way.&amp;nbsp; So penalizing your writes for read speed might be acceptable.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;&amp;nbsp;When deciding on which direction to go, you need to run in a stress environment, and analyze the numbers for a balance.&amp;nbsp; In addition the previous example only analyzes a single query and not the entire environment.&amp;nbsp; You need to look at what the user experience is with 100 clients hitting the server.&amp;nbsp; With an indexed view your writing to two objects, and doubling your locking on both objects (Writes are in a transaction).&amp;nbsp; So you need to answer, how&amp;nbsp; this effects the other 99 users?&amp;nbsp; &lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&amp;nbsp;&lt;/P&gt;&lt;img src="http://blogs.sqlservercentral.com/aggbug.aspx?PostID=3298" width="1" height="1"&gt;</description></item><item><title>Parameter Sniffing</title><link>http://blogs.sqlservercentral.com/ken_kaufman/archive/2007/11/28/3289.aspx</link><pubDate>Wed, 28 Nov 2007 14:42:00 GMT</pubDate><guid isPermaLink="false">70975365-724d-4ce8-8d1c-45c963ab81ff:3289</guid><dc:creator>Ken Kaufman</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://blogs.sqlservercentral.com/ken_kaufman/rsscomments.aspx?PostID=3289</wfw:commentRss><comments>http://blogs.sqlservercentral.com/ken_kaufman/archive/2007/11/28/3289.aspx#comments</comments><description>&lt;P&gt;Parameter sniffing is the default strategy used by SQL Server for caching query plans.&amp;nbsp; The concept is that when a paramatized query is compiled for the first time, it uses the input parameters to build an optimal plan.&amp;nbsp;&amp;nbsp; This plan is then cached.&amp;nbsp; Reuse of the query will always be based on the cached plan whether it is optimal for incoming parameters or not.&amp;nbsp; This explanation is a little convoluted and the best way to explain is through example. &lt;/P&gt;
&lt;P&gt;In the following example I’ve created a subset table based off of the “person.address” table in the AdventureWorks database. The primary field to be concerned with here is the “City” field where there has been a non-clustered index created.&amp;nbsp;&amp;nbsp; The subsetted data has a breakdown of the following city information,&lt;/P&gt;
&lt;P&gt;cityCount&amp;nbsp;&amp;nbsp; city&lt;BR&gt;----------- ------------------------------&lt;BR&gt;1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Crossville&lt;BR&gt;1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Denby&lt;BR&gt;1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Escondido&lt;BR&gt;213&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Beaverton&lt;BR&gt;214&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Concord&lt;BR&gt;215&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Burien&lt;BR&gt;398&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Paris&lt;BR&gt;434&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; London&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;To start out I’ve created the following SP to query the data by providing a parameter on the city&lt;/P&gt;
&lt;P&gt;CREATE procedure [dbo].[snifftest]&lt;BR&gt;@city varchar(100)&lt;BR&gt;as&lt;BR&gt;select * from testaddress where city = @city &lt;/P&gt;
&lt;P&gt;Once created we’ll look at the following:&lt;BR&gt;1.&amp;nbsp;What the plan looks like for London (29% of rows)&lt;BR&gt;2.&amp;nbsp;What the plan looks like for Denby (Less then 1% of rows)&lt;BR&gt;3.&amp;nbsp;How many reads for London&lt;BR&gt;4.&amp;nbsp;How many reads for Denby&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&lt;FONT color=#0000ff&gt;set showplan_text off&lt;BR&gt;go&lt;BR&gt;dbcc freeproccache&lt;BR&gt;go&lt;BR&gt;set showplan_text on&lt;BR&gt;go&lt;BR&gt;exec snifftest 'London'&lt;BR&gt;go&lt;BR&gt;exec snifftest 'Denby'&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT color=#0000ff&gt;|--Clustered Index Scan&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;The above returned a cluster index scan for both queries.&amp;nbsp; This is because London was the first parameter in, and it is more efficient to use a scan with such a high density. Now view the IO’s by running the following queries.&lt;/P&gt;
&lt;P&gt;&lt;FONT color=#0000ff&gt;set showplan_text off&lt;BR&gt;go&lt;BR&gt;set statistics IO on&lt;BR&gt;go&lt;BR&gt;exec snifftest 'London'&lt;BR&gt;go&lt;BR&gt;exec snifftest 'Denby'&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;Each query uses the same method which is a scan and therefore has the same IO’s which is 24 in this case.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&lt;FONT color=#0000ff&gt;set showplan_text off&lt;BR&gt;go&lt;BR&gt;set statistics IO on&lt;BR&gt;go&lt;BR&gt;exec snifftest 'London'&lt;BR&gt;go&lt;BR&gt;exec snifftest 'Denby'&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT color=#0000ff&gt;Scan count 1, logical reads 24&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;Now reverse the order, have Denby go first.&amp;nbsp; The results show an index seek.&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&lt;FONT color=#0000ff&gt;set showplan_text off&lt;BR&gt;go&lt;BR&gt;dbcc freeproccache&lt;BR&gt;go&lt;BR&gt;set showplan_text on&lt;BR&gt;go&lt;BR&gt;exec snifftest 'Denby'&lt;BR&gt;go&lt;BR&gt;exec snifftest 'London'&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT color=#0000ff&gt;|--Index Seek&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;The next query is where the rubber meets the road, when we execute this query to look at the IO’s for each value.&lt;/P&gt;
&lt;P&gt;&lt;FONT color=#0000ff&gt;set showplan_text off&lt;BR&gt;go&lt;BR&gt;set statistics IO on&lt;BR&gt;go&lt;BR&gt;exec snifftest 'London'&lt;BR&gt;go&lt;BR&gt;exec snifftest 'Denby'&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT color=#0000ff&gt;Scan count 1, logical reads 871&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT color=#0000ff&gt;Scan count 1, logical reads 4&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;For Denby it’s 4 reads, but for London it’s 871.&amp;nbsp; This is a huge difference.&amp;nbsp; With London making up such a large percentage of the data it only follows that the amount of calls to retrieve this data for London should be inline.&amp;nbsp; What this means is 29% of your calls will do 857 more reads then needed.&amp;nbsp; If London or another value with a high density is executed first parameter sniffing is optimal here.&amp;nbsp; Even though calls to a lower densitity value, loose a small amount of performance, it more then makes up for the overall system performance.&amp;nbsp; &lt;/P&gt;
&lt;P&gt;So now the question is why does SQL by default use parameter sniffing.&amp;nbsp; This is because of query compilation.&amp;nbsp; There’s a cost involved in compiling the correct plan for a query.&amp;nbsp; Rather then compiling on every query, SQL cheats by caching the plans.&amp;nbsp; When the call for a SP comes in it simply reuses the plan already in memory, forgoes the cost of compilation.&amp;nbsp; As with any cheat there’s always a risk in that on the first compilation an edge case value could come in, and force the wrong plan.&amp;nbsp;&amp;nbsp;&amp;nbsp; In the case above this would be a value requiring an index seek rather then a scan.&amp;nbsp; In a future blog I’ll talk about troubleshooting and avoiding these scenerios.&lt;BR&gt;&lt;/P&gt;&lt;img src="http://blogs.sqlservercentral.com/aggbug.aspx?PostID=3289" width="1" height="1"&gt;</description></item><item><title>Renaming Tables in a Transaction</title><link>http://blogs.sqlservercentral.com/ken_kaufman/archive/2007/11/15/3258.aspx</link><pubDate>Fri, 16 Nov 2007 05:25:00 GMT</pubDate><guid isPermaLink="false">70975365-724d-4ce8-8d1c-45c963ab81ff:3258</guid><dc:creator>Ken Kaufman</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://blogs.sqlservercentral.com/ken_kaufman/rsscomments.aspx?PostID=3258</wfw:commentRss><comments>http://blogs.sqlservercentral.com/ken_kaufman/archive/2007/11/15/3258.aspx#comments</comments><description>&lt;P class=MsoNormal&gt;&lt;BR&gt;A common method of ETL is to bulk load tables from a staging area into your production environment,&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;Of course the issue here is that if your production environment is 24X7, it means taking the table offline, which can cause significant issues for your users.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;One commonly used work around is to load this data into a holding table and rename.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;The rename is instantaneous, and usually has no to little effect on your users.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;To do so you would often issue a command similar to this.&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;execute&lt;/SPAN&gt;&lt;SPAN&gt; &lt;SPAN&gt;sp_rename&lt;/SPAN&gt; &lt;SPAN&gt;'table1'&lt;/SPAN&gt;&lt;SPAN&gt;,&lt;/SPAN&gt; &lt;SPAN&gt;'table1_old'&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;execute&lt;/SPAN&gt;&lt;SPAN&gt; &lt;SPAN&gt;sp_rename&lt;/SPAN&gt; &lt;SPAN&gt;'table1_new'&lt;/SPAN&gt;&lt;SPAN&gt;,&lt;/SPAN&gt; &lt;SPAN&gt;'table1'&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;The issue arrises with the above statement is what happens if the first command succeeds and the second command fails.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;You’ve now orphanned your users with no table to hit.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;The work around is to put this into a transaction.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;Pre 2005 transactions meant using the @@error return.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;For example after each statement you would need something like this:&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;Begin&lt;/SPAN&gt;&lt;SPAN&gt; &lt;SPAN&gt;Tran&lt;/SPAN&gt; rename&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;execute&lt;/SPAN&gt;&lt;SPAN&gt; &lt;SPAN&gt;sp_rename&lt;/SPAN&gt; &lt;SPAN&gt;'table1'&lt;/SPAN&gt;&lt;SPAN&gt;,&lt;/SPAN&gt; &lt;SPAN&gt;'table1_old'&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;if&lt;/SPAN&gt;&lt;SPAN&gt; &lt;SPAN&gt;@@error&lt;/SPAN&gt; &lt;SPAN&gt;&amp;gt;&lt;/SPAN&gt; 0 &lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;begin&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;goto&lt;/SPAN&gt;&lt;SPAN&gt; Errorhandler&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;end&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;execute&lt;/SPAN&gt;&lt;SPAN&gt; &lt;SPAN&gt;sp_rename&lt;/SPAN&gt; &lt;SPAN&gt;'table1_new'&lt;/SPAN&gt;&lt;SPAN&gt;,&lt;/SPAN&gt; &lt;SPAN&gt;'table1'&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;if&lt;/SPAN&gt;&lt;SPAN&gt; &lt;SPAN&gt;@@error&lt;/SPAN&gt; &lt;SPAN&gt;&amp;gt;&lt;/SPAN&gt; 0 &lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;begin&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;goto&lt;/SPAN&gt;&lt;SPAN&gt; Errorhandler&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;end&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;commit&lt;/SPAN&gt;&lt;SPAN&gt; &lt;SPAN&gt;tran&lt;/SPAN&gt; rename &lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;errorhandler&lt;SPAN&gt;:&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;Rollback&lt;/SPAN&gt;&lt;SPAN&gt; &lt;SPAN&gt;tran&lt;/SPAN&gt; rename&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;I’m not a big fan of complexity or typing, so the easier way around is to use the new try/catch feature of sql server. &lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;Put all your transactions in the try, and rollback in the catch&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;Begin&lt;/SPAN&gt;&lt;SPAN&gt; &lt;SPAN&gt;Tran&lt;/SPAN&gt; rename&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;begin&lt;/SPAN&gt;&lt;SPAN&gt; &lt;SPAN&gt;try&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;execute&lt;/SPAN&gt;&lt;SPAN&gt; &lt;SPAN&gt;sp_rename&lt;/SPAN&gt; &lt;SPAN&gt;'table1'&lt;/SPAN&gt;&lt;SPAN&gt;,&lt;/SPAN&gt; &lt;SPAN&gt;'table1_old'&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;execute&lt;/SPAN&gt;&lt;SPAN&gt; &lt;SPAN&gt;sp_rename&lt;/SPAN&gt; &lt;SPAN&gt;'table1_new'&lt;/SPAN&gt;&lt;SPAN&gt;,&lt;/SPAN&gt; &lt;SPAN&gt;'table1'&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;Commit&lt;/SPAN&gt;&lt;SPAN&gt; &lt;SPAN&gt;Tran&lt;/SPAN&gt; rename&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;end&lt;/SPAN&gt;&lt;SPAN&gt; &lt;SPAN&gt;try&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;Begin&lt;/SPAN&gt;&lt;SPAN&gt; &lt;SPAN&gt;Catch&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;rollback&lt;/SPAN&gt;&lt;SPAN&gt; &lt;SPAN&gt;tran&lt;/SPAN&gt; rename&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;end&lt;/SPAN&gt;&lt;SPAN&gt; &lt;SPAN&gt;catch&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;img src="http://blogs.sqlservercentral.com/aggbug.aspx?PostID=3258" width="1" height="1"&gt;</description></item><item><title>Looking Inside the Log for Transactional Replication</title><link>http://blogs.sqlservercentral.com/ken_kaufman/archive/2007/11/14/3255.aspx</link><pubDate>Wed, 14 Nov 2007 01:20:00 GMT</pubDate><guid isPermaLink="false">70975365-724d-4ce8-8d1c-45c963ab81ff:3255</guid><dc:creator>Ken Kaufman</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://blogs.sqlservercentral.com/ken_kaufman/rsscomments.aspx?PostID=3255</wfw:commentRss><comments>http://blogs.sqlservercentral.com/ken_kaufman/archive/2007/11/14/3255.aspx#comments</comments><description>&lt;P class=MsoNormal&gt;As the name implies the log reader is responsible for reading the log on the publishing database.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;Using the fn_dblog command we can peek inside the log to see what’s happening here.&amp;nbsp;&lt;/P&gt;
&lt;P class=MsoNormal&gt;When a database is set as the publisher in a replication schema, it marks dml statements against it’s published articles as for replication.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;What this does is it holds the transactions within its transaction log until the log agent comes by and picks up these transactions.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;Where the effect of this can be seen is in checkpointing, which flushes the dirty pages to disk.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;If a transaction has not been grabbed by the log reader a checkpoint will not flush to disk.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;This can cause significant headaches for an admin in that the log will continue to grow, even if the db is in simple mode or a backup log is issued.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;To see how this works you can leverage fn_dblog function.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;Create a db in “simple mode”, then create a sample table and publish it for transactional replication.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;Once this is setup view the transaction log using the following command:&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;select&lt;/SPAN&gt;&lt;SPAN&gt; &lt;SPAN&gt;*&lt;/SPAN&gt; &lt;SPAN&gt;from&lt;/SPAN&gt; fn_dblog&lt;SPAN&gt;(null,null)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;You should noticed several records returned.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;Since your db is in simple mode your seeing all the transactions that have occurred since the last “checkpoint” has occurred.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;FONT color=#000000&gt;You &lt;/FONT&gt;can clear your log (Flush to disk) by issuing the Checkpoint command.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;Execute&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;Checkpoint&lt;/P&gt;
&lt;P class=MsoNormal&gt;Go&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;select&lt;/SPAN&gt;&lt;SPAN&gt; &lt;SPAN&gt;*&lt;/SPAN&gt; &lt;SPAN&gt;from&lt;/SPAN&gt; fn_dblog&lt;SPAN&gt;(null,null)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;You’ll now notice only two records in the log, which represent the checkpoint that has occurred.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;LOP_BEGIN_CKPT&lt;/P&gt;
&lt;P class=MsoNormal&gt;LOP_END_CKPT&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;Now that we see how that works insert a row into your published table then issue the fn_dblog again.&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;Insert&lt;/SPAN&gt;&lt;SPAN&gt; &lt;SPAN&gt;into&lt;/SPAN&gt; table1&lt;SPAN&gt;(&lt;/SPAN&gt;charfield&lt;SPAN&gt;)&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;