<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss'><id>tag:blogger.com,1999:blog-866188004456276207</id><updated>2009-11-07T21:03:34.936-08:00</updated><title type='text'>Squared Thoughts</title><subtitle type='html'>Random thoughts of Erik E, aka ESquared</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://squaredthoughts.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default'/><link rel='alternate' type='text/html' href='http://squaredthoughts.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>ESquared</name><uri>http://www.blogger.com/profile/09187680112940803740</uri><email>noreply@blogger.com</email></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>16</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-866188004456276207.post-7147511436314100926</id><published>2008-09-19T10:27:00.001-07:00</published><updated>2008-09-19T11:13:36.988-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='SQL Confusion'/><title type='text'>Think about what you are doing!</title><content type='html'>Production code:&lt;br /&gt;&lt;DIV class="codebox"&gt;SELECT @ReturnStatus = @@ERROR&lt;br /&gt;IF(@ReturnStatus &lt;&gt; 0)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;RETURN (@ReturnStatus)&lt;/DIV&gt;&lt;br /&gt;Making things as simple as possible isn't just for self-congratulatory back-patting. It's because the next time you're looking at the code it will make it easier to understand what it's doing. So let's make it simple:&lt;br /&gt;&lt;br /&gt;&lt;DIV class="codebox"&gt;RETURN @@ERROR&lt;/DIV&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/866188004456276207-7147511436314100926?l=squaredthoughts.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://squaredthoughts.blogspot.com/feeds/7147511436314100926/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=866188004456276207&amp;postID=7147511436314100926' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/7147511436314100926'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/7147511436314100926'/><link rel='alternate' type='text/html' href='http://squaredthoughts.blogspot.com/2008/09/think-about-what-you-are-doing.html' title='Think about what you are doing!'/><author><name>ESquared</name><uri>http://www.blogger.com/profile/09187680112940803740</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='05197177171442690440'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-866188004456276207.post-7143585436110283731</id><published>2008-05-07T12:23:00.000-07:00</published><updated>2008-05-16T13:06:11.856-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='preventing mistakes'/><category scheme='http://www.blogger.com/atom/ns#' term='accidental update'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL Server'/><title type='text'>Some Pain Is Avoidable - So Spare Yourself</title><content type='html'>Ever heard of a runaway F5? This is when you intend to highlight only a small section of SQL code but you miss for some reason and when you hit F5, the entire script you have loaded runs instead of just the small part you tried to highlight.&lt;br /&gt;&lt;br /&gt;It's not enough to put a syntax error at the top of your script because there might be some batch separating GOs in there. So there are a few things that I routinely do to protect against this because the results of such a mistake can be, shall we say, painful.&lt;br /&gt;&lt;br /&gt;1. Put this at the top:&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;create database trash&lt;br /&gt;GO&lt;br /&gt;use trash&lt;br /&gt;GO&lt;/div&gt;&lt;br /&gt;This will make most of your code run in the wrong context. The GO statements are important as you need these to run without errors.&lt;br /&gt;&lt;br /&gt;If your code uses three-part names to point to specific databases, this will not help, so keep reading.&lt;br /&gt;&lt;br /&gt;2. Don't use the database name in dev-only code, and think carefully about your use of database context in your production code. If you put a USE statement in your manually-attended script, hide it inside comments:&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;/*&lt;br /&gt;USE Somedatabase&lt;br /&gt;*/&lt;/div&gt;&lt;br /&gt;&lt;div class="codebox"&gt;-- USE Somedatabase&lt;/div&gt;&lt;br /&gt;The block comment way makes highlighting the whole line easier if and when you need to run it.&lt;br /&gt;&lt;br /&gt;3. Comment out the modification part of your statements and turn them into selects:&lt;br /&gt;&lt;br /&gt;Instead of the following query:&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;UPDATE MyTable&lt;br /&gt;SET Salesrep = 42&lt;br /&gt;WHERE CustID = 16&lt;/div&gt;&lt;br /&gt;This is a much safer query to leave lying around in your script:&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;SELECT&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;SalesRep,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;NewSalesRep = 42,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;*&lt;br /&gt;-- &lt;span style="color:white;background-color:#800080;"&gt;UPDATE M SET Salesrep = 42&lt;br /&gt;FROM MyTable M&lt;br /&gt;WHERE CustID = 16&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;The highlighted part is what you have to select in order to run your update (note that you're skipping the commenting characters at the beginning of the line). You can still make a mistake by leaving off the WHERE clause, but at least you know no updates will be made unless you do some careful selecting.&lt;br /&gt;&lt;br /&gt;Here's one rough idea for a way to protect against forgetting the where clause:&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;SELECT&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;SalesRep,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;NewSalesRep = 42,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;*&lt;br /&gt;-- BEGIN TRAN UPDATE M SET Salesrep = 42&lt;br /&gt;FROM MyTable M&lt;br /&gt;WHERE CustID = 16&lt;br /&gt;ROLLBACK TRAN&lt;br /&gt;-- COMMIT TRAN&lt;/div&gt;&lt;br /&gt;You can still blow it but it's harder. Any full-line end selection is protected.&lt;br /&gt;&lt;br /&gt;And another, simpler and possibly better:&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;SELECT&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;SalesRep,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;NewSalesRep = 42,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;*,&lt;br /&gt;-- UPDATE M SET Salesrep = 42&lt;br /&gt;FROM MyTable M /*&lt;br /&gt;*/ WHERE CustID = 16&lt;/div&gt;&lt;br /&gt;A full-line end selection is again protected.&lt;br /&gt;&lt;br /&gt;4. Be paranoid. Don't let yourself get comfortable. THINK about each update you do to real, live, data. Make an extra "unnecessary" backup. Convert your modification statements to selects and check it. Select the results into a new table and do some joins to make sure they were right. Run the update in a transaction and do some queries to verify them. Keep in mind potential locking &amp; blocking, though, so if people are accessing the table you're modifying, create the check statements and do the update and the check all at once:&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;BEGIN TRAN&lt;br /&gt;&lt;br /&gt;UPDATE Blah -- My potentially dangerous modification&lt;br /&gt;&lt;br /&gt;SELECT * FROM Blah B INNER JOIN Gorp G ON Splat = Bonk&lt;br /&gt;&lt;br /&gt;ROLLBACK TRAN&lt;/div&gt;&lt;br /&gt;Only run the whole block. If you don't like what you got, fix it, and run it again.&lt;br /&gt;&lt;br /&gt;Be wary of leaving open any transaction, even one you don't think will affect other people. A stray SQL editor session holding some kind of lock is annoying and can cause problems.&lt;br /&gt;&lt;br /&gt;I hope these ideas are useful to you and save you some pain! I had to learn this the hard way. I am simply fortunate that I never did anything REALLY bad before acquiring these habits.&lt;br /&gt;&lt;br /&gt;Update 2008-05-16&lt;br /&gt;&lt;br /&gt;5. If you must have a USE statement, check the server:&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;IF @@Server = 'DevServerName' USE DevDatabase&lt;/div&gt;&lt;br /&gt;Depending on what you're doing in your code and how potentially destructive it is, you can do some pretty clever things to help prevent problems. Use the tools at your disposal.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/866188004456276207-7143585436110283731?l=squaredthoughts.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://squaredthoughts.blogspot.com/feeds/7143585436110283731/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=866188004456276207&amp;postID=7143585436110283731' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/7143585436110283731'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/7143585436110283731'/><link rel='alternate' type='text/html' href='http://squaredthoughts.blogspot.com/2008/05/some-pain-is-avoidable-so-spare.html' title='Some Pain Is Avoidable - So Spare Yourself'/><author><name>ESquared</name><uri>http://www.blogger.com/profile/09187680112940803740</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='05197177171442690440'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-866188004456276207.post-8444316478362294078</id><published>2008-04-18T16:47:00.000-07:00</published><updated>2008-05-16T09:58:05.426-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='storage bytes'/><category scheme='http://www.blogger.com/atom/ns#' term='char'/><category scheme='http://www.blogger.com/atom/ns#' term='wasted space'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL Server'/><category scheme='http://www.blogger.com/atom/ns#' term='varchar'/><title type='text'>SQL Waste Land</title><content type='html'>Welcome to Waste Land, the place where your server is full of &lt;em&gt;char&lt;/em&gt; and &lt;em&gt;nchar&lt;/em&gt; columns instead of &lt;em&gt;varchar&lt;/em&gt; and &lt;em&gt;nvarchar&lt;/em&gt; columns. In case you're not familiar with those, the difference between the two sets of data types is that the ones that aren't varying (don't have the letters "var" in the data type name) always consume a fixed amount of space. So if you define a column in a table as char(2) and then insert the value 'a', the database actually stores 'a ' with an extra space.&lt;br /&gt;&lt;br /&gt;In SQL Server 2008, when you turn on rowlevel and page compression, it will treat char as varchar and will also compress values. But until then, char columns waste space.&lt;br /&gt;&lt;br /&gt;Inspired by the wastefulness of a large database I work with, I wrote some code to find out: &lt;strong&gt;just how much of the database is wasted space?&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;First, some prerequisite functions:&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[TokenItem]') and xtype in (N'FN', N'IF', N'TF'))&lt;br /&gt;drop function [dbo].[TokenItem]&lt;br /&gt;GO&lt;br /&gt;/* Function TokenItem&lt;br /&gt;Author: Erik E&lt;br /&gt;Returns the [TokenNum]th item from a string&lt;br /&gt;For example&lt;br /&gt;PRINT dbo.TokenItem('The quick brown fox jumped over the lazy dog', 4, ' ')&lt;br /&gt;will return the fourth token of the string, with tokens identified as delimited by spaces: fox&lt;br /&gt;*/&lt;br /&gt;CREATE FUNCTION TokenItem (&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@String varchar(8000),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@TokenNum int,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Delimiter varchar(8000) = ','&lt;br /&gt;)&lt;br /&gt;RETURNS varchar(8000)&lt;br /&gt;AS&lt;br /&gt;BEGIN&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;DECLARE @DelimLen int&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;SET @DelimLen = Datalength(@Delimiter)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;IF @TokenNum &lt; 1 OR @TokenNum &gt; 1 + (Datalength(@String) - Datalength(Replace(@String, @Delimiter, ''))) / @DelimLen&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;RETURN ''&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;DECLARE @Pos int&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;SET @Pos = 1&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHILE @TokenNum &gt; 1 BEGIN&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SET @Pos = CharIndex(@Delimiter, @String, @Pos) + @DelimLen&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SET @TokenNum = @TokenNum - 1&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;END&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;RETURN SubString(@String, @Pos, CharIndex(@Delimiter, @String + @Delimiter, @Pos) - @Pos)&lt;br /&gt;END&lt;/div&gt;&lt;br /&gt;&lt;div class="codebox"&gt;if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[TokenItemRev]') and xtype in (N'FN', N'IF', N'TF'))&lt;br /&gt;drop function [dbo].[TokenItemRev]&lt;br /&gt;GO&lt;br /&gt;CREATE FUNCTION TokenItemRev (&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@String varchar(8000),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@TokenNum int,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Delimiter varchar(1) = ','&lt;br /&gt;)&lt;br /&gt;RETURNS varchar(8000)&lt;br /&gt;/*&lt;br /&gt;Returns the @TokenNum-th token as separated by @Delimiter, counting from the last token&lt;br /&gt;Returns an empty string if no token by that number exists (&lt; 1 or &gt; tokencount)&lt;br /&gt;Example:&lt;br /&gt;   PRINT dbo.TokenItemRev('a,b,c,d,e', 2, ',')&lt;br /&gt;   returns 'd'&lt;br /&gt;*/&lt;br /&gt;AS&lt;br /&gt;BEGIN&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;RETURN Reverse(dbo.TokenItem(Reverse(@String), @TokenNum, @Delimiter))&lt;br /&gt;END&lt;/div&gt;&lt;br /&gt;Now the real meat of the tool.&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[SpaceCharInfoCollectBackup]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)&lt;br /&gt;drop procedure [dbo].[SpaceCharInfoCollect]&lt;br /&gt;GO&lt;br /&gt;/*************************************************************************************&lt;br /&gt;Procedure:&amp;nbsp;&amp;nbsp;&amp;nbsp;SpaceCharInfoCollect&lt;br /&gt;Version:&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1.2&lt;br /&gt;Date:&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;8/11/2006&lt;br /&gt;Author:&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Erik E&lt;br /&gt;&lt;br /&gt;Description:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Find out how much wasted space there is in a database,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;because of the use of char or nchar columns instead of varchar or nvarchar.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Doesn't support object names containing periods.&lt;br /&gt;&lt;br /&gt;Parameters:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;TableName&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Table to analyze for space information. Since it defaults to the&lt;br /&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;database the stored procedure is in, include the database name&lt;br /&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;if the stored procedure is being run from a different database,&lt;br /&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;as in database.owner.table. Can also specify owner.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;DestTableName&amp;nbsp;&amp;nbsp;Table to receive the result information, can be in another database&lt;br /&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;on the same server. Use database.owner.table if desiring a table&lt;br /&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;in a different database than the stored procedure is located in or&lt;br /&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;if you want a different owner.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;DateStamp&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Time to store as the data collection time. Defaults to GetDate().&lt;br /&gt;&lt;br /&gt;Planned Updates:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;A version that runs against all databases on a server, without using undocumented&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;stored procedures (See SP SpaceInfoCharCollectAllDB).&lt;br /&gt;&lt;br /&gt;Update History:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;1.0 &lt;8/11/2006&gt;:&amp;nbsp;&amp;nbsp;&amp;nbsp;Original version&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;1.1 &lt;10/13/2006&gt;:&amp;nbsp;&amp;nbsp;Updated to accept destination table, and database name for&lt;br /&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;table being reported on. Added database name to results table.&lt;br /&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;Added object owner logic and column to results table.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;1.2 &lt;4/18/2007&gt;:&amp;nbsp;&amp;nbsp;&amp;nbsp;Used numbers instead of names for column selection to reduce&lt;br /&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;script size, to handle more char columns in a table without errors&lt;br /&gt;&lt;br /&gt;Examples:&lt;br /&gt;USE ThisDatabase&lt;br /&gt;EXECUTE SpaceInfoCharCollectAllDB&lt;br /&gt;&lt;br /&gt;USE DesiredDatabase&lt;br /&gt;EXECUTE sp_msforeachtable 'EXECUTE DatabaseOfThisSP.dbo.SpaceCharInfoCollect ''DesiredDatabase.?'', ''DesiredDatabase.dbo.SpaceCharInfo'''&lt;br /&gt;*************************************************************************************/&lt;br /&gt;&lt;br /&gt;CREATE PROCEDURE SpaceCharInfoCollect&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@TableName varchar(8000), -- table to analyze space information on, include databasename, use brackets if name has weird characters&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@DestTableName varchar(8000), -- table to receive the result information, can be in another database on the same server&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@DateStamp datetime = NULL, -- defaults to GetDate(),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Debug bit = 0&lt;br /&gt;AS&lt;br /&gt;DECLARE&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@NSQL nvarchar(4000),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@PreSQL varchar(8000),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@SQL1 varchar(8000),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@MidSQL1 varchar(8000),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@SQL2 varchar(8000),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@MidSQL2 varchar(8000),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@SQL3 varchar(8000),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@PostSQL varchar(8000),&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@DB varchar(8000),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Owner varchar(8000),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Table varchar(8000),&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@DestDB varchar(8000),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@DestOwner varchar(8000),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@DestTable varchar(8000)&lt;br /&gt;&lt;br /&gt;SET NOCOUNT ON&lt;br /&gt;&lt;br /&gt;PRINT @TableName&lt;br /&gt;&lt;br /&gt;IF @DateStamp IS NULL SET @DateStamp = GetDate()&lt;br /&gt;&lt;br /&gt;SET @TableName = Replace(@TableName, '''', '''''')&lt;br /&gt;SET @Table = &lt;strong&gt;WhatDatabase&lt;/strong&gt;.dbo.TokenItemRev(@TableName, 1, '.')&lt;br /&gt;SET @Owner = &lt;strong&gt;WhatDatabase&lt;/strong&gt;.dbo.TokenItemRev(@TableName, 2, '.')&lt;br /&gt;IF @Owner = '' SET @Owner = user&lt;br /&gt;IF @Owner LIKE '[[]%]' SET @Owner = SubString(@Owner, 2, Len(@Owner) - 2)&lt;br /&gt;SET @DB = &lt;strong&gt;WhatDatabase&lt;/strong&gt;.dbo.TokenItemRev(@TableName, 3, '.')&lt;br /&gt;IF @DB = '' SET @DB = Replace(DB_Name(), '''', '''''')&lt;br /&gt;IF @DB LIKE '[[]%]' SET @DB = SubString(@DB, 2, Len(@DB) - 2)&lt;br /&gt;&lt;br /&gt;IF @Table LIKE '[[]%]' SET @Table = SubString(@Table, 2, Len(@Table) - 2)&lt;br /&gt;&lt;br /&gt;SET @DestTableName = Replace(@DestTableName, '''', '''''')&lt;br /&gt;SET @DestTable = &lt;strong&gt;WhatDatabase&lt;/strong&gt;.dbo.TokenItemRev(@DestTableName, 1, '.')&lt;br /&gt;SET @DestOwner = &lt;strong&gt;WhatDatabase&lt;/strong&gt;.dbo.TokenItemRev(@DestTableName, 2, '.')&lt;br /&gt;IF @DestOwner = '' SET @DestOwner = user&lt;br /&gt;SET @DestDB = &lt;strong&gt;WhatDatabase&lt;/strong&gt;.dbo.TokenItemRev(@DestTableName, 3, '.')&lt;br /&gt;&lt;br /&gt;SET @SQL1 = 'IF NOT EXISTS (SELECT * FROM '&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;+ @DestDB&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;+ '.Information_Schema.Tables WHERE Table_Name = ''' + @DestTable + ''' AND Table_Schema = ''' + @DestOwner + ''')&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;SELECT&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SpaceDate = convert(datetime, NULL),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DatabaseName = convert(varchar(128), NULL),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;OwnerName = convert(varchar(128), NULL),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;TableName = convert(varchar(128), NULL),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ColumnName = convert(varchar(128), NULL),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ExtraSpaces = convert(bigint, 0),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;RowCnt = convert(bigint, 0)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;INTO ' + @DestTableName + '&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHERE 1 = 0'&lt;br /&gt;&lt;br /&gt;EXECUTE (@SQL1)&lt;br /&gt;&lt;br /&gt;SET @PreSQL = 'INSERT ' + @DestTableName + ' (SpaceDate, DatabaseName, OwnerName, TableName, ColumnName, ExtraSpaces, RowCnt)&lt;br /&gt;SELECT&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;SpaceDate = ''' + Convert(varchar(50), @DateStamp, 121) + ''',&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;DatabaseName = ''' + @DB + ''',&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;OwnerName = ''' + @Owner + ''',&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;TableName = ''' + Replace(@Table, '''', '''''') + ''',&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;ColumnName,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;ExtraSpaces = CASE ColumnNum'&lt;br /&gt;SET @MidSQL1 = '&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;END,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;RowCnt&lt;br /&gt;FROM&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SELECT'&lt;br /&gt;SET @MidSQL2 = ',&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;RowCnt = Count(*)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FROM ' + Replace(@TableName, '''', '''''') + ' WITH (NOLOCK)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;) X CROSS JOIN ('&lt;br /&gt;SET @PostSQL = '&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;) Y (ColumnNum, ColumnName)'&lt;br /&gt;&lt;br /&gt;SET @SQL1 = ''&lt;br /&gt;&lt;br /&gt;SET @NSQL =&lt;br /&gt;N'SELECT&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@SQL1 = @SQL1 + ''&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN '' + Convert(varchar(11), Ordinal_Position) + '' THEN ['' + Replace(Column_Name, '''''''', '''''''''''') + '']''&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;+ CASE WHEN Data_type = ''nchar'' THEN ''* 2'' ELSE '''' END,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@SQL2 = IsNull(@SQL2 + '','', '''') + ''&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;['' + Replace(Column_Name, '''''''', '''''''''''') + ''] = Sum(IsNull('' + LTrim(Str(Character_Maximum_Length)) + '' - Len(['' + Replace(Column_Name, '''''''', '''''''''''') + '']), 0))'',&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@SQL3 = IsNull(@SQL3, '''') + ''&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'' + CASE WHEN @SQL3 IS NULL THEN '''' ELSE ''UNION ALL '' END + ''SELECT ''&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;+ Convert(varchar(11), Ordinal_Position) + '', ''&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;+ '''''''' + Replace(Column_Name, '''''''', '''''''''''') + ''''''''&lt;br /&gt;FROM&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;' + QuoteName(@DB) + '.Information_Schema.[Columns] C&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;INNER JOIN ' + QuoteName(@DB) + '.Information_Schema.Tables T ON C.Table_Name = T.Table_Name and Table_Type = ''base table''&lt;br /&gt;WHERE&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Data_Type in (''char'', ''nchar'')&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;AND T.Table_Name = ''' + @Table + '''&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;AND T.Table_Schema = ''' + @Owner + ''''&lt;br /&gt;&lt;br /&gt;IF @Debug = 1 PRINT @NSQL&lt;br /&gt;&lt;br /&gt;EXECUTE sp_executesql @NSQL, N'@SQL1 varchar(8000) OUTPUT, @SQL2 varchar(8000) OUTPUT, @SQL3 varchar(8000) OUTPUT', @SQL1 OUTPUT, @SQL2 OUTPUT, @SQL3 OUTPUT&lt;br /&gt;IF @SQL3 IS NULL RETURN 0 --no columns found&lt;br /&gt;&lt;br /&gt;IF @Debug = 1&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;PRINT (@PreSQL + @SQL1 + @MidSQL1 + @SQL2 + @MidSQL2 + @SQL3 + @PostSQL)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;ELSE EXECUTE (@PreSQL + @SQL1 + @MidSQL1 + @SQL2 + @MidSQL2 + @SQL3 + @PostSQL)&lt;br /&gt;&lt;br /&gt;RETURN 0&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;--Compare before and after values from most recent two run dates:&lt;br /&gt;SELECT&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;DatabaseName = IsNull(x.databasename, y.databasename),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;OwnerName = IsNull(x.ownername, y.ownername),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;TableName = IsNull(x.tablename, y.ownername),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;ColumnName = IsNull(x.columnname, y.columnname),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;ExtraSpaces1 = Sum(x.extraspaces),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;ExtraSpaces2 = Sum(y.extraspaces),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;RowCnt1 = Min(x.rowcnt), -- / count(distinct IsNull(x.databasename, y.databasename) + IsNull(x.ownername, y.ownername) + IsNull(x.tablename, y.ownername)),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;RowCnt2 = Min(y.rowcnt), --/ count(distinct IsNull(x.databasename, y.databasename) + IsNull(x.ownername, y.ownername) + IsNull(x.tablename, y.ownername))&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;CollectionTime1 = Min(x.Spacedate),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;CollectionTime2 = Min(y.Spacedate)&lt;br /&gt;FROM&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SELECT * FROM dbo.spacecharinfo d1 WHERE spacedate = (select max(spacedate) from dbo.spacecharinfo where spacedate &lt; (select max(spacedate) from dbo.spacecharinfo))&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;) x&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;FULL JOIN (&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SELECT * FROM dbo.spacecharinfo d1 WHERE spacedate = (select max(spacedate) from dbo.spacecharinfo)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;) y ON x.databasename = y.databasename AND x.ownername = y.ownername AND x.tablename = y.tablename AND x.columnname = y.columnname&lt;br /&gt;GROUP BY&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;IsNull(x.databasename, y.databasename),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;IsNull(x.ownername, y.ownername),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;IsNull(x.tablename, y.ownername),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;IsNull(x.columnname, y.columnname)&lt;br /&gt;WITH ROLLUP&lt;br /&gt;ORDER BY&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;IsNull(x.databasename, y.databasename),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;IsNull(x.ownername, y.ownername),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;IsNull(x.tablename, y.ownername),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;IsNull(x.columnname, y.columnname)&lt;br /&gt;&lt;br /&gt;select&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;spacedate,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;databasename,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;ExtraSpaces = sum(extraspaces),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;[Rows] = sum(rowcnt),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;[Average spaces per row] = convert(float, sum(extraspaces)) / convert(float, sum(rowcnt)),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;[Average spaces per column] = convert(float, avg(extraspaces))&lt;br /&gt;from spacecharinfo&lt;br /&gt;group by spacedate, databasename&lt;br /&gt;with rollup&lt;br /&gt;having grouping(spacedate) = 0&lt;br /&gt;*/&lt;/div&gt;&lt;br /&gt;&lt;div class="codebox"&gt;if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[SpaceInfoCollectAllDB]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)&lt;br /&gt;drop procedure [dbo].[SpaceInfoCollectAllDB]&lt;br /&gt;GO&lt;br /&gt;-- runs SpaceInfoCollect against all databases and tables, see that SP for more information&lt;br /&gt;CREATE PROCEDURE SpaceInfoCollectAllDB(@DestTableName nvarchar(4000) = NULL)&lt;br /&gt;AS&lt;br /&gt;SET NOCOUNT ON&lt;br /&gt;IF @DestTableName IS NULL SET @DestTableName = QuoteName(DB_Name()) + '.' + QuoteName(User) + '.SpaceCharInfo'&lt;br /&gt;DECLARE&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@SQL nvarchar(4000),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@DBName nvarchar(128),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Dt datetime&lt;br /&gt;SET @Dt = GetDate()&lt;br /&gt;SET @SQL =&lt;br /&gt;'SELECT ''USE '' + [name] + '' EXECUTE sp_msforeachtable ''''EXECUTE ''&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;+ QuoteName(DB_Name()) + ''.dbo.SpaceCharInfoCollect '''''''''' + QuoteName(Replace([name], '''''''', '''''''''''')) + ''.''&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;+ Char(63) + '''''''''', ''''''''&lt;b&gt;YourDesiredDefaultDatabase&lt;/b&gt;.dbo.SpaceCharInfo'''''''', ''''''''' + Convert(varchar(50), @Dt, 121) + '''''''''''''''&lt;br /&gt;FROM master.dbo.sysdatabases&lt;br /&gt;WHERE [name] NOT IN (''master'', ''model'', ''tempdb'', ''pubs'', ''msdb'')'&lt;br /&gt;&lt;br /&gt;SET @DBName = DB_Name()&lt;br /&gt;&lt;br /&gt;EXECUTE sp_executesql N'EXECUTE master.dbo.xp_execresultset @SQL, @DBName', N'@SQL nvarchar(4000), @DBName nvarchar(128)', @SQL, @DBName&lt;br /&gt;SET @SQL = 'SELECT * FROM ' + @DestTableName + ' WHERE SpaceDate = ''' + Convert(varchar(50), @Dt, 121) + ''''&lt;br /&gt;SET NOCOUNT OFF&lt;br /&gt;EXECUTE (@SQL)&lt;/div&gt;&lt;br /&gt;Whew! Now how do you use all that? Easy.&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;EXECUTE SpaceInfoCollectAllDB&lt;/div&gt;&lt;br /&gt;When you're done, take a look at the code at the end of the SpaceCharInfoCollect to see some final analysis tools for the output table. The database I ran this on had a whopping 10 GB of extra spaces, which is something like 14% of the entire database size.&lt;br /&gt;&lt;br /&gt;Yes, the dynamic SQL is really difficult to work with. Making major changes directly in the script is nearly impossible: it's much easier to grab the section of script you want to change, paste it into a new window, and replace doubled single-quotes with single single-quotes. Which of course doesn't work for embedded values from the full outer script, but it helps immensely. Then when you have what you need, you double them back up and paste in just the parts you changed. At times you have to go two levels of dynamic SQL deep (or more) and undouble/double your quotes at each step. Use the power of search and replace to help you.&lt;br /&gt;&lt;br /&gt;In some cases for small changes it does work to use the debug mode to output the final script and then examine or try to run THAT, and iteratively modify the dynamic SQL and see what you get.&lt;br /&gt;&lt;br /&gt;If you find any bugs, please let me know. I haven't tested this on nchar columns, but it should multiply the space used by 2 since we care about bytes rather than characters.&lt;br /&gt;&lt;br /&gt;P.S. There are 15 single quotes in a row in this script. Wheeeeeeeee.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/866188004456276207-8444316478362294078?l=squaredthoughts.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://squaredthoughts.blogspot.com/feeds/8444316478362294078/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=866188004456276207&amp;postID=8444316478362294078' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/8444316478362294078'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/8444316478362294078'/><link rel='alternate' type='text/html' href='http://squaredthoughts.blogspot.com/2008/04/sql-waste-land.html' title='SQL Waste Land'/><author><name>ESquared</name><uri>http://www.blogger.com/profile/09187680112940803740</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='05197177171442690440'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-866188004456276207.post-452999327556818534</id><published>2008-04-04T13:13:00.000-07:00</published><updated>2008-04-04T13:59:50.714-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='time'/><category scheme='http://www.blogger.com/atom/ns#' term='UTC time'/><category scheme='http://www.blogger.com/atom/ns#' term='daylight savings time'/><title type='text'>Database Time Storage and Display Problems</title><content type='html'>After seeing a post about UTC time in a forum, I did some more searching and reading on the web, and found an article, &lt;a href="http://geekswithblogs.net/ewright/archive/2004/09/14/11180.aspx"&gt;UTC datetime values in SQL Server 2000&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Earlier today I had the same basic idea as the one presented in that blog, though a little more simply:&lt;br /&gt;&lt;div class="codebox"&gt;SELECT UTCDateColumn + Getdate() - Getutcdate()&lt;/div&gt;&lt;br /&gt;In the comments on the blog entry is a function someone tried to build that would automatically adjust for &lt;a href="http://en.wikipedia.org/wiki/Daylight_savings_time"&gt;daylight savings time&lt;/a&gt; as well. This immediately struck me as problematic, and this was borne out by further comments such as that the rules are different for Arizona. And to not be overly self-centric, what about the majority of the people in the world who do not observe DST at all? Follow the link in this paragraph to see a map of the world usage of DST.&lt;br /&gt;&lt;br /&gt;There were additional problems. One commenter said, "Say it's currently summer and I'm in daylight savings time, my difference will include DST. Now I try to convert a date from 6 months ago, I'll be an hour out."&lt;br /&gt;&lt;br /&gt;So here are my rambling thoughts on the subject for your amusement (at me or with me, your choice).&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;So you want to know the time. The time &lt;strong&gt;where&lt;/strong&gt; and in what time reference? Is it at the client? Is it at the server? Is it at the place the time event corresponds to like the cash machine where a withdrawal was made?&lt;/li&gt;&lt;br /&gt;&lt;li&gt;If you have web clients around the world, using the database to calculate "The Time" is pointless.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Even if your client location(s) only span one time zone (and can you guarantee this forever) are you sure that client will always be in the same time zone as the server?&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The various methods to automatically calculate the UTC differential on the server are clever but with DST are exposed for being completely inadequate, because all one is answering is, what is the current UTC differential, including DST, at the &lt;strong&gt;server's&lt;/strong&gt; location? One cannot reasonably expect that to apply to all clients and for all date ranges in the database.&lt;/li&gt;&lt;/ol&gt;In my mind, the correct answer is to let the user choose his UTC differential in the client, or let the client autodiscover the differential (through a server getutcdate(), or connecting to a time server, or reading the offset out of the registry, for example).&lt;br /&gt;&lt;br /&gt;Handling older dates that were stored during a different phase of DST is an interesting problem. Are we answering the question "what was the time then in terms of our time now" or "what was the time then in terms of the time then?" And the time now/then where?&lt;br /&gt;&lt;br /&gt;Before you answer that seemingly easy question overquickly, think about the 1 hour period just after DST adjustment makes the time jump back and the same hour is repeated. Imagine you're reviewing security logs for a crime event. Your system reports:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;2:05 am - last person to exit the facility was researcher John Smith&lt;/li&gt;&lt;br /&gt;&lt;li&gt;2:20 am - critical research samples verified to be secure&lt;/li&gt;&lt;br /&gt;&lt;li&gt;2:35 am - later investigation pinpoints this as the time critical research samples were known to be missing&lt;/li&gt;&lt;/ul&gt;If the system is adjusting times for "what was the time then" then you can't know whether John Smith left the building before the samples were verified secure, or if he left after they were stolen, because there were in fact two segments of time that night covering 2 am - 2:59:59 am, and he could have cleverly left the building during the second hour in order to fool the computer systems.&lt;br /&gt;&lt;br /&gt;Anyway, if you set a concern like this aside and still wish to adjust for the time things were when recorded, I can't see how this can be accomplished &lt;emph&gt;except on the client&lt;/emph&gt; because only the client can know the actual location of the client and his desired DST rules. Unless you keep a comprehensive and carefully-updated catalog of the DST rules everywhere your clients can possibly be, this sounds like a challenge.&lt;br /&gt;&lt;br /&gt;Maybe the answer is to store both dates in the database: the client time AND UTC time, or UTC time and client offset at that time. This data doesn't even have to be in the individual rows: it could be captured or updated in a UTC-offset history table for each client location every time a client connects. It has its own problems like: does the client know his location, UTC offset, and DST rules &amp; current phase correctly? If so, can the client always be trusted to present this information accurately or could the user purposefully taint the data?&lt;br /&gt;&lt;br /&gt;There is even more complication:&lt;br /&gt;&lt;br /&gt;What if you are in some place that doesn't observe DST and you want to compare transactions from months ago that were recorded for two different places: your current location and a location that is in a different time zone and does observe DST.&lt;br /&gt;&lt;br /&gt;What consistent method of adjustment do you plan to use to display all the times in the same list? Clearly they have to be adjusted the same for any sort of sensical ordering or comparison, and it makes sense to adjust them for the observer so he can view them in his own time. Yet now, adjusting the historical date of the time recorded for the observing-DST location has to be done in terms of the viewing location's DST rules, which can also lead to incorrect assumptions.&lt;br /&gt;&lt;br /&gt;So yet again it seems that time data needs to either be stored in UTC and viewed in UTC with an adjustment for the CURRENT offset only and the user knowing this information and making allowances for it, or time data needs to be stored in UTC and ALSO the client's time and the client's location, in some format or other, so that true reconstruction of "the time" can be answered from every perspective needed: any location, observing any DST rules, during any DST phase. At historical review time, there is a huge list of possible dates to display for a single recorded UTC time:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The UTC time of the event (the one place where all the madness is overcome!)&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The time of the event at the event location, using the DST offset of the event location then&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The time of the event at the event location, using the DST offest of the event location now&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The time of the event at the user's location, using the DST offset of the user location then&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The time of the event at the user's location, using the DST offset of the user location now&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The time of the event at the event location, using the DST offset of the user location then&lt;/li&gt;&lt;br /&gt;&lt;li&gt;The time of the event at the event location, using the DST offset of the user location now&lt;/li&gt;&lt;/ul&gt;Yikes!&lt;br /&gt;&lt;br /&gt;In closing, the getdate() - getutcdate() idea might be useful in conjunction with the client's stated known offset to determine if the client is observing DST *now*, but by itself can never solve the problem of fully-sensible historical time display in any time zone observing any DST rules (that themselves change over time).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/866188004456276207-452999327556818534?l=squaredthoughts.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://squaredthoughts.blogspot.com/feeds/452999327556818534/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=866188004456276207&amp;postID=452999327556818534' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/452999327556818534'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/452999327556818534'/><link rel='alternate' type='text/html' href='http://squaredthoughts.blogspot.com/2008/04/database-time-storage-and-display.html' title='Database Time Storage and Display Problems'/><author><name>ESquared</name><uri>http://www.blogger.com/profile/09187680112940803740</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='05197177171442690440'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-866188004456276207.post-6420193999378126063</id><published>2008-01-09T09:58:00.000-08:00</published><updated>2008-01-09T14:20:15.787-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='percent'/><category scheme='http://www.blogger.com/atom/ns#' term='modulo'/><category scheme='http://www.blogger.com/atom/ns#' term='expressive statements'/><category scheme='http://www.blogger.com/atom/ns#' term='%'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL Server'/><title type='text'>What's the Percent Character (%) in SQL?</title><content type='html'>&lt;style type="text/css"&gt;.c {color:#660000;}&lt;/style&gt;&lt;br /&gt;% - the percent character in SQL is the modulo (aka modulus) operator, which means give the remainder of a division. For example, 11 divided by 4 is 2 with a remainder of 3. It's the 3 that we're interested in.&lt;br /&gt;&lt;br /&gt;I think about the operation in a few different ways. Depending on what form your numbers are in and what functions are available to you, or depending on which strikes you as the most natural, one of these might be more helpful to you than the others.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Form 1&lt;/strong&gt; - modulo is to subtract from &lt;span class="c"&gt;n&lt;/span&gt; the highest multiple of &lt;span class="c"&gt;x&lt;/span&gt; that is less than &lt;span class="c"&gt;n&lt;/span&gt;:&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;n % x  = n – floor(n / x)&lt;/div&gt;&lt;br /&gt;&lt;strong&gt;Form 2&lt;/strong&gt; - modulo is to divide &lt;span class="c"&gt;n&lt;/span&gt; by &lt;span class="c"&gt;x&lt;/span&gt;, remove the whole number portion, then multiply by &lt;span class="c"&gt;x&lt;/span&gt; again:&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;n % x = (n / x - floor(n / x)) * x&lt;/div&gt;&lt;br /&gt;&lt;strong&gt;Form 3&lt;/strong&gt; - modulo is, using a clock with &lt;span class="c"&gt;x&lt;/span&gt; numbers (from &lt;span class="c"&gt;0&lt;/span&gt; to &lt;span class="c"&gt;x - 1&lt;/span&gt;), to start on &lt;span class="c"&gt;0&lt;/span&gt; and take &lt;span class="c"&gt;n&lt;/span&gt; steps, then look at what number we end up on.&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;Operation: 11 % 4&lt;br /&gt;Okay. Here's a 4-numbered clock to count on:&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;0&lt;br /&gt;3&amp;nbsp;&amp;nbsp;&amp;nbsp;1&lt;br /&gt;&amp;nbsp;&amp;nbsp;2&lt;br /&gt;&lt;br /&gt;Starting at &lt;span class="c"&gt;0&lt;/span&gt; we take &lt;span class="c"&gt;11&lt;/span&gt; clockwise steps and see that we end up on &lt;span class="c"&gt;3&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;So 11 mod 4 = 3.&lt;/div&gt;&lt;br /&gt;Here are some syntax notes for other programming environments:&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;n mod x &lt;span class="c"&gt;&lt;- how it’s written in VB&lt;/span&gt;&lt;br /&gt;mod(n, x) &lt;span class="c"&gt;&lt;- how it’s written in Excel&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;It’s a nice way to get the remainder without having to use &lt;span class="c"&gt;n&lt;/span&gt; twice in an expression.&lt;br /&gt;&lt;br /&gt;Examples:&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;10 % 10 = 0&lt;br /&gt;10 % 9 = 1&lt;br /&gt;10 % 8 = 2&lt;br /&gt;10 % 7 = 3&lt;br /&gt;10 % 6 = 4&lt;br /&gt;10 % 5 = 0&lt;/div&gt;&lt;br /&gt;It helps to keep in mind that the result will always be in the range &lt;span class="c"&gt;0 to (x – 1)&lt;/span&gt;. &lt;span class="c"&gt;&lt;em&gt;Anything&lt;/em&gt; mod 10&lt;/span&gt; will always end up in the range &lt;span class="c"&gt;0 to 9&lt;/span&gt;. Modulo has the same precedence as multiply and divide (at least in SQL): lower than negation and bitwise not but higher than everything else.&lt;br /&gt;&lt;br /&gt;If you want to add 1 to an hour value of &lt;span class="c"&gt;0 to 23&lt;/span&gt; and not just end up with &lt;span class="c"&gt;1 – 24&lt;/span&gt;, modulo is a great way to do that.&lt;br /&gt;&lt;br /&gt;For an hour value that goes from 0 to 23, to add one hour:&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;(Hr + 1) % 24 -&gt; 0 to 23 -&gt; 1 to 24 -&gt; 1 to 23, 0 &lt;span class="c"&gt;(0 to 23 again, rotated up one)&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;For an hour value that goes from 1 to 24, to add one hour:&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;Hr % 24 + 1 -&gt; 1 to 24 -&gt; 1 to 23, 0 -&gt; 2 to 24, 1 &lt;span class="c"&gt;(1 to 24 again, rotated up one)&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;Since the modulo of negative numbers can give unexpected results depending on the programming language (&lt;span class="c"&gt;-1 % x&lt;/span&gt; could be &lt;span class="c"&gt;1&lt;/span&gt; or it could be &lt;span class="c"&gt;x – 1&lt;/span&gt;, e.g., &lt;span class="c"&gt;-1 mod 5&lt;/span&gt; could be &lt;span class="c"&gt;1&lt;/span&gt; or &lt;span class="c"&gt;4&lt;/span&gt;) you just add a multiple of &lt;span class="c"&gt;x&lt;/span&gt; (since that won't change the remainder but will get us out of negative territory), then subtract what you need. Usually you only need &lt;span class="c"&gt;x&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;Here’s an example of finding last month’s month number from this month’s month number. Note that when you have ranges that start with 1 instead of 0, sometimes you have to subtract 1 before the modulo and then add 1 after the modulo.&lt;br /&gt;&lt;br /&gt;Note: we’re subtracting one from the month, then adding 12 so we don’t get negative numbers. We also subtract 1 before the modulo and add 1 after the modulo (to convert the 1-based range to a 0-based range and back).&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;(Month – 1 + 12 - 1) % 12 + 1&lt;br /&gt;&lt;span class="c"&gt;which simplified is:&lt;/span&gt;&lt;br /&gt;(Month + 10) % 12 + 1 -&gt; 1 to 12 -&gt; 11 to 22 -&gt; 11, 0 to 10 -&gt; 12, 1 to 11&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/866188004456276207-6420193999378126063?l=squaredthoughts.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://squaredthoughts.blogspot.com/feeds/6420193999378126063/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=866188004456276207&amp;postID=6420193999378126063' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/6420193999378126063'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/6420193999378126063'/><link rel='alternate' type='text/html' href='http://squaredthoughts.blogspot.com/2008/01/whats-percent-character-in-sql.html' title='What&apos;s the Percent Character (%) in SQL?'/><author><name>ESquared</name><uri>http://www.blogger.com/profile/09187680112940803740</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='05197177171442690440'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-866188004456276207.post-7381571960255457912</id><published>2008-01-07T16:57:00.001-08:00</published><updated>2008-01-08T10:06:04.463-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='SQL Confusion'/><title type='text'>SQL Trouble</title><content type='html'>The troubled code:&lt;br /&gt;&lt;div class="codebox"&gt;CASE&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &lt;=0030 THEN '0000'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;0031 AND theTime &lt;=0130 THEN '0100' &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;0131 AND theTime &lt;=0230 THEN '0200'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;0231 AND theTime &lt;=0330 THEN '0300'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;0331 AND theTime &lt;=0430 THEN '0400'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;0431 AND theTime &lt;=0530 THEN '0500' &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;0531 AND theTime &lt;=0630 THEN '0600'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;0631 AND theTime &lt;=0730 THEN '0700'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;0731 AND theTime &lt;=0830 THEN '0800'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;0831 AND theTime &lt;=0930 THEN '0900' &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;0931 AND theTime &lt;=1030 THEN '1000'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;1031 AND theTime &lt;=1130 THEN '1100'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;1131 AND theTime &lt;=1230 THEN '1200'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;1231 AND theTime &lt;=1330 THEN '1300' &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;1331 AND theTime &lt;=1430 THEN '1400'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;1431 AND theTime &lt;=1530 THEN '1500'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;1531 AND theTime &lt;=1630 THEN '1600'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;1631 AND theTime &lt;=1730 THEN '1700' &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;1731 AND theTime &lt;=1830 THEN '1800'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;1831 AND theTime &lt;=1930 THEN '1900'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;1931 AND theTime &lt;=2030 THEN '2000'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;2031 AND theTime &lt;=2130 THEN '2100' &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;2131 AND theTime &lt;=2230 THEN '2200'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;2231 AND theTime &lt;=2330 THEN '2300'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;2331 THEN '0000'&lt;br /&gt;END as GroupedTime&lt;/div&gt;&lt;br /&gt;First of all, the logic excludes every number ending in 31. The result for those will be NULL. Oops.&lt;br /&gt;&lt;br /&gt;So here's improvement #1:&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;GroupedTime =&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;CASE&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;= 2231 THEN '2300'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;= 2131 THEN '2200'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;= 2031 THEN '2100'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;= 1931 THEN '2000'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;= 1831 THEN '1900'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;= 1731 THEN '1800'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;= 1631 THEN '1700'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;= 1531 THEN '1600'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;= 1431 THEN '1500'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;= 1331 THEN '1400'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;= 1231 THEN '1300'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;= 1131 THEN '1200'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;= 1031 THEN '1100'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;= 0931 THEN '1000'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;= 0831 THEN '0900'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;= 0731 THEN '0800'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;= 0631 THEN '0700'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;= 0531 THEN '0600'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;= 0431 THEN '0500'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;= 0331 THEN '0400'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;= 0231 THEN '0300'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;= 0131 THEN '0200'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN theTime &gt;= 0031 THEN '0100'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ELSE '0000'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;END&lt;/div&gt;&lt;br /&gt;But here's improvement #2. Now, oddly enough, theTime is a varchar column (it was extracted from a datetime column as varchar for some reason, but I'm working to try to fix that).&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;Groupedtime = Right('0' + Convert(varchar(2), (Left(theTime, 2) + CASE WHEN Right(theTime, 2) &gt;= 31 THEN 1 ELSE 0 END) % 24), 2) + '00'&lt;/div&gt;&lt;br /&gt;More improvements can be made if the theTime column comes in as datetime or a proper numeric such as minutes (instead of the display varchar number where '0100' means 1&amp;nbsp;o'clock or 60 minutes, not 100 minutes. I will be making that change since the source of the data is also able to be changed.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/866188004456276207-7381571960255457912?l=squaredthoughts.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://squaredthoughts.blogspot.com/feeds/7381571960255457912/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=866188004456276207&amp;postID=7381571960255457912' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/7381571960255457912'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/7381571960255457912'/><link rel='alternate' type='text/html' href='http://squaredthoughts.blogspot.com/2008/01/sql-trouble.html' title='SQL Trouble'/><author><name>ESquared</name><uri>http://www.blogger.com/profile/09187680112940803740</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='05197177171442690440'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-866188004456276207.post-7122178931783977524</id><published>2008-01-07T16:44:00.000-08:00</published><updated>2008-01-08T10:09:19.796-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='SQL Confusion'/><title type='text'>SQL Puzzlement</title><content type='html'>The tortured SQL I find in production:&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;CASE WHEN ExtendedDescription is null&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;THEN replace(replace(replace(MainDescription, '''', '`'), char(10), ';'), char(13), ' ') &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;ELSE (replace(replace(replace(MainDescription, '''', '`'), char(10), ';'), char(13), ' ')&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;+ '(' + replace(replace(replace(ExtendedDescription, '''', '`') + ')', char(10), ';'), char(13), ' '))&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;END as FullDescription,&lt;/div&gt;&lt;br /&gt;A simpler way to express this:&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;FullDescription =&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Replace(Replace(Replace(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MainDescription + Coalesce('(' + ExtendedDescription + ')', '')&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;, '''', '`'), char(10), ';'), char(13), ' '&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;),&lt;/div&gt;&lt;br /&gt;It's not just about the simplest way to express something, but also about how easy it is to read and see what it's doing.&lt;br /&gt;&lt;br /&gt;Oddly enough I do put commas at the beginning of lines sometimes when expressions are very long. I'll never put commas at the beginning of a new column expression, though.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/866188004456276207-7122178931783977524?l=squaredthoughts.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://squaredthoughts.blogspot.com/feeds/7122178931783977524/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=866188004456276207&amp;postID=7122178931783977524' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/7122178931783977524'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/7122178931783977524'/><link rel='alternate' type='text/html' href='http://squaredthoughts.blogspot.com/2008/01/sql-confusion.html' title='SQL Puzzlement'/><author><name>ESquared</name><uri>http://www.blogger.com/profile/09187680112940803740</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='05197177171442690440'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-866188004456276207.post-3352087556173632760</id><published>2007-12-27T14:18:00.000-08:00</published><updated>2008-01-08T09:30:02.964-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='SQL Confusion'/><category scheme='http://www.blogger.com/atom/ns#' term='LIKE'/><title type='text'>SQL Overelaboration</title><content type='html'>Random forum posting, function that detects if a string is made only of numbers:&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;CREATE function fn_numeric&lt;br /&gt;-- input is varchar string.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;(@string varchar(12) )&lt;br /&gt;returns bit -- 0 = false, 1 = true&lt;br /&gt;as&lt;br /&gt;begin&lt;br /&gt;declare @x smallint, @y smallint, @z bit&lt;br /&gt;set @string = replace(@string, ' ', '@') &lt;br /&gt;set @x = len(@string)&lt;br /&gt;set @y = 0&lt;br /&gt;set @z = 1&lt;br /&gt;while @y &lt; @x&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;begin&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set @y = @y + 1&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if substring(@string, @y, 1) between '0' and '9'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;continue&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;begin &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;set @z = 0&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;break&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&lt;br /&gt;return (@z)&lt;br /&gt;end&lt;/div&gt;&lt;br /&gt;My response:&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;WHERE ColumnToTest NOT LIKE '%[^0-9]%'&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/866188004456276207-3352087556173632760?l=squaredthoughts.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://squaredthoughts.blogspot.com/feeds/3352087556173632760/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=866188004456276207&amp;postID=3352087556173632760' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/3352087556173632760'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/3352087556173632760'/><link rel='alternate' type='text/html' href='http://squaredthoughts.blogspot.com/2007/12/sql-confusion-december-2007.html' title='SQL Overelaboration'/><author><name>ESquared</name><uri>http://www.blogger.com/profile/09187680112940803740</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='05197177171442690440'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-866188004456276207.post-1381440863401623277</id><published>2007-10-29T10:41:00.000-07:00</published><updated>2007-10-29T11:48:11.820-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='performance'/><category scheme='http://www.blogger.com/atom/ns#' term='constraint'/><category scheme='http://www.blogger.com/atom/ns#' term='unique'/><category scheme='http://www.blogger.com/atom/ns#' term='primary key'/><category scheme='http://www.blogger.com/atom/ns#' term='index'/><category scheme='http://www.blogger.com/atom/ns#' term='clustered'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL Server'/><title type='text'>Sometimes Optional Keywords Shouldn't Be</title><content type='html'>I always use the CLUSTERED or NONCLUSTERED keywords in my constraint scripting. I used to not do it. But then I realized the following problem:&lt;br /&gt;&lt;br /&gt;You have a table with some stuff in it.&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;CREATE TABLE Test(a int NOT NULL, b int NOT NULL)&lt;br /&gt;INSERT Test VALUES (1, 2)&lt;br /&gt;INSERT Test VALUES (2, 3)&lt;br /&gt;INSERT Test VALUES (3, 4)&lt;/div&gt;&lt;br /&gt;You go and add a primary key to it:&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;ALTER TABLE Test ADD CONSTRAINT PK_Test PRIMARY KEY (a) --&lt;-- Clustered, right? Perhaps...&lt;/div&gt;&lt;br /&gt;And now you have a clustered index, right? Yes. But let's say that this table had different needs than most and its primary key wasn't the clustered index and there was already a non-primary-key clustered index! So drop the constraint you just made, then re-add it after making our test table correspond with your "real" table we're imagining:&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;ALTER TABLE Test DROP CONSTRAINT PK_Test&lt;br /&gt;CREATE CLUSTERED INDEX IX_Test_b ON Test (b)&lt;br /&gt;ALTER TABLE Test ADD CONSTRAINT PK_Test PRIMARY KEY (a) --&lt;-- Not clustered this time.&lt;/div&gt;&lt;br /&gt;So now your primary key constraint is NOT clustered, even though the default for primary keys IS clustered. And it was the same DDL script each time, performing something &lt;strong&gt;very&lt;/strong&gt; different. So I concluded that it's best to always include the keyword CLUSTERED or NONCLUSTERED so that you and other developers know that's what was intended. Plus now you won't go your merry way thinking that you did something you didn't. Let's try to re-add that PK but using the CLUSTERED keyword (which we are expecting to happen by default without using it):&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;ALTER TABLE Test DROP CONSTRAINT PK_Test&lt;br /&gt;ALTER TABLE Test ADD CONSTRAINT PK_Test PRIMARY KEY CLUSTERED (a)&lt;/div&gt;&lt;br /&gt;And now you get the information you needed to do your job properly:&lt;br /&gt;&lt;br /&gt;&lt;div class="whitebox"&gt;&lt;span style="color:red;"&gt;Server: Msg 1902, Level 16, State 3, Line 1&lt;/span&gt;&lt;br /&gt;Cannot create more than one clustered index on table 'Test'. Drop the existing clustered index 'IX_Test_b' before creating another.&lt;br /&gt;&lt;span style="color:red;"&gt;Server: Msg 1750, Level 16, State 1, Line 1&lt;/span&gt;&lt;br /&gt;Could not create constraint. See previous errors.&lt;/div&gt;&lt;br /&gt;UNIQUE is another word that I've seen people forget to include in their NONCLUSTERED index creation (not primary keys of course) so don't forget to put that in when you are expecting uniqueness. On the one hand, you might think that checking for uniqueness on every update or insert would be a performance hit. But that is not a problem: the real performance hit comes from the extra bytes required on that non-unique index as a unique-ifier bloating the index's size and reducing its speed.&lt;br /&gt;&lt;br /&gt;I should add that having extra unused indexes is a performance hit all by itself. &lt;a href="http://www.sqlskills.com/blogs/paul/default,date,2007-10-05.aspx#a3456055f-3416-44e4-be42-9aeeda05da36"&gt;Here's a good discussion about that&lt;/a&gt;. (This guy has a WAY better blog than I will ever have. If you've gotten anything from reading my blog, then you should forget about mine and go read his. When you're done reading all 50,000 of his posts, then come back here, where there's still a teensy tiny chance remaining you'll learn something from me that you didn't from him.)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/866188004456276207-1381440863401623277?l=squaredthoughts.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://squaredthoughts.blogspot.com/feeds/1381440863401623277/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=866188004456276207&amp;postID=1381440863401623277' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/1381440863401623277'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/1381440863401623277'/><link rel='alternate' type='text/html' href='http://squaredthoughts.blogspot.com/2007/10/sometimes-optional-keywords-shouldnt-be.html' title='Sometimes Optional Keywords Shouldn&apos;t Be'/><author><name>ESquared</name><uri>http://www.blogger.com/profile/09187680112940803740</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='05197177171442690440'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-866188004456276207.post-8531828373094758423</id><published>2007-10-25T15:52:00.000-07:00</published><updated>2007-10-25T16:12:57.560-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='performance'/><category scheme='http://www.blogger.com/atom/ns#' term='correlated subquery'/><category scheme='http://www.blogger.com/atom/ns#' term='tempdb'/><category scheme='http://www.blogger.com/atom/ns#' term='temp table'/><category scheme='http://www.blogger.com/atom/ns#' term='execution plan'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL Server'/><category scheme='http://www.blogger.com/atom/ns#' term='RAM'/><category scheme='http://www.blogger.com/atom/ns#' term='table variable'/><title type='text'>Table Variables Use Tempdb</title><content type='html'>&lt;div class="codebox"&gt;create table #t(a int)&lt;br /&gt;select * from tempdb.dbo.sysobjects where name not like 'sys%'&lt;br /&gt;drop table #t&lt;br /&gt;GO&lt;br /&gt;declare @t table(a int)&lt;br /&gt;select * from tempdb.dbo.sysobjects where name not like 'sys%'&lt;/div&gt;&lt;br /&gt;Now remove the GO and run it again. Notice that the table variable exists in tempdb during the first select! This is generically in computer programming called &lt;a href="http://blogs.msdn.com/ericlippert/archive/2004/06/18/159378.aspx"&gt;hoisting&lt;/a&gt; (and see &lt;a href="http://blogs.msdn.com/ericlippert/archive/2004/12/07/277763.aspx"&gt;more interesting related stuff&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;The query engine decides how much to put in tempdb and how much to keep in memory. Some reports I've read say that the data always lives in memory, but others say that tempdb can be used for some of the data. What I'm taking away is that, while there is always the possibility that your table variable will live only in memory, it is not guaranteed. But using a table at all will always incur a performance hit. And consuming too much RAM can be as much a detriment to a server as consuming too much tempdb space.&lt;br /&gt;&lt;br /&gt;Create a correlated subquery and put it in the SELECT list of a simple query against a many-row table that has a clustered index, and put a literal derived table in the subquery that UNION ALL SELECTS the numbers 1 through 10. It doesn't matter what the subquery does as long as it logically runs once for each row in the main query's table (it has a reference to a column from the main table).&lt;br /&gt;&lt;br /&gt;Now create a table variable and insert the same numbers 1 through 10 and modify the correlated subquery to use the table variable instead of the UNION list. Compare execution plans and traces. Note that the first query has 10 constant scans in it, repeated as many times as there are rows, but the number of reads are close to the number of &lt;strong&gt;pages&lt;/strong&gt; in the physical table.&lt;br /&gt;&lt;br /&gt;The second query has no constant scans in it, but the number of reads is now closer to the number of &lt;strong&gt;rows&lt;/strong&gt; in the physical table.&lt;br /&gt;&lt;br /&gt;You cannot think of table variables as no-cost, memory only, super-fast objects with no side effects. They are not. They have side effects that can very nearly be main effects.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/866188004456276207-8531828373094758423?l=squaredthoughts.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://squaredthoughts.blogspot.com/feeds/8531828373094758423/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=866188004456276207&amp;postID=8531828373094758423' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/8531828373094758423'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/8531828373094758423'/><link rel='alternate' type='text/html' href='http://squaredthoughts.blogspot.com/2007/10/table-variables-use-tempdb.html' title='Table Variables Use Tempdb'/><author><name>ESquared</name><uri>http://www.blogger.com/profile/09187680112940803740</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='05197177171442690440'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-866188004456276207.post-242586355322175172</id><published>2007-09-24T16:35:00.000-07:00</published><updated>2007-09-24T16:57:26.920-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='SQL Confusion'/><title type='text'>SQL Confusion</title><content type='html'>It is bizarre how often I find laughable and silly code in production databases that are part of software which costs millions of dollars.&lt;br /&gt;&lt;br /&gt;I'm sure there will be more of these to come. Here's my contribution for today.&lt;br /&gt;&lt;br /&gt;&lt;DIV class="codebox"&gt;SELECT distinct&lt;br /&gt;...,&lt;br /&gt;(select DATEADD(mi,-DatePart(mi, GetDate()),  DATEADD(ss,-(DatePart(ss, GetDate())), GetDate()))) as orecordeddtm&lt;/DIV&gt;&lt;br /&gt;WHOA! Run that statement and you'll see it's not only convoluted but it's wrong, too: it leaves the milliseconds portion of the time still on there (it's a datetime column). Here's my proposed simple and &lt;EM&gt;working&lt;/EM&gt; alternative:&lt;br /&gt;&lt;br /&gt;&lt;DIV class="codebox"&gt;DateAdd(hh, DateDiff(hh, 0, GetDate()), 0)&lt;/DIV&gt;&lt;br /&gt;The query is DISTINCT, which isn't a problem in this case because the expression is GetDate(). But what if the author of this used the same logic for a column? The left-in milliseconds would be messing everything up. Plus, why is the expression inside its very own select clause? Bizarre. Which of the following two statements makes more sense to YOU?&lt;br /&gt;&lt;br /&gt;&lt;DIV class="codebox"&gt;SELECT 1 + 1&lt;br /&gt;SELECT (SELECT 1) + (SELECT 1)&lt;/DIV&gt;&lt;br /&gt;Interesting is about the kindest description I can come up with for this.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/866188004456276207-242586355322175172?l=squaredthoughts.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://squaredthoughts.blogspot.com/feeds/242586355322175172/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=866188004456276207&amp;postID=242586355322175172' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/242586355322175172'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/242586355322175172'/><link rel='alternate' type='text/html' href='http://squaredthoughts.blogspot.com/2007/09/sql-confusion.html' title='SQL Confusion'/><author><name>ESquared</name><uri>http://www.blogger.com/profile/09187680112940803740</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='05197177171442690440'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-866188004456276207.post-3266741431564833340</id><published>2007-09-06T08:28:00.001-07:00</published><updated>2008-05-20T10:32:32.784-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='procedural statements'/><category scheme='http://www.blogger.com/atom/ns#' term='expressive statements'/><category scheme='http://www.blogger.com/atom/ns#' term='manipulative statements'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL Server'/><title type='text'>Mixed-Up Statement Types</title><content type='html'>I am going to start trying to put together the basic "What you need to know to get started with SQL Server." I see the same exact problems over and over again in SQL help forums. FAQs are good, but there are dozens and dozens of them, each generally solving a specific problem. I see a need for something before that. People are getting so confused they don't even know what to ask, let alone know what kind of FAQ is going to help them with their problem.&lt;br /&gt;&lt;br /&gt;So I'll one at a time make some posts about different conceptual issues I see and then finally I'll pull it all together as the "Top 10 Concepts" for beginners. Or whatever name seems right at the time. :)&lt;br /&gt;&lt;br /&gt;So today I'm going to start with the different types of statements in T-SQL, and particularly the difference between expressive, procedural, and manipulative statements. Other kinds of statements we won't concern ourselves with now are &lt;a href="http://orafaq.com/faq/what_are_the_difference_between_ddl_dml_and_dcl_commands"&gt;data definition, data control, and transaction control&lt;/a&gt;. People generally don't get as confused about those.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Expressive Statements&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;An expression consists of one or more literal values or functions tied together with operators, which when evaluated in the correct order result in a value or collection of values.&lt;br /&gt;&lt;br /&gt;In most cases an expression returns only a single value. In computing there is a special term for "a thing that is only a single value" called a &lt;em&gt;scalar&lt;/em&gt;. A composite or many-valued expression may be a list, array, or record. In SQL Server there are some cases where a rowset can be treated as a list and used in an expression.&lt;br /&gt;&lt;br /&gt;Here are some example expressions in T-SQL:&lt;br /&gt;&lt;DIV class="codebox"&gt;Power(2, 10)&lt;br /&gt;-- The literal values 2 and 10 are operated on by the built-in function &lt;em&gt;power&lt;/em&gt; which returns a single value.&lt;br /&gt;&lt;br /&gt;(18 + 23) * 7 % 5&lt;br /&gt;— The literal values 18, 23, 7, and 5 are combined with operators: addition, grouping (parentheses), multiplication, and modulus, to return a single value.&lt;br /&gt;&lt;br /&gt;CASE ... WHEN ... &amp;lt;WHEN ...&amp;gt; ELSE ... END&lt;/DIV&gt;&lt;br /&gt;These are all scalar expressions. I'll give an example of a multi-valued expression later because to use it one has to mix some of the statement types.&lt;br /&gt;&lt;br /&gt;Let me call attention specifically to the fact that with only one exception, all parts of an expression are evaluated. CASE statements do support "short-circuit" logic where the evaluation stops with the last WHEN condition that is true. But in general, you should think about expressions as an all-or-nothing proposition. Nothing can be done with all the parts of an expression until the whole thing is evaluated into a final and single scalar.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Procedural Statements&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;Procedural statements are called that because there is some &lt;em&gt;procedure&lt;/em&gt; that must be followed. It isn't a simple case of order-of-operations resulting in a single value. There is in fact no value &lt;em&gt;expressed&lt;/em&gt; at all. Here are some examples:&lt;br /&gt;&lt;br /&gt;&lt;DIV class="codebox"&gt;IF &amp;lt;true-or-false expression&amp;gt;&lt;br /&gt;WHILE &amp;lt;true-or-false expression&amp;gt;&lt;br /&gt;RETURN &amp;lt;integer expression&amp;gt;&lt;br /&gt;THEN&lt;br /&gt;ELSE&lt;br /&gt;BEGIN&lt;br /&gt;END&lt;br /&gt;GOTO&lt;br /&gt;RAISERROR(...)&lt;br /&gt;PRINT&lt;/DIV&gt;&lt;br /&gt;These all tell the query parser what statement it should &lt;strong&gt;proceed&lt;/strong&gt; to next. Note that some of these procedural statements expect expressions immediately following. The server, while executing the statements, has to evaluate the expression in order to know what it is supposed to do. Let me call attention specifically to the fact that not every statement may be executed, and they may be executed repeatedly or out of order.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Manipulative statements&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;These are normally called queries (though some of them do things instead of just asking questions as the word &lt;em&gt;query&lt;/em&gt; implies).&lt;br /&gt;&lt;br /&gt;&lt;DIV class="codebox"&gt;SELECT &lt;br /&gt;INSERT&lt;br /&gt;UPDATE&lt;br /&gt;DELETE&lt;br /&gt;UNION&lt;/DIV&gt;&lt;br /&gt;&lt;strong&gt;The Point Of All This&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;Now that you know the three main kinds of statements (and I won't rule out the possibility of there being more or of there being subclassifications of these) the key concept you must know to get along well with SQL Server is that when a certain kind of statement is expected, you can't use a different one in its place.&lt;br /&gt;&lt;br /&gt;Here are some examples of confusing one kind of statement with another.&lt;br /&gt;&lt;br /&gt;&lt;DIV class="codebox"&gt;-- Example 1&lt;br /&gt;IF (IF @b = 2 THEN @i ELSE @j) = 1 PRINT 'True' ELSE PRINT 'False'&lt;/DIV&gt;&lt;br /&gt;IF expects a true-false expression after it. You can't put another procedural statement there.&lt;br /&gt;&lt;br /&gt;&lt;DIV class="codebox"&gt;-- Example 2&lt;br /&gt;EXECUTE CASE WHEN @Action = 'Kill' THEN MyKillingSP ELSE MyNonKillingSP END&lt;/DIV&gt;&lt;br /&gt;EXECUTE isn't looking for an expression. It wants an SP name. Instead, do&lt;br /&gt;&lt;br /&gt;&lt;DIV class="codebox"&gt;IF @Action = 'Kill' EXECUTE MyKillingSP ELSE EXECUTE MyNonKillingSP.&lt;/DIV&gt;&lt;br /&gt;There. EXECUTE is happy now because it has an SP name immediately following in each case.&lt;br /&gt;&lt;br /&gt;&lt;DIV class="codebox"&gt;-- Example 3 - An actual example from online&lt;br /&gt;RETURN&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;IF @LINEID = '123' &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;BEGIN&lt;br /&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;select * FROM TABLE1&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;END&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ELSE&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;BEGIN&lt;br /&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;SELECT * FROM TABLE2&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;END&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;END&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&lt;/DIV&gt;&lt;br /&gt;In a stored procedure, RETURN expects an integer and in a user-defined function it expects an expression or a rowset or nothing, depending on its type. You can't put procedural statements in. This is obviously a rowset-returning user-defined function. So it will have to be a table-variable returning one. Use procedural logic to insert the right rows into the table variable, and then simply RETURN.&lt;br /&gt;&lt;br /&gt;&lt;DIV class="codebox"&gt;-- Example 4&lt;br /&gt;SELECT *&lt;br /&gt;FROM MyTable&lt;br /&gt;WHERE&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Status = (IF @Status IS NULL THEN '' ELSE @Status)&lt;br /&gt;&lt;/DIV&gt;&lt;br /&gt;IF is a procedural statement. You can't just embed it in the middle of an expression. And the IF statement switches back to expressive again: the first keyword after THEN can't be an expression. What's supposed to be done with that? It has to be procedural or manipulative: SET or INSERT or GOTO or PRINT. If you ran the following all by itself as its own batch, you'd get an error:&lt;br /&gt;&lt;br /&gt;&lt;DIV class="codebox"&gt;''&lt;/DIV&gt;&lt;br /&gt;So '' can't be the first thing after THEN.&lt;br /&gt;&lt;br /&gt;The way to do an &lt;em&gt;expressive&lt;/em&gt; if-then-else is with a CASE statement. I think it's worth spending a moment to explain that it has two forms:&lt;br /&gt;&lt;br /&gt;&lt;DIV class="codebox"&gt;--Simple equivalency&lt;br /&gt;CASE CaseExpression WHEN TestEquivalentExpression THEN ResultExpression &amp;lt;WHEN ... THEN ...&amp;gt; ELSE ... END&lt;br /&gt;&lt;br /&gt;CASE @i&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN 1 THEN 'One'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN 2 THEN 'Two'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN 3 THEN 'Three'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;SPAN style="color:#660000;"&gt;WHEN NULL THEN 'NULL'&lt;br /&gt;&amp;nbsp;-- This doesn't work because "@i = NULL" will always fail.&lt;br /&gt;&amp;nbsp;-- Use the next construction to test for this.&lt;/SPAN&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;ELSE 'Invalid'&lt;br /&gt;END&lt;br /&gt;&lt;br /&gt;-- fully expressive&lt;br /&gt;CASE WHEN TrueOrFalseExpression THEN ResultExpression &amp;lt;WHEN ... THEN ...&amp;gt; ELSE ... END&lt;br /&gt;&lt;br /&gt;CASE&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN @i = 1 THEN 'One'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN @i = 2 THEN 'Two'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN @i = 3 THEN 'Three'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHEN @i IS NULL THEN 'NULL'&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;ELSE 'Invalid'&lt;br /&gt;END&lt;br /&gt;&lt;/DIV&gt;&lt;br /&gt;But it resolves to a single final value. I believe (though I need confirmation on this) that the query engine expands the first syntax into the second. So be wary of using nondeterministic functions like Rand() in CASE statements. (Nullif() definitely gets expanded to a CASE statement so it has the same issue.)&lt;br /&gt;&lt;br /&gt;&lt;DIV class="codebox"&gt;SELECT *&lt;br /&gt;FROM MyTable&lt;br /&gt;WHERE&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Status = CASE WHEN @Status IS NULL THEN '' ELSE @Status END&lt;/DIV&gt;&lt;br /&gt;This is just an example and may not be best coding practice.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Manipulative Statements As Expressions&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;Finally, there are some special cases where manipulative statements can be used in expressions or as expressions.&lt;br /&gt;&lt;br /&gt;1. A manipulative statement that returns a single column and a single row can be used in most any place that an expression can be used. This is done by placing it in parentheses:&lt;br /&gt;&lt;br /&gt;&lt;DIV class="codebox"&gt;IF (&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;SELECT OrderGroup FROM OrderStatuses WHERE OrderStatus = @OrderStatus&lt;br /&gt;) = 2 ...&lt;br /&gt;&lt;br /&gt;PRINT (SELECT Count(*) FROM MyTable)&lt;/DIV&gt;&lt;br /&gt;2. A manipulative statement that returns a single column and many rows can be used in an expressive statement immediately following certain operators, again in parentheses. Those keywords are IN, EXISTS, and in conjunction with inequality operators, ANY, SOME, and ALL:&lt;br /&gt;&lt;br /&gt;&lt;DIV class="codebox"&gt;SELECT *&lt;br /&gt;FROM Orders&lt;br /&gt;WHERE OrderStatus IN (SELECT OrderStatus FROM OrderStatuses WHERE OrderGroup = 2)&lt;br /&gt;-- Note this is better done with an INNER JOIN.&lt;br /&gt;&lt;br /&gt;IF EXISTS (&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;SELECT *&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;FROM Orders&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHERE&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ExpectedShipDate &gt;= GetDate()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AND OrderStatus = 1&lt;br /&gt;) ...&lt;br /&gt;-- note that because of EXISTS, no columns are returned, and the query engine stops as soon as it finds one row meeting the criteria.&lt;br /&gt;&lt;br /&gt;SELECT *&lt;br /&gt;FROM Orders&lt;br /&gt;WHERE&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;OrderStatus &gt; ALL (&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SELECT OrderStatus&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FROM OrderStatuses&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHERE OrderGroup = 2&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&lt;br /&gt;-- can use ANY and SOME as well as ALL&lt;/DIV&gt;&lt;br /&gt;Note that some other SQL languages may allow multiple columns in some of these kinds of expressions.&lt;br /&gt;&lt;br /&gt;That should be enough to get people started on understanding &lt;br /&gt;&lt;br /&gt;Note: I might have mistakes or be missing some details in this blog entry. If I am, I'd appreciate you pointing it out so I can make the final version accurate and complete.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/866188004456276207-3266741431564833340?l=squaredthoughts.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://squaredthoughts.blogspot.com/feeds/3266741431564833340/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=866188004456276207&amp;postID=3266741431564833340' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/3266741431564833340'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/3266741431564833340'/><link rel='alternate' type='text/html' href='http://squaredthoughts.blogspot.com/2007/09/mixed-up-statement-types.html' title='Mixed-Up Statement Types'/><author><name>ESquared</name><uri>http://www.blogger.com/profile/09187680112940803740</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='05197177171442690440'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-866188004456276207.post-6442903877545448384</id><published>2007-08-29T16:03:00.001-07:00</published><updated>2007-09-06T16:51:35.727-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='OUTPUT'/><category scheme='http://www.blogger.com/atom/ns#' term='BOL'/><category scheme='http://www.blogger.com/atom/ns#' term='ROW_NUMBER'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL Server'/><title type='text'>SQL Server 2005</title><content type='html'>I've only recently really started to get into some of the new T-SQL stuff in SQL Server 2005. I am really digging them. And of course, at the same time, I am still wanting yet even more!&lt;br /&gt;&lt;br /&gt;Today I used ROW_NUMBER() for the first time, and it was easy. It will take a few tries to remember the syntax exactly, but it makes perfect sense. You have to be able to say when to begin counting at 1 again (PARTITION OVER) and what order to count in (ORDER BY). RANK, DENSE_RANK, and TILE_N (if I'm remembering that right) all make sense, too.&lt;br /&gt;&lt;br /&gt;I'm looking forward to getting into the OUTPUT clause. Before I had even learned about it in SQL 2005 I wanted this functionality for INSERT, UPDATE, and DELETE. I imagined its syntax slightly differently. First, any of these statement could be made to have a rowset. I actually overlooked this part when I was originally thinking about it, but it's fairly obvious once you realize its necessity. Here's one possible way:&lt;br /&gt;&lt;br /&gt;RETURN_ROWSET INSERT Stuff SELECT Blah FROM Gorp&lt;br /&gt;&lt;br /&gt;Then this query could be used any place a SELECT could be used:&lt;br /&gt;&lt;br /&gt;&lt;DIV class="codebox"&gt;UPDATE H&lt;br /&gt;SET Column = NewValue&lt;br /&gt;FROM&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;StuffHistory H&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;INNER JOIN (&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;RETURN_ROWSET INSERT Stuff SELECT Blah FROM Gorp&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;) S ON H.StuffID = S.StuffID&lt;/DIV&gt;&lt;br /&gt;This would allow you to chain together any number of statements, causing each INSERT, UPDATE, or DELETE to return a rowset, which would either go to a client or could be used again in the next statement. I realize now that there are some issues with the syntax I envisioned, but I think it's interesting that I "invented" this on my own before learning about it from SQL 2005.&lt;br /&gt;&lt;br /&gt;(The issues are that for an update there are two potential rowsets: the Inserted and Deleted meta-tables, so the way Microsoft implemented it makes a certain sort of sense, though I find the syntax a little weird, and I don't think you can nest these over and over again.)&lt;br /&gt;&lt;br /&gt;Perhaps instead of nesting things this way it could be more like a common table expression, with many queries using the thing just like a table:&lt;br /&gt;&lt;br /&gt;&lt;DIV class="codebox"&gt;;WITH_ROWSET S AS (INSERT Stuff SELECT Blah FROM Gorp)&lt;br /&gt;BEGIN&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;UPDATE H&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;SET Column = NewValue&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;FROM&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;StuffHistory H&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;INNER JOIN S ON H.StuffID = S.StuffID&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;SELECT NewValue FROM H WHERE OtherColumn = 1&lt;br /&gt;END&lt;/DIV&gt;&lt;br /&gt;There are also a bunch of gotchas surrounding views, triggers, remote tables, and limitations on the target of an OUTPUT INTO clause (target table can't have triggers, foreign keys, CHECK constraints, or enabled rules).&lt;br /&gt;&lt;br /&gt;Here's one example from BOL 2005. Do you understand it on the first try? I didn't.&lt;br /&gt;&lt;ul&gt;&lt;em&gt;If the OUTPUT clause is specified without also specifying the INTO keyword, the target of the DML operation cannot have any enabled trigger defined on it for the given DML action. For example, if the OUTPUT clause is defined in an UPDATE statement, the target table cannot have any enabled UPDATE triggers.&lt;/em&gt;&lt;/ul&gt;Anyway, that's enough for now. I'll talk another time about alternate syntax I wish one could use for UPDATEs and INSERTs.&lt;br /&gt;&lt;br /&gt;P.S. If you are trying to insert HTML in your own blog on blogspot.com (blogger.com) stay away from the "Compose" mode and stay in "Edit Html" mode. They try to parse the HTML and fail and end up eating your stuff.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/866188004456276207-6442903877545448384?l=squaredthoughts.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://squaredthoughts.blogspot.com/feeds/6442903877545448384/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=866188004456276207&amp;postID=6442903877545448384' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/6442903877545448384'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/6442903877545448384'/><link rel='alternate' type='text/html' href='http://squaredthoughts.blogspot.com/2007/08/sql-server-2005.html' title='SQL Server 2005'/><author><name>ESquared</name><uri>http://www.blogger.com/profile/09187680112940803740</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='05197177171442690440'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-866188004456276207.post-7769643444859349086</id><published>2007-08-23T10:36:00.000-07:00</published><updated>2007-08-28T14:35:15.260-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='troubleshooting'/><category scheme='http://www.blogger.com/atom/ns#' term='thinking'/><category scheme='http://www.blogger.com/atom/ns#' term='deliberate'/><category scheme='http://www.blogger.com/atom/ns#' term='query visualization'/><category scheme='http://www.blogger.com/atom/ns#' term='spatial relationships'/><title type='text'>Am I Thinking About The Right Thing?</title><content type='html'>I have thought for a long time about deliberately ordering my thought processes. It's something that I do quite frequently. I'd like to share that with people and maybe spur others to do it more, but ironically I'm finding it difficult to express the concept clearly.&lt;br /&gt;&lt;br /&gt;I think I'll start for now with an example. If I come back to this topic later, I can build on that example and maybe come up with something that helps me communicate what I'm talking about more effectively.&lt;br /&gt;&lt;br /&gt;Sometimes I wonder if the way I think is substantially different from the way many people think. I mean, the areas where my brain works well (or doesn't work well) may not coincide with many people's. So I'm not really sure about the validity of some of my thoughts on how one might order one's own thought processes to yield better results. For example, I like to use spatial representations to figure out problems. There are many times when working on a query that I imagine little squares (representing tables or groups or some kind of data) touching each other, splitting, moving, folding, and so on. But for someone who doesn't think well spatially, that's probably not going to work. There are other modes and ways, though. So even if you can't relate directly to what I'm talking about, maybe you can find some comparable process that does work for you, that helps you be more deliberate in your thinking process.&lt;br /&gt;&lt;br /&gt;On the other hand, maybe I'm way out in left field. Maybe my brain is stuffed full of cheese-filled rotten rutabagas! So take what I say here with a grain of salt.&lt;br /&gt;&lt;br /&gt;When one is doing analysis of a query, one should check each thing along the way to see if it's giving the right value. It's kind of like when you have a problem with a computer peripheral, say, a printer. There is sort of a list, roughly from lowest to highest: Is it turned on? Is the power cable plugged in? Is the network/parallel cable connected to the right location? Does it have paper? Are error codes showing, is there a paper jam, are there any obvious latches or doors that need to be closed, is there a ready mode that it needs to enter? Can you generate a test page? If it's a network printer, can you ping it? Can anyone else print to it? Can you print to it with a different OS? Can you print to it with a different program? Can you print a smaller document? Can you print a document without images in it? Can you print a document with a different font?&lt;br /&gt;&lt;br /&gt;If you find yourself troubleshooting at a certain level and it's not working, then back up and go to the more basic questions. You might even start at the beginning. Doing this helps you check your assumptions and guides you into the next correct troubleshooting step. If the extent of the description of the problem is "I can't print," you don't know much at all. But if the problem is, "I can print from Joe's computer but not from mine," then you can be directed to the level of problem-solving you ought to be at. Since now you know that it's plugged in, turned on, ready, has paper, is connectable for Joe and is responding to print requests, what level are you at? You're ready to verify your own connectivity. Only &lt;em&gt;then&lt;/em&gt; deal with access rights. &lt;em&gt;Then&lt;/em&gt; drivers. &lt;em&gt;Then &lt;/em&gt;different programs. &lt;em&gt;Then&lt;/em&gt; things affecting printer memory.&lt;br /&gt;&lt;br /&gt;Don't waste your time with printing from different programs until you're sure you can even connect to that thing. Maybe the network cable on your computer came unplugged—you'd just be wasting your time messing around with printing from different programs. Maybe it's a private printer connected directly to his computer with a USB or parallel cable and it's not shared or you don't have access to it—you'd be wasting your time messing around with trying to ping it or using different drivers.&lt;br /&gt;&lt;br /&gt;Going back to databases, here's a real-world example of getting stuck too high up in the chain. Several experienced SQL people were helping a poster with a query where he was trying to show summary data from several tables at once, but he was getting sums that were multiples of the correct sums. Everything was complicated because he wanted some pivoting, but was doing it by joining to the tables multiple times instead of using case statement mini-machines to calculate the correct sums as the stream of rows passed by. (Here I go with a spatial representation again. I tell you, it really helps me.)&lt;br /&gt;&lt;br /&gt;The reason he couldn't figure it out was that he was thinking of the query in terms of its structure &lt;em&gt;after&lt;/em&gt; grouping instead of &lt;em&gt;before&lt;/em&gt;. When there was more than one row to be grouped by for one table, and then he joined to another table (whether or not it had multiple rows per group), everything was getting cross-joined among the groups. He needed to step back and run his query without the group by clause, and see if the rows made sense before being collapsed into their defined buckets (there's more spatial framing again). If he'd done that, he'd have seen the cross-joining behavior and realized what the problem was. All the experts, myself included, forgot about that at first and started fiddling with the summing/aggregating stuff!&lt;br /&gt;&lt;br /&gt;That was too high of a level. We hadn't even checked to see if the data we were trying to manipulate was in the correct form. And it wasn't.&lt;br /&gt;&lt;br /&gt;So. Deliberately ordering one's own thought processes. The next time I am about to think about a problem, I'm going to &lt;strong&gt;stop thinking&lt;/strong&gt;. Then I'm going to think about what I'm thinking about for a minute. Is this the right thing to think about? Am I making assumptions? Can I test those assumptions?&lt;br /&gt;&lt;br /&gt;I'm going to back off from solving the problem and think about &lt;em&gt;whether it's even the right problem to start with&lt;/em&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/866188004456276207-7769643444859349086?l=squaredthoughts.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://squaredthoughts.blogspot.com/feeds/7769643444859349086/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=866188004456276207&amp;postID=7769643444859349086' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/7769643444859349086'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/7769643444859349086'/><link rel='alternate' type='text/html' href='http://squaredthoughts.blogspot.com/2007/08/am-i-thinking-about-right-thing.html' title='Am I Thinking About The Right Thing?'/><author><name>ESquared</name><uri>http://www.blogger.com/profile/09187680112940803740</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='05197177171442690440'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-866188004456276207.post-4051386119545929724</id><published>2007-08-21T13:42:00.000-07:00</published><updated>2008-05-16T11:26:32.932-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='between'/><category scheme='http://www.blogger.com/atom/ns#' term='precision'/><category scheme='http://www.blogger.com/atom/ns#' term='datetime'/><category scheme='http://www.blogger.com/atom/ns#' term='exclusive'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL Server'/><category scheme='http://www.blogger.com/atom/ns#' term='inclusive'/><category scheme='http://www.blogger.com/atom/ns#' term='milliseconds'/><category scheme='http://www.blogger.com/atom/ns#' term='ranges'/><title type='text'>The 3 Millisecond Datetime Trick Is Dead</title><content type='html'>At times I've used my knowledge that SQL Server 2000 stores datetimes to a precision of 1/300th of a millisecond (I won't go into full details here). Here are some examples:&lt;br /&gt;&lt;br /&gt;If @StartDate and @EndDate have already had their time portions removed (or guaranteed to have none) I might do this:&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;WHERE DateCol BETWEEN @StartDate AND @EndDate - '0:00:00.003'&lt;/div&gt;&lt;br /&gt;or perhaps&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;WHERE DateCol BETWEEN @TheDate AND @TheDate + '23:59:59.997'&lt;/div&gt;&lt;br /&gt;But I realized today that these tricks, though cool and interesting, and while perhaps reducing the amount of code or increasing readability, assume that the data type of DateCol will never change. That doesn't sound so bad since the only alternative is smalldatetime, right? Not for long.&lt;br /&gt;&lt;br /&gt;In SQL Server 2008, the TIME, datetimeoffset, and datetime2 data types will have 100 nanosecond precision. It's not a bad bet that somewhere, some time, someone's going to be converting one of the datetime columns I was querying against to one of those. And then all my code written in this sort of style is going to break big-time.&lt;br /&gt;&lt;br /&gt;And this is the kind of nasty break that doesn't bring all systems to a screeching halt (which has the benefit of notifying you that something is wrong) but lets you keep running your processes for days or weeks or months, until eventually something slips through that 3 millisecond hole or accumulates enough to be noticeable. Something critical, something important, something I should have thought about before using this kind of technique.&lt;br /&gt;&lt;br /&gt;From now on I will be a good camper and deal with the pain of&lt;br /&gt;&lt;br /&gt;&lt;div class="codebox"&gt;WHERE DateCol &gt;= @StartDate AND DateCol &lt; @EndDate&lt;br /&gt;--AND&lt;br /&gt;WHERE DateCol &gt;= @TheDate AND DateCol &lt; (@TheDate + 1)&lt;/div&gt;&lt;br /&gt;since they work no matter what the data type of DateCol is.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/866188004456276207-4051386119545929724?l=squaredthoughts.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://squaredthoughts.blogspot.com/feeds/4051386119545929724/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=866188004456276207&amp;postID=4051386119545929724' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/4051386119545929724'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/4051386119545929724'/><link rel='alternate' type='text/html' href='http://squaredthoughts.blogspot.com/2007/08/3-millisecond-datetime-trick.html' title='The 3 Millisecond Datetime Trick Is Dead'/><author><name>ESquared</name><uri>http://www.blogger.com/profile/09187680112940803740</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='05197177171442690440'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-866188004456276207.post-85875432072302097</id><published>2007-08-21T11:44:00.000-07:00</published><updated>2008-04-20T22:16:44.885-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='speed'/><category scheme='http://www.blogger.com/atom/ns#' term='naming conventions'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL Server'/><category scheme='http://www.blogger.com/atom/ns#' term='efficiency'/><title type='text'>Naming Objects In Your Database</title><content type='html'>This is something I posted first in a SQL Server forum. Here it is again, touched up a bit.&lt;br /&gt;&lt;br /&gt;&lt;hr /&gt;&lt;br /&gt;So you want to name the objects in your database: your tables, your views, your functions, and anything else. And there are a lot of people out there who name them with what the object type is. Maybe even with more information. For example:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;tblOrders&lt;br /&gt;Table_Orders&lt;br /&gt;sp_OrderSend (NEVER prefix SPs with "sp_" because the server will look for a system SP first!)&lt;br /&gt;Proc_OrderSend&lt;br /&gt;OrderStatusFunc&lt;br /&gt;fn_OrderStatus&lt;br /&gt;fn_t_OrderStatus&lt;/ul&gt;and even worse:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;joe_sp_OrderSend&lt;br /&gt;mike_sp_OrderCancel&lt;br /&gt;david_fn_OrderStatus&lt;/ul&gt;Then there are column names:&lt;br /&gt;&lt;ul&gt;ID&lt;br /&gt;ProductID&lt;br /&gt;keyProductID&lt;br /&gt;pkeyProductID&lt;br /&gt;pkProductID&lt;br /&gt;pkProductIDCode&lt;br /&gt;frnkeyProductID&lt;br /&gt;fkProductID&lt;/ul&gt;So does this stuff help at all? Is it useful to put the type or relationship in the name of objects? Can you leave the name off the ID column in your tables? Should you put more information in there? What does it matter, anyway, as these are mere naming conventions, right? Just pick something, and stick with it!&lt;br /&gt;&lt;br /&gt;I agree that consistency is important. But consistently helpful is a far improvement over consistently something-less-than-that. Consistently unnecessary is extra work and frustration. Consistently interfering impairs your ability and speed. Consistently mediocre is not the greatest.&lt;br /&gt;&lt;br /&gt;Overall, &lt;strong&gt;I think it is best to take a minimalistic approach&lt;/strong&gt;. I hope my reasons make sense to you, but whatever you do pick, you should have clear and compelling reasons for what you do. Notice the compelling part. Your default should be to avoid clutter and only add things if there is a materially demonstrable reason to violate that rule.&lt;br /&gt;&lt;br /&gt;First of all, much of my advice is meaningless if you're using a GUI where one performs the less taxing task of recognition instead of the recall required to write queries in text. If you are writing queries in text, more power to you, and if you keep doing it I predict you'll eventually see the sense of my recommendations.&lt;br /&gt;&lt;br /&gt;But if you're using a GUI to write queries you'll only reach a certain level of ability, never stepping into the next level of professional skill and seasoned experience in the SQL realm. I haven't used a GUI to write a serious query in a very long time (I might have started a couple in an Access database but even then switched to SQL view). I recently helped someone who posted his query that was obviously built in a GUI and it had about 10 left joins. I seriously doubt they all needed to be left joins, which means he was killing performance for no reason.&lt;br /&gt;&lt;br /&gt;So if you &lt;em&gt;are&lt;/em&gt; writing queries in a text editor, here are some thoughts for you:&lt;br /&gt;&lt;br /&gt;• Lose the underscores. They clutter up the screen. They take the shift key to type and also your pinky finger which has to move two rows up from the home row. And using CamelCase is just as clear and saves space and that pinky traversing. And if you're really feeling lazy you don't even have to hit the shift key.&lt;br /&gt;&lt;br /&gt;order_id&lt;br /&gt;customer_name&lt;br /&gt;device_code_reason&lt;br /&gt;&lt;br /&gt;OrderID&lt;br /&gt;CustomerName&lt;br /&gt;DeviceCodeReason&lt;br /&gt;&lt;br /&gt;Look at it this way: if you can train your eyes to scan either kind of naming convention just as efficiently, why not use the one that involves less typing and takes less space on the screen?&lt;br /&gt;&lt;br /&gt;• The prefix/suffix naming convention is not always understood or followed. In one shop I worked in they named tables with prefixes: tbl for base tables, tlk for lookup tables, and tin for "intersect" tables. But no one really knew what they were for sure. "tin" tables were supposed to be transactional tables that had constantly changing data such as orders and transactions. "tbl" tables were supposed to be things like customers and products. But the problem was that many tables didn't fit a clear definition, and people were careless, or came along years after the creator of the database (who yes used a GUI database creation program), and they got named wrong. So now after all that silliness and extra typing, there was a "tin" table that was really a "tbl" table and a "tlk" table that was really a "tin" table after some changes, and ... so on and so forth. Just leave off that junk.&lt;br /&gt;&lt;br /&gt;• You'll eventually become completely sick of typing the extra characters over and over again for NO GOOD REASON. That is, if you care about speed. If you don't care about speed, type your prefixes merrily. If you do get tired of them, though, you'll have these prefixes so wrapped up in your code and database that ripping them out becomes impossible. All those extra letters will come to be so much useless froofroo to you. At the same shop as tbl/tin/tlk they really named columns like &lt;em&gt;keyOrderID&lt;/em&gt; and &lt;em&gt;frnkeyOrderID&lt;/em&gt;. They added nine characters of typing to EVERY SINGLE JOIN I ever had to write. After a year of working there, the pain was no less. It took me about 2 weeks to learn the primary and foreign keys of that database. The other fifty weeks I wished the database designer had left well enough alone.&lt;br /&gt;&lt;br /&gt;• Naming is something that is so much more important than most people seem to think. What does it matter what we call things, as long as we're "consistent," right? I disagree. My experience is that the naming in a database the single most important thing that structures how programmers relate to the data and that determines their final speed and facility working with it in the long run.&lt;br /&gt;&lt;br /&gt;Mislabeling things is like putting up signs at the cliff edge that say "keep going" and "this is the right way" when it's not. Label things appropriately.&lt;br /&gt;&lt;br /&gt;• Scanning and comprehension speed suffer when extra things are tacked on. When you look at a stored procedure or query, if you yourself weren't just working on it yesterday, then you have to digest it to understand it. The more prefix_blob_description_gorp you have in there, the harder it is to see what's going on. The assumption here is, you know your database like the back of your hand. Will that extra stuff speed you up now or slow you down? Who should you code for, the least common denominator or a reasonable level of facility? In fact, this brings up another important point:&lt;br /&gt;&lt;br /&gt;• You do not want to give that new developer, who is unfamiliar with your database, a prematurely quick sense of competence and ability in your database. By doing this, you increase the chances that he or she will make some serious mistake. This is the opposite mistake of mislabeling. Now the path over the edge of the cliff is littered with so many friendly and distracting signals that the poor new person gets the same wrong signal to keep going when the correct response ought to be, "wait, I'm not sure I am doing the right thing." I'm all for helping new developers get going as fast as possible. But they ought to do this by studying the schema and practicing with it. Don't make it so &lt;em&gt;apparently&lt;/em&gt; easy (but not necessarily materially easy) that even an ignorant person will believe he knows all about your database.&lt;br /&gt;&lt;br /&gt;• The information is unnecessary and ultimately makes developers remember more stuff, not less. It sounds at first like a great way to expose information about the tables and objects. But the fact is that the period of time where a new developer isn't familiar with the database is actually very short. So for the benefit of two months of instant answers to the beginner, everyone else in the shop is suffering through unnecessary baggage.&lt;br /&gt;&lt;br /&gt;• Don't label things with each developer's name. Why would you do that? Do all the developers write different versions of the same SPs? Are they not production SPs but simply a sort of learning environment where each can write whatever he wants so it's important to keep the names straight? In that case, why do the objects have the same owners? Just use a different owner name and don't put your name in the object. But wait, why are they even in the same database? Why does it possibly matter who wrote something in the function of the database as a whole? Things should be named according to what they are or do, not who worked on it first or last.&lt;br /&gt;&lt;br /&gt;If I was offered a consulting job on a database that had every stored procedure and object using different names based on who had written them, I would refuse the work for fear of going absolutely nuts. If I was forced by my situation to take the work, I would every day curse the people who decided that putting their names on their stuff would somehow have any kind of long-term value and I would quit the moment I found something else. Long after Shannon and Victor and Felix are gone from the company, poor developers are toiling away trying to remember if that was a felix function or a victor view or perhaps it was a shannon function and a felix view! GAHHH!&lt;br /&gt;&lt;br /&gt;• Spend careful and deliberate time naming things. I often use a thesaurus when designing a database. I have at times spent (cumulatively over the project) hours working out the correct naming of things. But when I was done, WOW how things flowed. I've worked with databases where the detail table wasn't, and the parent table wasn't, and the order table wasn't, and the Determination column wasn't (it should have been called DeterminationMethod, the actual determination was called something else entirely). Every single one of those is a roadblock to proper usage, even to the developer who's been working with that database for years. Make it intuitive instead of notated.&lt;br /&gt;&lt;br /&gt;The way I do it is to never add anything to table names, and to use carefully chosen words for my functions and SPs that make it clear what they are. For example, all my SPs are either SubjectVerb or VerbSubject, consistently throughout the database (still experimenting with which I like better--I'm leaning toward SubjectVerb). Functions have words that make it clear what they do. NumberToHex is a function. OrderNumberToOrderID is a function. ProductDetailCount is a scalar function, ProductDetail is a table, and ProductCreate is a stored procedure. Develop your own pattern. I can go through my own code so fast when I've been careful like this. The meaning nearly leaps out at me.&lt;br /&gt;&lt;br /&gt;• If you absolutely must use a prefix or a suffix (I lean toward suffixes myself because I like things to group by subject instead of function) why not use a single character instead of three? Tables can still do without any fluff at all. Then your other objects are still distinguishable:&lt;br /&gt;&lt;br /&gt;OrderNumber_OrderIDF&lt;br /&gt;&lt;br /&gt;(Before you criticize why one might have order numbers &lt;strong&gt;and&lt;/strong&gt; order IDs, consider a system that has to handle orders from disparate sources which use overlapping order number schemes. So you either have to have a composite key--source,ordernumber--or use a global and internal order ID that can be unique per order.)&lt;br /&gt;&lt;br /&gt;And think about that suffix part. In what possible situation would you see all your objects mixed together, and you need to ordered them by type but don't have a column that specifies type? This is the only way I can think of that a prefix with the type &lt;em&gt;might&lt;/em&gt; be helpful. If it was actually needed at all because the objects aren't named intuitively.&lt;br /&gt;&lt;br /&gt;• The object type can change. What if you decide to change the structure of one of your tables, and to support your older versions of software, you build the new table with a new name and convert your old table to a view? Now you have tbl_Whatever that's a view. Whoops. And what if you need to use that really important sp, and you name SPs with their creator (shudder), only you can't remember who wrote it? Now you have to look it up. What a time waster. What if you can't remember if it's an inline table function or a table-variable function? More wheels spinning. If these seem farfetched, they are not. I have worked with databases that had "tbl" objects that were views and believe me it was a nightmare remembering which was which. Who cares what it is! Views and tables are used in exactly the same way so why call them something different? If you're worried about the execution plan and query performance (and you ought to be) then you're already at a level that demands familiarity with the type of objects you're working with. If you absolutely need to notate that something is a view, put it in comments.&lt;br /&gt;&lt;br /&gt;• Think of it this way: you're trying to build a race car--a sleek, elegant, efficient thing that can go as fast as possible and be interfaced with well. What good is a racecar that can theoretically go 200mph if the steering wheel has sharp spikes all over it and the driver has to spend extra time avoiding them? He won't be spending as much time at top speed as he ought to be.&lt;br /&gt;&lt;br /&gt;• Name a column the same thing everywhere. Don't call it different things in different tables. Especially don't just call it "ID." Call it BlahID everywhere, including the Blah table that it originates from.&lt;br /&gt;&lt;br /&gt;Calling all identity columns ID is certainly a kind of standardization. But it's a standardization of seriously doubtful value that makes different columns in different tables have the &lt;em&gt;same name&lt;/em&gt;! This is now the opposite error: conflating things that one ought to be able to tell apart. If you've been trained to differentiate each thing by putting the type of it in the name, why now depart radically from that philosophy by calling all the IDs the same thing? From the beginner/new developer perspective, it seems to make things easier in some ways--no looking up column names when selecting, right? But it's not easier, it's harder. If you ever get the experience of doing it both ways in a large and busy production database (that is, ID vs. TableNameID), I suspect you'll eventually agree (unless you've become dogmatic about it by then and don't really care about the actual function or efficency any more).&lt;br /&gt;&lt;br /&gt;I prefer to standardize things in this way: If there is an ID column, it shares the same name as the table, + ID. Now there's no confusion about what the name is. Now if you have a query with many joins and you need to use ID you're not getting confused.&lt;br /&gt;&lt;br /&gt;• Another reason to avoid columns named "ID" is that you will eventually put the wrong alias on some join. You may not even notice because the query parses fine and you save your SP and off you go, but whoops, you got an effective cartesian product because your join condition referenced the wrong ID. Or just the wrong resultset. What a confusing mess. Much better, when you accidentally put D.ProductID instead of P.ProductID, for the compiler to complain that the table aliased by D has no such column. The alternate "D.ID" though would have compiled just fine and given the wrong results farther down the path, maybe even after release. It's much better to catch errors early.&lt;br /&gt;&lt;br /&gt;• Selecting is not where the problem is. Yes, it's shorter to say SELECT ID FROM Table. Fewer characters. But the real problem is in JOINs. The problem is in old resultsets you saved in Excel that you don't know what table they came from because they so unhelpfully say ID. You don't know what some error message means that a user reported to you because all he remembered was the main column that the constraint violated and he says "there was a foreign key constraint in column ID!" Great. That's every table in your database.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/866188004456276207-85875432072302097?l=squaredthoughts.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://squaredthoughts.blogspot.com/feeds/85875432072302097/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='https://www.blogger.com/comment.g?blogID=866188004456276207&amp;postID=85875432072302097' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/85875432072302097'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/866188004456276207/posts/default/85875432072302097'/><link rel='alternate' type='text/html' href='http://squaredthoughts.blogspot.com/2007/08/naming-object-in-your-database.html' title='Naming Objects In Your Database'/><author><name>ESquared</name><uri>http://www.blogger.com/profile/09187680112940803740</uri><email>noreply@blogger.com</email><gd:extendedProperty xmlns:gd='http://schemas.google.com/g/2005' name='OpenSocialUserId' value='05197177171442690440'/></author><thr:total xmlns:thr='http://purl.org/syndication/thread/1.0'>2</thr:total></entry></feed>