Thursday, 19 January 2012

Get @jamiet's SQLBits agenda in your calendar

@jamiet has created a brilliant calendar for SQL Bits - on his blog - Get the SQLBits agenda in your phone's calendar.

If you can't get the link to work, try this:

In Google Calendar, go to Other calendars -> Add By URL and paste this link: http://cid-550f681dad532637.calendar.live.com/calendar/SQLBits/calendar.ics

Add Calendar By URL

Click Add Calendar and you're done!

Tuesday, 27 December 2011

Blast from the past: Top 10 tips for database developers

New year means a clear out! Stumbled across this written between 2007 and 2009. I sound really lecturing, pompous and stuffy! I think this is the point I started doing more development than database administration. At the time it helped, not all of the tips would my top 10 today. The great thing about finding it? Food for thought on rewriting.

If you could go back and give your younger self 10 tips, what would be yours? Do you disagree with any of the advice? Would love your thoughts.

1. Invest in good database design.
A poor database design will impact query performance and data quality to a larger extent than purchasing additional hardware. Once a well designed, fully normalised database has been achieved, ensure that modifications to the design are not carried out in an organic fashion but are subjected to the same normalisation checks of the initial design.

2.Use the right data types.
As well as designing the database correctly, data types should be the smallest required. This should take into account future growth as well. For example, do not store numbers or dates as strings, nor booleans as integers.

3. Images and files do not belong in the database.
Instead, store the path to the object in the database and store the object in the file system.

4. Put business rules in the DBMS
Use the appropriate objects to enforce business rules at the database level. This can improve performance, for example, trusted check constraints will affect query optimisation. More importantly, they ensure data integrity. At a column level, ensure NULLs are only allowed where required. At a table level, ensure check constraints and foreign key relationships are specified. Use triggers prudently, both at a table and database level.

5. Use surrogate keys.
A surrogate key is a unique identifier that has no business related value. An sequential surrogate key that never requires modification as the primary key and clustered index, will ensure that other indexes created on the table are smaller in size and more efficient.

6. Be prudent with indexes.
The need to create too many indexes may indicate poor database design. Use the “Included Columns” feature to extend the usefulness of indexes for heavily used queries.

7. Make Where clauses SARGable.
A SARGable where clause takes advantage of indexes on a table to speed up completion of the query. The following search arguments will usually prevent query optimisation: “IS NULL”, “<>”, “!=”, “!>”, “!<”, “NOT”, “NOT EXISTS”, “NOT IN”, “NOT LIKE”, “LIKE ‘%xxx’”.
Additionally, using a function within a Where clause like “WHERE DATEADD(yy, 21, ReportDate) > 21” etc will usually prevent query optimisation. Use a variable instead of a function or rewrite the Where clause to not include the column name in these cases.

8. Use stored procedures.
Provided the SQL statements within the stored procedures are SARGable, a stored procedure will be compiled and improve execution performance. It will also ensure that business rules can be encapsulated and reused.

9. Avoid using cursors.
There is rarely a requirement to use a cursor or While statement. If a row-by-row operation is necessary, it could indicate poor database design and can be very expensive.

10. Test and measure.
The only way to be sure that a change will benefit is to test and measure the impact. Where possible, a replica of the live environment is recommended to ensure performance testing can also be carried out.

Saturday, 24 December 2011

Add your Amazon Wishlist to Blogger in 3 easy steps


Step 1: Create an Amazon widget.

Go to https://widgets.amazon.co.uk/Amazon-Wishlist-Widget/ (change amazon.co.uk to your Amazon site) and sign with your Amazon Associates details in to earn referral fees.



No account? No worries, just sign up to earn fees or skip the sign up and search for your Amazon wishlist. You can search for an Amazon wishlist by name or email, then pick the Amazon wishlist you want and use the wizard to give your list a name, tweak the colour, size etc. (I used a width of 200 px. which fits neatly in my Blogger side bar).

Click "Add to my Web page" to generate the Amazon wishlist widget code, copy and save your wishlist widget.



Step 2: Create a Blogger widget

In Blogger, go to the layout section of your blog and add a HTML/Javascript widget to the section you'd like it to appear in. I put mine in the side bar.



Paste your Amazon wishlist widget code and add a title if you'd like. I left the title blank as the Amazon wishlist widget already has one. Save your Blogger HTML/Javascript widget.


Step 3: Preview, tweak & save

Preview to check the Amazon wishlist widget shows up and is sized and positioned properly. You can go back and edit your Amazon wishlist widget as many times as you'd like and use the Blogger preview to refresh till it looks right. Save the layout to update your blog.



Add Amazon wishlist to Blogger - done!

Wednesday, 21 December 2011

Study Brain Dump: Administering Service Broker

Delving into the delights of SQL Service Broker. I've started a brain dump, might do a mindmap too, helps to focus my brain when faced with huge blocks of text!

Study brain dump: Administering Service Broker

Trying out the Google docs iframe - what do you think?

Wednesday, 12 October 2011

Switch between servers in one t-sql script with SQLCMD scripting

If you haven't used SQLCMD scripting, you're missing a trick. Switching between query windows can be a pain, but in SQL 2005+, you can save time and effort with these commands.

SQLCMD is a command line utility for running SQL queries, enabling the option in SSMS can either be:

Temporary: Query -> SQLCMD mode



Permanent: Tools -> Options -> Query Execution -> By default, option new queries in SQLCMD mode.



Now it's switched on, you can run code like this:

:CONNECT .
--Connects to the local instance
USE [Northwind];
SELECT TOP 1 [OrderID], [CustomerID]
FROM dbo.[Orders];
-- Need the GO statement in-between to split this into two batches.
GO
:CONNECT .\SQLEXPRESS
--Connects to the sqlexpress instance
USE [Northwind];
SELECT TOP 1 [ProductName], [Discontinued]
FROM   dbo.[Products];
GO

The results?


More info:
Editing SQLCMD Scripts with Query Editor

Mindmap:

Wednesday, 22 December 2010

Extending code: Microsoft SQL Server 2008 Integration Services: Problem, Design, Solution

The book was very useful when I first began developing in SSIS 2005/2008 packages. The original code can be is available to downloand from Wrox's website: Microsoft SQL Server 2008 Integration Services: Problem, Design, Solution

There were two things missing which my client wanted - record counts and the ability to restrict the level of information recorded.  For the logging level, I created 3 levels 0, 1 and 2.

Level 0 - Logging is off
Level 1 - Logging is minimal (excludes variable logging)
Level 2 - Logging is verbose

There are 6 stored procedures for logging events from the SSIS packages:

  1. LogPackageEnd
  2. LogPackageError
  3. LogPackageStart
  4. LogTaskPostExecute
  5. LogTaskPreExecute
  6. LogVariableValueChanged
The @LoggingLevel int parameter was added to each of the procedures to capture the user variable LoggingLevel passed in by the SSIS package. A check was added to each stored procedure to reflect the levels above.

Each of the SQL tasks in the SSIS package was amended to include the User::LoggingLevel in the Parameter Mapping section and the SQL statement amended to include @LoggingLevel = ? statement.

For the record count, the PackageTaskLog table was extended to include RecordsAffected and the LogTaskPostExecute stored procedure extended to include @RecordsAffected int parameter defaulting to 0. Changes were made in the SSIS package to pass this information in.

In Data Flow tasks, I used the Row Count component to record record counts to the User::RecordsAffected variable. For SQL tasks, I returned RecordsAffected whenever a stored procedure was called and saved this information to user variable via the Result Set.

Code is below and a sample package will follow:


/* Extend the PackageTaskLog table */
IF NOT EXISTS (
  SELECT * FROM information_schema.COLUMNS 
  WHERE column_name='RecordsAffected' and table_name='PackageTaskLog' and table_schema='dbo'
  )
BEGIN
 ALTER TABLE dbo.PackageTaskLog ADD [RecordsAffected] [int] NULL
END
GO
/* Alter the stored procedures */
ALTER PROCEDURE [dbo].[LogVariableValueChanged]
(    @PackageLogID int
    ,@VariableName  varchar(255)
    ,@VariableValue  varchar(max)
 ,@LoggingLevel int

)
AS
BEGIN
 SET NOCOUNT ON
 /* Check if logging is verbose */
 IF @LoggingLevel = 2 and (select 1 from dbo.PackageLog where PackageLogID = @PackageLogID) = 1
 BEGIN
  INSERT INTO dbo.PackageVariableLog(PackageLogID, VariableName, VariableValue, LogDateTime)
  VALUES (@PackageLogID, @VariableName, @VariableValue, getdate())
 END
END
GO
ALTER PROCEDURE [dbo].[LogTaskPreExecute]
(    @PackageLogID int
    ,@SourceName varchar(64)
    ,@SourceID uniqueidentifier
    ,@PackageID uniqueidentifier
 ,@LoggingLevel int

)

AS
BEGIN
    SET NOCOUNT ON
 /* Check if logging is verbose = 1 */
 IF @PackageLogID > 0
 BEGIN
  IF @PackageID <> @SourceID
   AND @SourceName <> 'SQL LogPackageStart'
   AND @SourceName <> 'SQL LogPackageEnd'
   INSERT INTO dbo.PackageTaskLog (PackageLogID, SourceName, SourceID, StartDateTime)
   VALUES (@PackageLogID, @SourceName, @SourceID, getdate())
 END

 SELECT 0 AS RecordsAffected
END
GO
ALTER PROCEDURE [dbo].[LogPackageEnd]
(    @PackageLogID int
    ,@BatchLogID int
    ,@EndBatchAudit bit
 ,@LoggingLevel int
)

AS
BEGIN
    SET NOCOUNT ON

 /* Check if logging is disabled */
 IF @LoggingLevel > 0
 BEGIN
  UPDATE dbo.PackageLog
   SET Status = 'S'
   , EndDatetime = getdate()
   WHERE PackageLogID = @PackageLogID

  IF @EndBatchAudit = 1
  BEGIN
   UPDATE dbo.BatchLog
   SET Status = 'S'
   , EndDatetime = getdate()
   WHERE BatchLogID = @BatchLogID
  END
 END
END
GO
ALTER PROCEDURE [dbo].[LogPackageError]
(    @PackageLogID int
    ,@BatchLogID int
    ,@SourceName varchar(64)
    ,@SourceID uniqueidentifier
    ,@ErrorCode int
    ,@ErrorDescription varchar(2000)
    ,@EndBatchAudit bit
 ,@LoggingLevel int
)

AS
BEGIN
    SET NOCOUNT ON

 /* Check if logging has been disabled */
 IF @LoggingLevel > 0
 BEGIN
  INSERT INTO dbo.PackageErrorLog (PackageLogID, SourceName, SourceID, ErrorCode, ErrorDescription, LogDateTime)
  VALUES (@PackageLogID, @SourceName, @SourceID, @ErrorCode, @ErrorDescription, getdate())

  UPDATE dbo.PackageLog
   SET Status = 'F'
    , EndDatetime = getdate()
   WHERE PackageLogID = @PackageLogID

  IF @EndBatchAudit = 1
  Begin
  UPDATE dbo.BatchLog
   SET Status = 'F'
   , EndDatetime = getdate()
   WHERE BatchLogID = @BatchLogID
  End
 END
END
GO
ALTER PROCEDURE [dbo].[LogPackageStart]
(    @BatchLogID int
    ,@PackageName varchar(255) 
    ,@ExecutionInstanceID uniqueidentifier 
    ,@MachineName varchar(64) 
    ,@UserName varchar(64) 
    ,@StartDatetime datetime 
    ,@PackageVersionGUID uniqueidentifier 
    ,@VersionMajor int 
    ,@VersionMinor int 
    ,@VersionBuild int 
    ,@VersionComment varchar(1000) 
    ,@PackageGUID uniqueidentifier 
    ,@CreationDate datetime 
    ,@CreatedBy varchar(255) 
 ,@LoggingLevel int
)

AS
BEGIN
SET NOCOUNT ON

DECLARE @PackageID int
,@PackageVersionID int 
,@PackageLogID int
,@EndBatchAudit bit

/* Check if logging has been disabled: LoggingLevel = 0*/
IF @LoggingLevel > 0
BEGIN
 /* Initialize Variables */
 SELECT @EndBatchAudit = 0

 /* Get Package Metadata ID */
 IF NOT EXISTS (SELECT 1 FROM dbo.Package WHERE PackageGUID = @PackageGUID AND PackageName = @PackageName)
 Begin
  INSERT INTO dbo.Package (PackageGUID, PackageName, CreationDate, CreatedBy)
   VALUES (@PackageGUID, @PackageName, @CreationDate, @CreatedBy)
 End

 SELECT @PackageID = PackageID
  FROM dbo.Package 
  WHERE PackageGUID = @PackageGUID
  AND PackageName = @PackageName

 /* Get Package Version MetaData ID */
 IF NOT EXISTS (SELECT 1 FROM dbo.PackageVersion WHERE PackageVersionGUID = @PackageVersionGUID)
 Begin
  INSERT INTO dbo.PackageVersion (PackageID, PackageVersionGUID, VersionMajor, VersionMinor, VersionBuild, VersionComment)
   VALUES (@PackageID, @PackageVersionGUID, @VersionMajor, @VersionMinor, @VersionBuild, @VersionComment)
 End
 SELECT @PackageVersionID = PackageVersionID
  FROM dbo.PackageVersion 
  WHERE PackageVersionGUID = @PackageVersionGUID

 /* Get BatchLogID */
 IF ISNULL(@BatchLogID,0) = 0
 Begin
  INSERT INTO dbo.BatchLog (StartDatetime, [Status])
  VALUES (@StartDatetime, 'R')
  SELECT @BatchLogID = SCOPE_IDENTITY()
  SELECT @EndBatchAudit = 1
  /* Update previous batches to 'E' - error if 'R' - Running */
  UPDATE dbo.BatchLog
  SET [Status] = 'E'
  WHERE BatchLogID != @BatchLogID
  AND [Status] = 'R'
 End

 INSERT INTO dbo.PackageLog (BatchLogID, PackageVersionID, ExecutionInstanceID, MachineName, UserName, StartDatetime, [Status])
  VALUES(@BatchLogID, @PackageVersionID, @ExecutionInstanceID, @MachineName, @UserName, @StartDatetime, 'R')

 SELECT @PackageLogID = SCOPE_IDENTITY()


END

SELECT @BatchLogID as BatchLogID, @PackageLogID as PackageLogID, @EndBatchAudit as EndBatchAudit

END
GO
ALTER PROCEDURE [dbo].[LogTaskPostExecute]
(    @PackageLogID int
    ,@SourceID uniqueidentifier
    ,@PackageID uniqueidentifier
 ,@LoggingLevel int
 ,@RecordsAffected int = 0
)

AS
BEGIN
    SET NOCOUNT ON

 /* Do not log if logging is off */
 IF @LoggingLevel > 0
 BEGIN
  IF @PackageID <> @SourceID
   UPDATE dbo.PackageTaskLog 
    SET EndDateTime = getdate(), RecordsAffected = @RecordsAffected
    WHERE PackageLogID = @PackageLogID AND SourceID = @SourceID
     AND EndDateTime is null
 END
END

Tuesday, 12 October 2010

SQL Server 2008 R2 Trial

It's been far too long since I blogged anything. So a reboot seemed apt, since I'm now looking very seriously at the MCITP certifications.

To help this along, I'm downloading the 180 day SQL Server 2008 R2 Trial from Microsoft. The time limit is generous enough to complete the bulk of the study, you simply have to register first. This is made even easier using your Live account, a couple of clicks and away you go.

I downloaded this using Firefox, which worked out quite well. It's the first time I've successfully done this so kudos to Microsoft for fixing whatever prevented it in the past. The 32-bit exe is a slimline 1.31GB, which is great as it will be easy to move to the dedicated SQL PC.

Now, to install on a brand, spanking, shiny new laptop with Windows 7 ...