Tuesday, 16 August 2011

TSQL: Printing strings longer than 4000 characters


When you're debugging a dynamic SQL query, it can be helpful help to output the final query onto the message window. The main problem with this is that the PRINT command has a limit of 4000 unicode characters, as demonstrated here:

DECLARE @seqno AS int = 0
DECLARE @sql AS nvarchar(MAX)

SET @sql = 'SELECT ' + CAST(@seqno AS varchar) + ' AS sequence'+ CHAR(10)

WHILE (@seqno < 1000)
BEGIN
      SET @seqno = @seqno + 1
      SELECT @sql = @sql + 'UNION SELECT ' + CAST(@seqno AS varchar) + CHAR(10)
END

PRINT @sql --Limited to 4000 characters.
     
EXEC sp_executesql @sql

The string can be outputted in 4000 character chunks, but because each PRINT command adds it's own new-line character, the final result can appear chopped up and may be invalid. To overcome this, the following method finds an existing new-line character and uses it as a break off point:


DECLARE @seqno AS int = 0
DECLARE @sql AS nvarchar(MAX)

SET @sql = 'SELECT ' + CAST(@seqno AS varchar) + ' AS sequence'+ CHAR(10)

WHILE (@seqno < 1000)
BEGIN
      SET @seqno = @seqno + 1
      SELECT @sql = @sql + 'UNION SELECT ' + CAST(@seqno AS varchar) + CHAR(10)
END

/* Print entire statement */
DECLARE @SqlLength int = 0
DECLARE @CutOff int = 0
WHILE @SqlLength < LEN(@sql)
BEGIN
      SET @CutOff = 4000 - CHARINDEX(CHAR(10),REVERSE(SUBSTRING(@sql, @SqlLength, 4000)))
      PRINT SUBSTRING(@sql, @SqlLength, @CutOff)
      SET @SqlLength = @SqlLength + @CutOff + 1
END
/* END Print entire statement */
     
EXEC sp_executesql @sql

Tuesday, 31 May 2011

SSRS: Dynamic Datasets in Excel using SSRS

I recently came across a requirement for an Excel template which would contain a dataset which could be refreshed by the end user on demand.

For security reasons, we could not allow the user direct access to the sproc on our reporting database. Instead, I discovered a way to pull data into Excel from an SSRS report. This gives the benefit of using an already established reporting outlet with its own built-in security model.

To set this up (in Excel 2007), open a new sheet and go to the 'From Web' option in the Data ribbon. In the address bar, enter the URL of your report in the following format:

http://SERVERNAME/reportserver?REPORTPATH&rs:Format=html4.0&rc:Toolbar=false&rs:Command=render
(Note the string of options at the end of the URL (&rs:Format=html4.0&rc:Toolbar=false&rs:Command=render). These are necessary for the link to work correctly.)

eg: http://59.145.122.172/reportserver?/Report+Project1/Report2&rs:Format=html4.0&rc:Toolbar=false&rs:Command=render   [This server has nothing to do with me. Just some public SSRS server.]

Report parameters can be specified in the URL string by tagging them onto the end. eg. &StartDate=2011-01-01

Next, click the table selector attached to the main data table and click Import.

That's it. This dataset can now be refreshed by anyone who has access to the report.

*** One thing to point out is that the complete dataset must be visible on the first page of the report. To make sure of this, open up the RDL, go into the tablix properties and enable the 'Keep together on one page if possible' option.

SQL Server: Keep a Log of Your Executed Queries in SSMS

Every SQL Server DBA has experienced the odd Management Studio crash. They usually happen close to the end of a five hour session involving ten different tabs and a mountain of code. Occassionally, SSMS will look on you favouribly and restore some of your work. Usually, not.

A while back, I discovered an add-in for Management Studio which has helped me recover from these little frustrations. The SSMS Tool Pack is a suite of small utilities which generally make SSMS easier to work with. The most useful of these utilities is a query execution logger. It maintains two seperate logs. The first contains a copy of every query executed within SSMS. The second is a complete snapshot of the code sitting in the current tab.

As well as helping to recover from a SSMS crash, the logs can act as a memory aid. The execution log window allows you to search against execution date, server name and, of course, the code itself. Struggling to remember that smart-arse method you sussed out last year? Fire up the execution history window and tell it what you know.

By default, the Tool Pack's query log is saved to a file on the local drive. Although, it can be configured to save to a database table instead, which can speed up lookups and searches.

As well as the query logging tool, the Tool Pack contains 12 other handy utilities, including a code formatter, a tool to quickly store and recall snippets of code and a feature which adds a server-specific strip of colour to the each query tab - to help remind you that the DROP DATABASE command you are about to fire is going to be run on the live server.

The Tool Pack add-in is available for free download at http://www.ssmstoolspack.com. It's developed and maintained by one guy, Mladen Prajdić. So, if you get some use out of it, be sure to drop him a few Euros through his donations page.

Thursday, 26 May 2011

T-SQL: Including Total Row Count in Your Dataset

A common way to include aggregate amounts in a query is to use a subquery:
SELECT ProductID
     , Name
     , ProductNumber
     , Color
     , (SELECT COUNT(*) FROM Production.Product WHERE ProductID BETWEEN 970 AND 980) AS RC
FROM Production.Product
WHERE ProductID BETWEEN 970 AND 980
ProductID       Name                            ProductNumber           Color           RC
970             Touring-2000 Blue, 46           BK-T44U-46              Blue            11      
971             Touring-2000 Blue, 50           BK-T44U-50              Blue            11      
972             Touring-2000 Blue, 54           BK-T44U-54              Blue            11      
973             Road-350-W Yellow, 40           BK-R79Y-40              Yellow          11      
974             Road-350-W Yellow, 42           BK-R79Y-42              Yellow          11      
975             Road-350-W Yellow, 44           BK-R79Y-44              Yellow          11      
976             Road-350-W Yellow, 48           BK-R79Y-48              Yellow          11      
977             Road-750 Black, 58              BK-R19B-58              Black           11      
978             Touring-3000 Blue, 44           BK-T18U-44              Blue            11      
979             Touring-3000 Blue, 50           BK-T18U-50              Blue            11      
980             Mountain-400-W Silver, 38       BK-M38S-38              Silver          11      

This query returns the total number of rows in the dataset. However, it's bulky and requires duplication of code.
A neater solution is to use the PARTITION BY clause:
SELECT ProductID
     ,
Name
     ,
ProductNumber
     ,
Color
     ,
COUNT(*)
OVER (PARTITION BY 1)
AS RC
FROM
Production.Product
WHERE
ProductID BETWEEN 970 AND 980
The PARTITION BY clause can also be used to count the number of records falling into a particular category:
SELECT ProductID
     , Name
     , ProductNumber
     , Color
     , COUNT(*) OVER (PARTITION BY Color) AS RC
FROM Production.Product
WHERE ProductID BETWEEN 970 AND 980
ProductID       Name                            ProductNumber           Color           RC
977             Road-750 Black, 58              BK-R19B-58              Black           1       
978             Touring-3000 Blue, 44           BK-T18U-44              Blue            5       
979             Touring-3000 Blue, 50           BK-T18U-50              Blue            5       
970             Touring-2000 Blue, 46           BK-T44U-46              Blue            5       
971             Touring-2000 Blue, 50           BK-T44U-50              Blue            5       
972             Touring-2000 Blue, 54           BK-T44U-54              Blue            5       
980             Mountain-400-W Silver, 38       BK-M38S-38              Silver          1       
973             Road-350-W Yellow, 40           BK-R79Y-40              Yellow          4       
974             Road-350-W Yellow, 42           BK-R79Y-42              Yellow          4       
975             Road-350-W Yellow, 44           BK-R79Y-44              Yellow          4       
976             Road-350-W Yellow, 48           BK-R79Y-48              Yellow          4

This query returns the number of records which contain the same Color value as the current record.

Wednesday, 9 February 2011

T-SQL: Looping Without Cursors or Temporary Tables

Today, I found a neat way of looping through records in a table without running the risk of hogging memory up with cursor or temporary tables. This technique involves querying against the recordset one record at a time. @@ROWCOUNT is used to determine when the end of recordset has been reached.

Example:

USE AdventureWorks
DECLARE @loopid  INT, @loopval VARCHAR(255)

SELECT TOP 1 @loopid = AddressID, @loopval = AddressLine1
FROM   Person.Address
WHERE  City = 'Seattle'

WHILE @@ROWCOUNT > 0
  BEGIN
      PRINT @loopval -- do stuff here
      SELECT TOP 1 @loopid = AddressID, @loopval = AddressLine1
      FROM   Person.Address
      WHERE  City = 'Seattle'
             AND AddressID > @loopid
  END 

Because this techinque involves querying the table multiple times, it might not be a good idea to use it against a huge recordset, or one without a valid index.