A blog about SQL Server, SSIS, C# and whatever else I happen to be dealing with in my professional life.

Find ramblings

Monday, July 27, 2009

SQL coding standards

I wrote this 2 years ago and have only now gotten back to a place where we are starting to talk about coding standards. The google doc version looks much better. Although I realistically should have written it in LaTeX to keep my nerd chops sharp.

Preface

The following is intended to provide a guideline to developers so that their code can be more consistent which will lead to lower maintenance costs and less chances for errors.  The general rule of thumb is to remove the opportunity for ambiguity to exist.

Some rules may appear to be violated if this document is read from start to finish.  This is intentional and is done to reduce document length.

 

 

Definitions

Camel Case, camelCase, is the practice of writing the first letter of the first word lowercased and all subsequent words with their first letter uppercased.

 

Pascal Case, PascalCase, is the practice of writing the first letter in the identifier and the first letter of each subsequent concatenated word capitalized.

 

Hard tab, ASCII character 9, \t, [Tab key]

 

Soft tab, ASCII character 32, [Spacebar],  repeated n times.  For our development purposes, n = 4

 

 

General rules 

Be explicit in your code, avoid implicit behavior

 

Verbosity is preferred to terseness

 

Be consistent

 

Nested views are a maintenance nightmare and to be avoided

 

Stored procedures should have SET NOCOUNT ON as the first statement within them

 

Explicitly drop and recreate objects instead of using ALTER statements

 

Capture the value of @@ERROR and @@ROWCOUNT before inspecting them

 

Dynamic SQL statements will not be built nor executed programmatically

 

Do not use sentinel values or empty strings to get around NOT NULL column constraints

 

Avoid using magic numbers in code. Define constants and reference the identifier

 

Lookup identity values programmatically via the human readable key

 

Avoid using print statements within your stored procedures

 

Store data independent of formats (e.g. phone or social security numbers)

 

 

Layout 

Hard tabs are not to be used.  Configure your text editor to use soft tabs instead

 

Code will generally be longer than it is wide

 

Declare variables as early as possible

 

An empty line is to be used to separate statements

-- Correct

    DECLARE @currentDate datetime

 

    SELECT @currentDate = GetDate()

 

    SELECT TOP 10 ST.sales_people INTO #EXAMPLE FROM dbo.SALES_TABLES ST

 

    UPDATE EX SET sales_people = IsNull(sales_people, 'uknown') FROM #EXAMPLE EX


 

    SELECT EX.sales_people FROM #EXAMPLE EX


 

    RETURN @currentDate

-- Avoid


    DECLARE @currentDate datetime

    SELECT @currentDate = GetDate()

    SELECT TOP 10 ST.sales_people INTO #EXAMPLE FROM dbo.SALES_TABLES ST

    UPDATE EX SET sales_people = IsNull(sales_people, 'uknown') FROM #EXAMPLE EX


    SELECT EX.sales_people FROM #EXAMPLE EX


    RETURN @currentDate


 

Each element of a statement should exist on its own line, indented appropriately (awkward

-- Correct

    SELECT DISTINCT

        A.column1

    ,   B.column2

    FROM

        TABLEA A

        INNER JOIN

            TABLEB B

            ON B.foreign_key = A.primary_key

                AND B.foreign_key2 = A.primary_key2

    WHERE

        A.primary_key IN

        (

            'A'

        ,   'B'

        )

        OR B.column2 = 'Test'

-- Avoid

    SELECT DISTINCT A.column1, B.column2 FROM TABLEA A INNER JOIN TABLEB B ON B.foreign_key = A.primary_key AND B.foreign_key2 = A.primary_key2 WHERE A.primary_key IN ('A', 'B') OR B.column2 = 'Test'

  

One declaration or assignment is to be used per line.  However, do make declarations/assignments with as few statements as possible

-- Correct

    DECLARE

        @salesPreviousMonthToDate decimal(18, 2)

    ,   @salesCurrentMonthToDate decimal(18, 2)

    ,   @salesPreviousYearToDate decimal(18, 2)

    ,   @salesCurrentYearToDate decimal(18, 2)

-- Avoid

    DECLARE

        @salesPreviousMonthToDate decimal(18, 2), @salesCurrentMonthToDate decimal(18, 2), @salesPreviousYearToDate decimal(18, 2), @salesCurrentYearToDate decimal(18, 2)

 

For single line statements, commas should succeed an argument with a single space after it

-- Correct

    SELECT IsNull(NULL, 'Hello world')

-- Avoid

    SELECT IsNull(NULL ,'Hello world')

 

For multi-line statements, commas should precede arguments by a soft tab at the appropriate block level

-- Correct

    SELECT

        column1

    ,   column2

    ,   column3

    FROM

        dbo.TEST

-- Avoid

    SELECT

        column1,

        column2

,       column3

    FROM

        dbo.TEST

 

BEGIN and END should be used to indicate block levels, even for optional locations

-- Correct

    IF (@@ROWCOUNT > 0)

    BEGIN

        PRINT 'Blocking level'

    END 

-- Avoid

    IF (@@ROWCOUNT > 0)

        PRINT 'Blocking level'

 

Casing

Table names should be upper cased

-- Correct

    CREATE TABLE dbo.SALES_FIGURES(sales_figures_identifier int PRIMARY KEY)

-- Avoid

    CREATE TABLE dbo.sales_figures(sales_figures_identifier int PRIMARY KEY)

 

Column names should be lower cased

-- Correct

    CREATE TABLE dbo.SALES_FIGURES(sales_figures_identifier int PRIMARY KEY)

-- Avoid

    CREATE TABLE dbo.SALES_FIGURES(SALES_FIGURES_IDENTIFIER int PRIMARY KEY)

 

Functions and procedures should be Pascal cased

-- Correct

    CREATE FUNCTION dbo.SalesGetByYear()

    CREATE PROCEDURE dbo.SalesInsert

-- Avoid

    CREATE FUNCTION dbo.salesGetByYear()

    CREATE PROCEDURE dbo.SALES_INSERT

 

Parameters should be Camel cased

-- Correct

    CREATE FUNCTION dbo.SalesGetByYear(@currentYear int, @salesPersonId int)

    CREATE PROCEDURE dbo.SalesInsert(@currentDate, @salesPersonId int, @salesAmount decimal(18, 2))

-- Avoid

    CREATE FUNCTION dbo.SalesGetByYear(@CURR_YEAR int, @ID int)

    CREATE PROCEDURE dbo.SalesInsert(@CurrentDate, @SalesPersonId int, @salesamount decimal(18, 2))

 

Local variables should be prefaced with an underscore and Camel cased

-- Correct

    DECLARE @_insuranceSalesPreviousMonthToDate decimal(18, 2

-- Avoid

    DECLARE @insuranceSalesPreviousMonthToDate decimal(18, 2)

 

Native SQL types are to be lower cased.

-- Correct

    DECLARE @_insuranceSalesPreviousMonthToDate decimal(18, 2)

-- Avoid

    DECLARE @_insuranceSalesPreviousMonthToDate DECIMAL(18, 2)

 

User Defined Types, a.k.a. aliases, should be Pascal cased

-- Correct

    CREATE TYPE dbo.SocialSecurityNumber FROM varchar(9)

-- Avoid

    CREATE TYPE dbo.socialSecurityNumber FROM varchar(9)

    CREATE TYPE dbo.Social_Security_Number FROM varchar(9)

 

CLR User Defined Types would follow the C# guidelines for casing and thus are Pascal cased

 -- Correct

     CREATE TYPE dbo.ClrUserDefinedType EXTERNAL NAME Assembly.ClassName


-- Avoid

    CREATE TYPE dbo.clrUserDefinedType EXTERNAL NAME Assembly.ClassName

    CREATE TYPE dbo.CLR_User_Defined_Type EXTERNAL NAME Assembly.ClassName

    CREATE TYPE dbo.CLR_USER_DEFINED_TYPE EXTERNAL NAME Assembly.ClassName

 

 

Naming conventions 

Do not abbreviate

-- Correct

    DECLARE @insuranceSalesPreviousMonthToDate decimal(18,2)

-- Avoid

    DECLARE @insSlsPrevMTD decimal(18,2)


 

Employ a consistent naming pattern within and across methods

-- Correct

    DECLARE @firstName char(10), @lastName char(10), @middleName char(10)

-- Avoid

    DECLARE @firstName char(10), @nameLast char(10), @mid_nm char(10)

 

Items should be named in a NounVerb pattern.  This will ensure that like items visually appear grouped together. The four standard verbs are Insert, Update, Delete and Get. If a combined Insert/Update statement is needed, its verb should be Add.

-- Correct

    CREATE PROCEDURE dbo.AdvisorInsert

    CREATE PROCEDURE dbo.AdvisorUpdate

    CREATE PROCEDURE dbo.AdvisorDelete

    CREATE PROCEDURE dbo.AdvisorGet

-- Avoid

    CREATE PROCEDURE dbo.SetAdvisor

    CREATE PROCEDURE dbo.UpdateAdvisor

    CREATE PROCEDURE dbo.AdvisorPurge

    CREATE PROCEDURE dbo.GetAdvisorAll

 

Hungarian notation is not to be used.  To this end, do not preface procedures and functions with usp and udf, even worse is the use of sp_.  What an object is will be quite apparent when you expand the Programmability tree, when you read the code or by querying the sys.objects table

-- Correct

    CREATE FUNCTION dbo.SalesGetByYear()

    CREATE PROCEDURE dbo.SalesInsert

-- Avoid

    CREATE FUNCTION dbo.udfSalesGetByYear()

    CREATE PROCEDURE dbo.uspSalesInsert

 

All tables should be aliased in queries and all columns should be fully qualified.  Abbreviations are preferred in aliases

-- Correct

    SELECT SLS.year, SLS.month FROM dbo.SALES_INVOICES_YEAR_TO_DATE SLS 

 

-- Avoid

    SELECT SALES_INVOICES_YEAR_TO_DATE.year, SALES_INVOICES_YEAR_TO_DATE.month FROM dbo.SALES_INVOICES_YEAR_TO_DATE

 

Join type should be fully qualified

-- Correct

    SELECT A.column1 FROM dbo.TABLEA A LEFT OUTER JOIN dbo.TABLEB B ON A.key = B.key

-- Avoid

    SELECT A.column1 FROM dbo.TABLEA A JOIN dbo.TABLEB B ON A.key = B.key

 

ANSI joins are preferred to Transact SQL style joins

-- Correct

    SELECT A.column1 FROM dbo.TABLEA A LEFT OUTER JOIN dbo.TABLEB B ON A.key = B.key

-- Avoid

    SELECT A.column1 FROM dbo.TABLEA A, dbo.TABLEB B WHERE A.key *= B.key

 

Transact-SQL reserved words should be capitalized  ms-help://MS.SQLCC.v9/MS.SQLSVR.v9.en/tsqlref9/html/ed8b3e27-6796-40f0-aef3-0cac5e0e2418.htm

 

 

Power items 

If you know about stored procedures named sp_OA*, let that knowledge remain in the recesses of your brain and do not use them here

 

Extended stored procedures are not to be written

 

 

Comments and versioning 

Employ a consistent style of commenting

 

Single line comments with a trailing space are preferred

-- Correct

    -- Insightful comment here

-- Avoid

    --Insightful comment here

 

Remove old blocks of code---do not hide it via comments. If you think you might need it later, place a comment in the code referencing the last tag in version control

 

Code should not be checked into the repository without a useful comment. Describe why the item was changed and what the scope of the changes are. Project references are great for further reading but they should not be the sole comment.

 

To paraphrase Socrates, the uncommented code is not worth maintaining.  At the beginning of all of your procedures and functions, you should have a statement of purpose. The header should indicate what the method does and what problem it solves. It should clearly indicate: any side-effects it will induce, what data it returns, assumptions about the values of its arguements, custom procedures and functions it invokes and the orientation towards transactions and atomicity

 

Tuesday, July 21, 2009

VS2010 inconsistent menus?

It could be that I'm just rusty but digging through the options for a new web site in 2010, I ran across the following inconsistency. If I wanted to create a new "Dynamic Data Linq to SQL Web Site", I can click the helpful background Start Page and select "New Project" That results in the following menu Menu without location (http/ftp/file) selector I could hear my coworker's voice though saying he prefers to create them in IIS but there doesn't seem to be an option for that. Digging further, if one clicks File, New, Web Site... that results in this menu Menu with web location selector

Now that I see both pictures together, I suppose the difference is between Web Site and Web application. So, maybe a worthless blog post unless you're just n00bing it up like me.

Monday, July 20, 2009

Facebook and Yahoo! do not mix

A friend recently had this experience. In a game on a popular social networking site, one of the goals is to obtain a progressively larger network of friends to move effectively play the game. In this case, I'm talking about Mafia Wars on FaceBook. A common practice is to randomly add people in groups dedicated to increasing group size and after linking up, "defriend" them. The game does not require you to remain friends for purposes of increasing your group size.

A spurned "friend" used the unfortunately visible personal information to engage in a simple identity theft. Where it gets ugly is Yahoo! They allow a person to recover their account by date of birth and postal code. If you are on FaceBook, look through your contacts and see how many have that information exposed. My friend recovered his account and changed the password but Yahoo!, in their infinite wisdom, allows you to recover your account forever once you know birthdate and postal code as those two items are immutable. Once your Yahoo! account is compromised by that vector, it's game over. There is no way to make the account safe again. The best you can do is shut it down and create a new account. That was their advice to my friend.

I've urged my friend to report the activity to FB as well as the Police Department of the assumed cracker's hometown. A wonderful double edged sword these social networking sites---the gentleman exposed enough information about himself to allow me to track down his age, city, high school (surprise) and his activities (6 mph runner) but as I have no desire to expose myself to legal issues, I won't join this troll in the gutters. I would like to call his parents though, that'd be delicious.

Friday, July 17, 2009

Google Voice rocks

Last night I received my invite for Google Voice which is their latest
product offering. http://www.google.com/voice Details of what it is
are there but my summary would be a portable phone number. It's your
phone number for life and it doesn't have to be in your area code. I
can set up a local phone number in Baltimore and when it's called,
it's seamlessly forwarded to my Kansas City number. It has call
screening for unknown number or just blocked numbers which should
handle the telemarketers the DNC doesn't handle.
The feature that has had me most excited is the voice transcription-it coverts speech to
text. Not that it transcribes your current conversation, it only
performs this task against voice mail. Those voice mails show up in a
dashboard, much like the current gmail inbox. The sender /subject is
the caller and the body of the message is the text transcription as
well as the original audio. I have not looked to see what format the
data is in but i would etpect some mp3 encoding. It doesn't stop
there of course. It can forward the transcript to another email
account or to your phone via sms. Screen shots to follow as well as
formatting cleanup since this post was written on my Diamond Touch
phone by Htc using handwriting transcriber. Soon Skynet will show
signs of sentience and the war will begin.

If you would like to see an example, call my house and leave a voice
mail with whatever message and your email address and I will send you
the text and audio. 816.200.0360

[edit]Did I think transcription was cool? Phone widgets blow that out of the water. "Call Widgets can be put on any web page, and allow people to call from that web page. When somebody clicks on the widget, we call them and connect them to you. Your number is always kept private. You can create multiple call widgets and have different settings for each of them." Check this out.

That widget, if you were to punch in your phone number, it's going to call me and your number to connect us for a phone call. How swank is that? Props to Andrew for pointing that one out. Seems there is plenty to dig in to
[/edit]