Tags: SQL

T-SQL Tuesday #22 – Data Presentation

Tags: ,
Comments: No Comments
Published on: September 13, 2011

Tuesday is upon us.  It also happens to be the second Tuesday of the month and you know what that means.  It is time for TSQL Tuesday.  This month, Robert Pearl, a friend, is hosting.  Read his invitation here.

As the invitation suggests, this topic is wide open.  There are many ways to present data.  Some of those methods may be better than others.  There are a few things of note with Data Presentation that will impact how much better your Data Presentation will be.

The items I will discuss that will help your data be better presented are: Performance, Accuracy, Display, and Business Requirements.  I will use a few scripts to progress through each of these topics.  Let’s start with Performance.

Performance

Why is performance included in this discussion?  How does performance relate to data presentation?  Well, have you ever had an end-user complain that a report was utterly useless because it was too slow?  If the report is too slow, it won’t matter if the data is accurate in the eyes of some.  They needed to have the data yesterday and it simply took too long.

Here is an example of a query that could be optimized a bit more (admittedly this query does not perform soooo slowly that a user would give up on it – by the end you will see that it could perform better).

DECLARE @StartDate DATETIME = '2011-09-08'
		,@EndDate DATETIME = '2013-11-09'
		,@numMonths TINYINT
DECLARE @calendarTable TABLE (monthNum TINYINT, monthDays TINYINT)
 
SET @numMonths = (SELECT DATEDIFF(m,@StartDate,@EndDate) + 1)
 
WHILE @numMonths >= 1
        BEGIN
                INSERT INTO @calendarTable SELECT MONTH(@StartDate), DATEDIFF(d, @startDate, DATEADD(m, 1, DATEADD(d, 1 - DAY(@StartDate), @StartDate)))
                SET @numMonths = @numMonths - 1
                SET @StartDate = (SELECT DATEADD(m, 1, DATEADD(d, 1 - DAY(@StartDate), @StartDate)))
                --SELECT @StartDate
        END
 
SELECT * FROM @calendarTable
 
Go

The requirements for this script are simple.  Provide the number of days in a month in a given date range.  If the starting date provided is not the first of the month, then we must only provide the number of days from that date to the end of the month.

As you can see, this script utilizes a looping mechanism to provide this information.  The loop inserts into a table variable one record at a time.  When examining the execution plan and the execution time on this query, one would see that the insert is the most expensive part of the query.  One would also find that this query does take a fair amount of time to run – despite its’ simplicity.  On my machine, it takes about 1.3 seconds to execute.

Certainly, if this were a more complicated query, one would see that this type of query could cause some delays in data rendering and subsequently cause grief for the end-user and you.

There is one more issue with the provided query in that it doesn’t meet all requirements.  I neglected to mention that the date format needs to accept date/month/year format.  Yes it is nitpicking, but it was a requirement and an invalid date is far too easy to submit with this query.

Accuracy

There should be no question on how accuracy affects data presentation.  No matter how pretty the rest of the data/report may look, wrong data will render the report useless in the eyes of many business users.

Again, this example is not extreme – but it does create sufficient concern that the query should not be used – unless fixed.

SET DATEFORMAT DMY
 
DECLARE @StartDate      DATE = '05/06/2011'
        ,@EndDate       DATE = '31/08/2012'
 
;
WITH getmonths AS (
        SELECT CAST(CONVERT(VARCHAR,'01/' + CAST(MonthNum AS VARCHAR) + '/' + CAST(yr.yr AS VARCHAR)) AS DATE) AS Moy
                FROM ( VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)) Mnths (MonthNum)
                Cross Apply (SELECT YEAR(@StartDate) AS yr
                                                UNION
                                                SELECT YEAR(@EndDate) AS yr
                                        ) yr
), inputdates AS (
        SELECT dates FROM (VALUES (@StartDate),(@EndDate)) D (Dates)
        )
 
SELECT DATENAME(m,g.moy) AS MonthInRange,YEAR(g.moy) AS YrInRange
                ,DATEPART(d,DATEADD(mm, DATEDIFF(mm, 0, g.moy)+1, 0)-1)
                        - CASE WHEN DATEPART(d,id.Dates) = DATEPART(d,DATEADD(mm, DATEDIFF(mm, 0, g.moy)+1, 0)-1)
                                                THEN 0
                                        WHEN DATEPART(d,g.moy) < DATEPART(d,id.Dates)
                                                THEN DATEPART(d,id.Dates)
                                        ELSE Isnull(DATEPART(d,id.Dates),DATEPART(d,g.moy)-1)
                                END AS DaysInMonthInRange
        FROM getmonths g
                LEFT Outer Join inputdates Id
                        ON DATEPART(m,g.moy) = DATEPART(m,id.Dates)
                        And YEAR(g.moy) = YEAR(id.Dates)
        WHERE g.moy between @StartDate and @EndDate
                Or id.Dates between @StartDate and @EndDate
 
Go

Though this query works faster than the first query, it is not entirely accurate.  This query only supports a max of 12 months.  Also, this query is overly complex.  There is value in keeping this particular query more simple.

Display

This topic could be deemed to be largely a matter of preference.  That said, there is great value in how you display the data to the end user.  Displaying the data involves such things as useful column headings, and meaningful data.  For instance, naming a column “Month” but displaying a year value in it – is not very useful.  This could also overlap with the prior topic of accurate data.

Personally, when I am displaying the month, I like to see month names.  In my experience, displaying the name of the month is more easily recognizable than the month number for many users.

SET STATISTICS TIME ON
SET DATEFORMAT DMY
 
DECLARE @StartDate   DATETIME = '05/06/2011',
        @EndDate     DATETIME = '31/08/2013'
 
BEGIN
	WITH Nbrs_2( n ) AS (SELECT 1 UNION all
					SELECT 1 UNION all
					SELECT 1 UNION all
					SELECT 1 UNION all
					SELECT 1 UNION all
					SELECT 1 UNION all
					SELECT 1 UNION all
					SELECT 1 UNION all
					SELECT 1 UNION all
					SELECT 0),
			Nbrs_3( n ) AS ( SELECT 1 FROM Nbrs_2 n1 CROSS JOIN Nbrs_2 n2 CROSS JOIN Nbrs_2 n3)
	, tally (n) AS (SELECT ROW_NUMBER() OVER (ORDER BY n)
							   FROM Nbrs_3
							   )
	,cteMonthEnd AS
	(
	 SELECT t.N,
			MonthEnd = DATEADD(mm,DATEDIFF(mm,0,@StartDate) + t.N, 0) - 1
	   FROM Tally t
	  WHERE t.N BETWEEN 1 AND DATEDIFF(mm,@StartDate,@EndDate) + 1
	)
	 SELECT MonthEnd
		,NumDays     = DATEPART(dd,MonthEnd) - CASE WHEN N > 1 THEN 0 ELSE DATEPART(dd,@StartDate) END
	   FROM cteMonthEnd
	   ORDER BY MonthEnd ASC
END

In this case, the Column Heading matches the data and works.  However, a little change could make this query and output more useful to an end user who is quickly scanning over the results.

SET STATISTICS TIME ON
SET DATEFORMAT DMY
 
DECLARE @StartDate   DATETIME = '05/06/2011',
        @EndDate     DATETIME = '31/08/2013'
 
BEGIN
	WITH Nbrs_2( n ) AS (SELECT 1 UNION all
					SELECT 1 UNION all
					SELECT 1 UNION all
					SELECT 1 UNION all
					SELECT 1 UNION all
					SELECT 1 UNION all
					SELECT 1 UNION all
					SELECT 1 UNION all
					SELECT 1 UNION all
					SELECT 0),
			Nbrs_3( n ) AS ( SELECT 1 FROM Nbrs_2 n1 CROSS JOIN Nbrs_2 n2 CROSS JOIN Nbrs_2 n3)
	, tally (n) AS (SELECT ROW_NUMBER() OVER (ORDER BY n)
							   FROM Nbrs_3
							   )
	,cteMonthEnd AS
	(
	 SELECT t.N,
			MonthEnd = DATEADD(mm,DATEDIFF(mm,0,@StartDate) + t.N, 0) - 1
	   FROM Tally t
	  WHERE t.N BETWEEN 1 AND DATEDIFF(mm,@StartDate,@EndDate) + 1
	)
	 SELECT DATENAME(m,MonthEnd) AS [MONTH],YEAR(MonthEnd) AS [YEAR]
		,NumDays     = DATEPART(dd,MonthEnd) - CASE WHEN N > 1 THEN 0 ELSE DATEPART(dd,@StartDate) END
	   FROM cteMonthEnd
	   ORDER BY MonthEnd ASC
END

To this point, each query has progressively increased in performance.  This last query (both versions) also matches the need to accurately display the data.

The display of data is closely coupled with the next topic – Business Requirements.

Business Requirements

When looking at the results of the last two queries, which one is more accurate?  Well, that entirely depends on what the business has specified as acceptable output for this query.  In this case, both could be correct or both could be wrong.

It is essential to have the requirements written down and understood by all parties so as to avoid any misconception or misunderstanding of what the query should display.  Matching your output to the specified business requirements will help to provide an overall impression of accuracy and usefulness.  It will also help to achieve faster sign-off by the business.

Conclusion

Data Presentation is not just about the look of a report.  Data Presentation involves performance, accuracy, display and the business requirements.  By giving proper attention to each of these facets, the Data presented will be both remarkable and acceptable to the end-user.

Bitwise and Derived Table revisited

Categories: News, Professional, SSC
Tags: ,
Comments: No Comments
Published on: August 31, 2011

Today I am going to revisit two posts from the past couple of weeks.  I want to revisit them just to make some minor updates and clarifications.  This is nothing earth-shattering but is good info to have.

The two posts to revisit are:

  1. Bitwise Operations
  2. Derived Table Column Alias

Bitwise Operations

In this particular post, I shared a simple example of how to perform bitwise operations.  The example involved the bit comparison of up to three values.  I made the query overly complicated.  Here is a less complicated method to get to the same results.

DECLARE @ColorType INT = 3
 
SELECT cp1.*
	FROM ColorPlate cp1
WHERE cp1.colortype & @ColorType <> 0 
 
ORDER BY ColorID;

Can you see the simplicity in that?  Both methods work.  Looking at this code, it is a little easier to follow and understand.

Derived Table Column Alias

In the post about subqueries and derived tables, there was an important piece of information that I neglected.  In the first example I posted there is a good example of what was neglected.  The first example was a derived table based on values rather than a query.  Here is that example again.

SELECT *
	FROM (
			VALUES (1, 2), (3, 4), (5, 6), (7, 8), (9, 10)
		) AS MyTable(a, b);

If you were to try to write that query without the external column alias naming convention, you would get an error.  The error message(s) would be like the following.

Msg 8155, Level 16, State 2, Line 4
No column name was specified for column 1 of 'MyTable'.
Msg 8155, Level 16, State 2, Line 4
No column name was specified for column 2 of 'MyTable'.

Knowing this information could save you a bit of headache and time.  When using a value set rather than query, the column alias is required after the table alias.

Like I said, nothing big or fancy today – just a quick revisit to clarify some previous posts.  Oh, and I have some more good stuff coming down the pipe (like another bitwise related post).

Bitwise Operations

Tags: , ,
Comments: No Comments
Published on: August 19, 2011

Some time ago, I wrote an introductory post about bitwise operations in SQL Server.  I had fully intended on writing a follow-up to that.  Alas the opportunity has passed for the idea I was working on back then.

As luck would have it though, I encountered a new opportunity to share something on this topic.  This one came to me by once again helping out in the forums.  And, since I worked it out, I will be using the same problem posed in the forum and the solution I proposed.

First we need a little setup.  Let’s create a simple table and populate that table with some data.

CREATE TABLE ColorPlate (ColorID INT PRIMARY KEY IDENTITY(1,1),ColorPlate VARCHAR(10), ColorType INT)
 
INSERT INTO ColorPlate (ColorPlate,ColorType)
SELECT 'Red',1
UNION ALL
SELECT 'Blue',2
UNION ALL
SELECT 'Yellow',4

As I said, this setup is rather simple.  The solution is not much more complex.  However, before we get to the solution, we need to know what we need the solution to do.  From this table, I need to be able to determine the primary colors that make up a different color based on input of an ID relating to that color.  I know.  I know.  We don’t have all of the colors and their ColorTypes presented to us at this point – but let’s just go with it for a bit.  I would imagine that the other colors and the number assigned to their colortype would be populated at some other time.

For now, we are only working with seven color variations – so any number from 1-7 is a valid input.  How do we find all of the colors that are required for the number that we input?  Well, we use some smoke and mirrors.  Just kidding.  Seriously though, we use bitwise operations as well as a neat trick called “cross apply.”

DECLARE @ColorType INT = 3
 
SELECT cp1.*
	FROM ColorPlate cp1
	CROSS APPLY ColorPlate cp2
	CROSS APPLY ColorPlate cp3
WHERE cp1.colortype & cp2.colortype & cp3.colortype & @ColorType <> 0 
 
ORDER BY ColorID;

Do you see what is being done there?  I have known values in this table of 1,2, and 4.  I know that 7 is the max number I am allowing for input at this time.  Because of that, I know that I need three values in order to arrive at a value of 7.  Due to this requirement, I know I must Cross Apply the ColorPlate table twice beyond the first select from it.  That will permit me to sum three values from the ColorPlate table.

Now that I have access to three possible values, I need to compare those values using the Bitwise And operator.  This is denoted by ampersand ( & ).  Note that the where clause checks each of the three tables as well as the variable.  Then, I want to make sure that their bitand operation is not 0.  Pretty slick eh?

Let’s put it to action.  If I run the above query with a value of 6 for the @ColorType variable, I will get a two record result set.  The results returned would be the primary colors for green (which are Blue and Yellow).  If I use 7 for that same variable, I will get a three record result-set which would include red, blue and yellow.

This was a rather simple solution and scenario for a bitwise operation.  There are plenty of other examples out there of how to use these types of solutions.  Some more elaborate than others – but many good examples nonetheless.

I am interested in finding more solutions that involve these types of operations.  Who knows, maybe I will even be able to remember the neat stuff I learned while writing the last article on the topic and be able to put that up before too long.

Derived Table Column Alias

Comments: No Comments
Published on: August 18, 2011

By now, you have heard of subqueries.  You have also heard of Common Table Expressions.  I am sure you know what a derived table is and that you get a derived table through either a subquery or CTE.  How familiar are you with the subquery flavor of a derived table though?

I encountered something about derived tables recently that I had never seen, let alone heard of up to that point.  Let’s start with the Microsoft documentation on the topic.  If you browse to this page, you will find a description for column_alias immediately following the description of derived table.  What you don’t get is an example of how it is applicable.  Or do you?

If you look in the example of the derived table on that same page, you will see the following code (formatting added for readability).

SELECT *
	FROM (
			VALUES (1, 2), (3, 4), (5, 6), (7, 8), (9, 10)
		) AS MyTable(a, b);

Here, we can see that column_alias is optionally supplied after the table_alias for the derived table.  In this example, we have supplied two new column aliases called a and b.

If we want, we can take this a step further and see the same sort of example supplied by Sybase.

SELECT dt_b.book_title, dt_b.tot_sales
   FROM (SELECT title, total_sales
            FROM titles) dt_b (book_title, tot_sales)
   WHERE dt_b.book_title LIKE "%Computer%"

You can read the documentation about derived table syntax in Sybase, here, if you so desire.  The point of this is to show similar code and documentation between SQL Server and its resuscitated predecessor.

And for grins, you actually have the same sort of optional syntax available for the derived table known as a CTE.  You can see the documentation, from Microsoft, on that here.

So, how do we put this to use?  Well, I am glad you asked that.  I have an example ready to go.

DECLARE @StudentId INT = 1
		,@RequestDate DATE = '7/1/2011'
		,@RequestNbr INT = 2
		,@SchoolYear INT = 2011
		,@RequestTypeID INT = 1
 
;
WITH Request AS (
				SELECT RequestNbr = 2
					, RequestDate = @RequestDate
					, StudentId = 1
					, SchoolYear = 2011
					, RequestTypeID = 1
					, CompletionDate = null)
SELECT	 Request.StudentId
		,Request.RequestNbr
		,Request.RequestDate
FROM Request
JOIN (SELECT StudentId AS StudentNumber, MAX(RequestNbr) AS RequestNb
		FROM Request
		WHERE RequestTypeId = @RequestTypeId
			AND StudentId = @StudentId
			AND CompletionDate IS NULL
			AND SchoolYear	= @SchoolYear
		GROUP BY StudentId) AS Latest (StudentId, RequestNbr)
	ON Request.StudentId = Latest.StudentId
	AND Request.RequestNbr = Latest.RequestNbr

In this example, I have a derived table implemented through a subquery.  The alias of this derived table is “Latest.”  Note that there is an additional set of parenthesis after that table alias.  Inside this set of parenthesis, you will see a couple of column names.  Those columns are called StudentId and RequestNbr.

Now, I want you to take a look inside that derived table and note the names of the columns I provided in the aliases there.  See how those column_aliases are different than the column_aliases provided after the table_alias?  By looking at the query, can you tell which takes precedence?  Aliases supplied for columns in the optional column_alias outside of the derived table override the column_aliases of those provided inside the derived table.  You can verify that by looking at the join conditions provided after those aliases were defined.

Running this script, you will see it execute without error.  Using this kind of syntax could be useful in certain cases.  I think that it could make finding those column names considerably easier.  It could also help with readability.

Let’s take a quick look at the same kind of setup, but using a CTE instead.

DECLARE @StudentId INT = 1
		,@RequestDate DATE = '7/1/2011'
		,@RequestNbr INT = 2
		,@SchoolYear INT = 2011
		,@RequestTypeID INT = 1
 
;
WITH Request AS (
				SELECT RequestNbr = 2
					, RequestDate = @RequestDate
					, StudentId = 1
					, SchoolYear = 2011
					, RequestTypeID = 1
					, CompletionDate = NULL
), Latest (StudentId, RequestNbr) AS (
		SELECT StudentId AS StudentNumber, MAX(RequestNbr) AS RequestNb
			FROM Request
			WHERE RequestTypeId = @RequestTypeId
				AND StudentId = @StudentId
				AND CompletionDate IS NULL
				AND SchoolYear	= @SchoolYear
			GROUP BY StudentId
)
SELECT	 Request.StudentId
		,Request.RequestNbr
		,Request.RequestDate
FROM Request
JOIN Latest AS Latest
	ON Request.StudentId = Latest.StudentId
	AND Request.RequestNbr = Latest.RequestNbr

Note that I moved that entire derived table from subquery to be a new CTE defined immediately after Request.  Now take note of the difference in declaration between Request and Latest.  In Latest, I define the column names up front and have the columns aliased differently inside the CTE.  I do not define the column_alias list for the Request derived table.  You can also note that the colum_alias defined prior to the guts of the Latest derived table take precedence over any column_alias defined inside that particular derived table.

I hope this was new information to somebody else.  If you learned something new, let me know.

Top 5 Oracle Nuances I learned Today

Categories: News, Professional, SSC
Tags: ,
Comments: 1 Comment
Published on: August 16, 2011

I don’t do much with Oracle – at all.  Once in a blue moon, I find a little project to do that might involve Oracle.  I have never put a lot of thought to the differences between SQL and Oracle.  On the pet project I am doing right now, I put a little more thought into those differences and finally decided to write a little something about five things I am working with in the Oracle world and how those translate (or at least how I translated them) to the SQL world.

Let’s start with some very similar commands.

  1. substr().  In SQL, this translates to substring.  Easy enough right?  There is one more difference between the two than just the name.  The parameters are ordered differently in substr() than they are in substring().   Pay careful attention to your parameter sequence when converting this function from Oracle to SQL Server.
  2. instr().  This one is less obvious.  I have used PatIndex() and CharIndex() for this one – depends on needed functionality.  If you understand that instr is searching for a value within a string – it makes it a little easier to understand.  Also knowing that PatIndex searches for “Patterns” and Charindex() searches for a character is helpful.  If you need to supply the optional parameter used by instr(), then you should use Charindex.  Though not entirely the same – similar functionality is available in SQL for the instr() function.
  3. trunc().  This is a function used in Oracle to convert date and numbers to a shorter format (either different date format or fewer decimal places).  This is achieved through different means in SQL.  Two common methods are cast() and convert().
  4. dual.  This is not a function.  This is an internal table containing a single row.  There are many uses for this internal table.  One common use is equivalent to the Numbers/Tally table in SQL server.  Pick your favorite numbers/tally table method in these types of cases.
  5. connect by.  This is actually a pretty cool piece of functionality unique to Oracle.  I have seen this used in recursive CTEs to help control the hierarchy.  In these cases, it limits the result set to rows meeting the criteria of the connect by statement.  Similar functionality can be achieved through use of Joins and the Where clause.  This is a command that would be really cool in SQL.  It is true that you can build the hierarchy without this command in SQL.  I think it would help make that task easier and give it more flexibility.  It would also make it a little easier to read/understand.

This is all pretty cool.  It should be pretty straight forward stuff for most DBAs.  Some day, maybe we’ll explore a post dedicated to connect by and how some of the features of that command can be translated into SQL.  For now, just know that there is some commonality between the two RDBMSs – just a little translation may be necessary.

SSRS Export En Masse

Tags: , , ,
Comments: 5 Comments
Published on: August 8, 2011

Have you ever found yourself in need of exporting all of the reports from a Report Server?  I have found myself in need of extracting all RDLs from a Report Server on more than one occasion.  Reporting Services 2008 would rather have you do that in a rather monotonous and repetitive style.  That doesn’t work very well for me – and especially not if you have a ton of reports to export.

I combed the internet to see if there was a quick to implement way to do this – and found some recommendations.  Most of these  required the installation of another utility or the purchase of other software.  I found one site however that was extremely useful for what I wanted.  Brett Stateham used some queries to display the XML for these reports that is stored in the Catalog for Report Server.  You can read about that here.

Reading his blog post on the topic, I got the distinct idea that I could use a query to extract the xml and save it to an RDL file.  To verify this, I copied the XML (as returned by the queries on Bretts page) to a notepad file and save the file with an RDL extension.  Then I added that file back into a solution in Visual Studio and ran the report – sweet.  It worked.

Now that I know it can be done as simply as he displayed, it was time to take it a step further.  I now need to create something that will help me export numerous reports to their own individual files.  Hmmm, what could I use?  Having done something similar in the past for a single xml file in SSIS, I decided I would just use SSIS.  Thinking it through, I figured this should be pretty simple in appearance (a file destination object, an execute sql task, a data source, a file source, a foreach loop – you get the idea).

As I started to work through the solution, I found that I was over thinking it a bit and the solution could possibly be easier.  That made me wonder if a script task would be helpful here.  I decided to research and see if there was a quick way to write the xml to a file via a script task.  Guess what, there is.  Using VB .Net, there are simple methods inside a script task to write to the filesystem.

So, without further ado, let’s take a look at the package.

First, let’s create a few variables for use throughout the package.

  • objReport – This is of type object and will be used to store the results of our TSQL statement from an Execute SQL Task.
  • ReportExportPath – Type is String and will be the destination file path.  The last character for this variable should be a “\”
  • ReportName – Type is String and is for use by the ForEach Loop.  This variable will receive the report name from each iteration through the objReport and then be used by the Script Object to later give a name to the output RDL file.
  • ReportSourcePath – Type is String.  This variable is to help extract only the reports from a specific location.  In many cases, reports are stored in different folder paths and you may not need all folders’ contents.
  • ReportXML – Type is String.  Similar in function to ReportName
  • DBName – Type is String.  This value will be used to override the Initial Catalog of the Connection String
  • ServerName – Type is String.  This value will be used to override the Server Name of the Connection String.

Next, we need to create a connection object to create a data source for this package.  I created an ADO.Net connection object and named it ReportSourceDB.  Once created, use expressions from the properties tab to overwrite the Initial Catalog and Server Name values with the already mentioned variables.

Now that we have that stuff out of the way, we can begin work on creating the data flow.

Above is displayed the entire data flow for this package.  I adapted, albeit ever so slightly, the script we discussed at the beginning to the following for use in this package.

WITH ItemContentBinaries AS
(
  SELECT
     ItemID,Name,[Type]
   ,CASE Type
      WHEN 2 THEN 'Report'
      WHEN 5 THEN 'Data Source'
      WHEN 7 THEN 'Report Part'
      WHEN 8 THEN 'Shared Dataset'
      ELSE 'Other'
     END AS TypeDescription
   ,CONVERT(VARBINARY(MAX),Content) AS Content
   FROM ReportServer.dbo.CATALOG
   WHERE Type IN (2,5,8)
	And LEFT(PATH,LEN(@ReportPath)) = @ReportPath
),
--The second CTE strips off the BOM if it exists...
ItemContentNoBOM AS
(
  SELECT
     ItemID,Name,[Type],TypeDescription
    ,CASE
      WHEN LEFT(Content,3) = 0xEFBBBF
        THEN CONVERT(VARBINARY(MAX),SUBSTRING(Content,4,LEN(Content)))
      ELSE
        Content
    END AS Content
  FROM ItemContentBinaries
)
--The outer query gets the content in its varbinary, varchar and xml representations...
SELECT
  NAME
,CONVERT(xml,Content) AS ContentXML                --xml
FROM ItemContentNoBOM

This script is in the ExecuteSQL task.  The task is configured to retrieve the full result set and store it in objReport.  A parameter is specified and passed to the above query in the first CTE.  The following images should help clarify the configuration a bit.

General Tab:

Parameter Mapping Tab:

Result Set Tab:

So far so good.  This is now getting all of the data that we need from the ReportServer database.  We can now pass this information to the ForEach Loop container and write the XML out to RDL files.  The configuration of the ForEach Loop is not too complicated and looks like this:

Collection Tab:

Variable Mappings Tab:

Inside of this ForEach Loop container we have that script task that was displayed.  This was probably the trickiest (yet extremely simple) part of the whole exercise for myself.  I have used script tasks in the past for various tasks and was worried this might be a bit more complicated.  So let’s start with a couple of pictures.

There is no need to use a ReadWrite variable in this task.  This task simply reads the variable and then writes the contents of that variable out to a file.  Note that the script language is set to VB 2008.  The default (for me) was C# – and I changed that.  Once the variables are specified for ReadOnlyVariables, click the Edit Script button at the bottom of the screen.

In the new window that opens, highlight everything and overwrite it all with the following.

Imports System
Imports System.Data
Imports System.Math
Imports Microsoft.SqlServer.Dts.Runtime
 
<System.AddIn.AddIn("ScriptMain", Version:="1.0", Publisher:="", Description:="")> _
<System.CLSCompliantAttribute(False)> _
Partial Public Class ScriptMain
	Inherits Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase
 
	Enum ScriptResults
		Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success
		Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
	End Enum
 
    Public Sub Main()
        My.Computer.FileSystem.WriteAllText(Dts.Variables("ReportExportPath").Value + Dts.Variables("ReportName").Value + ".rdl", Dts.Variables("ReportXML").Value.ToString, False)
        Dts.TaskResult = ScriptResults.Success
    End Sub
 
End Class

The line that is important here is the line containing My.Computer.FileSystem inside Public Sub Main().  Note how I am using the variables we have created to this point to create the file name (first parameter series inside WriteAllText()) and also to write the contents of the file based on the ReportXML variable (that is the second parameter inside WriteAllText()).

From here, it is simply a matter of testing the package.  Run the package and check the directory you specified in the ReportExportPath variable.  This little project will save me an enormous amount of time in just exporting the reports to files from the database.  Yay, no more repetitive download task done through report manager.

Enjoy, and put it to good use.

T-SQL Tuesday #18 – CTEs

Comments: 3 Comments
Published on: May 10, 2011

To CTE or not to CTE, that is the Question

So my post really has nothing to do with the title.  This is not a post to help you determine whether a CTE is appropriate or not.  Or is it?

This month, we have the 18th installment in the TSQLTuesday series.  We are being hosted by Bob Pusateri  (Blog|@SQLBob) this month.  The essence of the topic this month is around CTEs (common table expressions).  There are a great many uses for a CTE in SQL server and this was a nice addition to the product.

One thing I like about CTEs is how much cleaner the code looks to me.  Another benefit for me is the recursion that is available through the use of a CTE.  An observation about CTEs is that a common use would be to use them to replace inline derived tables (which lends to cleaner looking code for some).

Think Think Think

I gave this topic a good long thought.  As I thought about the topic, I came to the conclusion that I had nothing new or unique on the subject.  I did however have some scripts that I posted once upon a time that would work very well for this topic.  Though it is a bit of a cop out, it is an appropriate solution for this month.

In the case of the CTE that I have chosen, there are multiple CTEs being used.  I use the CTE to recurse through data, and then to recurse that same data again – in reverse.  This particular script was created to traverse through system catalogs and create a hierarchy of table relationships.  I use this hierarchy to better understand the structure of the database and the interrelationship of the data between objects.  It is a cheap way of mapping out the objects in an effort to better understand it.

I had thought about using this script once upon a time for a different TSQLTuesday, but thought better of it that time.  Since the original post is more than a year old, it is a good time to bring it up and use it again.  Without further adieu, you can read about that script and CTE here.  I hope you enjoy.

Meme Monday – “I got 99 problems but a disk ain’t one”

Categories: Meme Monday, News, Professional, SSC
Tags: ,
Comments: No Comments
Published on: May 2, 2011

Today is Meme Monday and is a little thing that Thomas LaRock (Blog | Twitter)  has started.  Today he has started things off with a list of things that could cause a Database to go “wrong” that are not always disk related.

He has tagged a few people to add to the list in an effort to get up to 99 things that are unique that could cause failures in the database.  I haven’t been tagged, but wanted to throw out a few of my thoughts to add to the list.

  1. invoices – missing, unpaid, or wrong altogether
  2. vendors (misinformed, ship wrong items)
  3. undefined requirements
  4. bad documentation
  5. rampant use of sa
  6. no backups at all (full, log, or otherwise)
  7. very wide clustered indexes (i.e. three guids make up the PK and Clustered Index)
  8. P2V of a VLDB (not always bad but can be disastrous)
  9. Catch-all queries

These are some of the things I have seen cause some database problems.  Sure some of these things are more encompassing than just the database (like a vendor shipping the wrong part), but they can have a huge impact on the database.

A SQL Experiment – Mentoring

Categories: News, Professional, SSC
Comments: 1 Comment
Published on: April 26, 2011

News Flash

The SQL Community is about to get stronger.  Why?  There is a great new initiative that was recently launched.  Steve Jones and Andy Warren are teaming up to conduct this initiative.  As they aptly named it, the new initiative is called “The Mentoring Experiment.”

How many times have you wished you had somebody to show you the ropes?  Have you ever started a new job and been thrown to the wolves to try and figure things out on your own?  Many of us wish we had a mentor at some stage or another in our career – even if it is somebody more experienced with the current systems or applications in your current environment.

What is  a mentor?

A mentor is defined as 1) a wise and trusted counselor or teacher, or 2) an influential senior sponsor or supporter.  A mentor might also be referred to as a guide, counselor, master, or adviser.

A mentor is a person, in other words, that can be trusted to give you good advice and information.  This is a person to whom you should be able to bounce questions off of in order to learn more.  This person is somebody with more experience and/or knowledge, in the given domain, than you currently have.  And a mentor is a person who is willing to give a little service or volunteers to give of him/herself for the betterment of another individual.

What is the Goal?

Much the same as a mentor should have a Goal in regards to the padawan to help that person get better, Andy and Steve have a goal of improving mentoring in the SQL Server community.  There is a plan in place for this experiment.  Furthermore, there are multiple stages to this experiment.  The first stage is focused on a smaller group of mentors/mentees and the relationship that evolves between the two.

From the information gathered, they hope to be able to provide better information to mentors to determine if maybe there is something that a mentor is missing and needs to learn as well.

You can become involved in this project by submitting a short application.  Not everybody will be selected, but you can’t be selected if you don’t try.  I hope there will be plenty of community support for this experiment.  I look to provide support where I can.

SSIS Job Ownership

Comments: 2 Comments
Published on: April 11, 2011

I was strolling along one day when I saw somebody asking how to find out who owns a maintenance plan.  That evolved into finding out who owns the the job associated with the maintenance plan.  All of this in SQL 2005 and SQL 2008.

Well, we were stumped for a bit trying to figure the link between the job tables in the msdb database and the ssis table in the same database.  Linking the two together is not very obvious and we struggled with it for a bit.  After some research and trying this that and the other, I was able to come up with the below script.

-- Display SSIS Package Owners and Job Owners SQL 2008
 SELECT dts.[name]
        ,dts.[description]
        ,dts.createdate
        ,dts.ownersid
        ,p.name AS PackageOwner
        ,JOB.name AS JobOwner
        ,SSISPackageType =
			CASE dts.packagetype
				WHEN 0 THEN 'default value'
				WHEN 1 THEN 'SQL Server Import and Export Wizard'
				WHEN 2 THEN 'DTS Designer in SQL Server 2000'
				WHEN 3 THEN 'SQL Server Replication'
				WHEN 5 THEN 'SSIS Designer'
				WHEN 6 THEN 'Maintenance Plan Designer or Wizard'
			END
   FROM msdb.dbo.sysssispackages       dts
	LEFT Join master.sys.server_principals   p
		ON p.sid = dts.ownersid
	LEFT Outer Join msdb.dbo.sysjobsteps SJS
		ON dts.name = SUBSTRING(SJS.command,CHARINDEX('\',sjs.command)+1,charindex('"',sjs.command,CHARINDEX('\',sjs.command))-CHARINDEX('\',sjs.command)-1)
		And SJS.subsystem = 'ssis'
	Left Outer Join msdb.dbo.sysjobs SJ
		On SJS.job_id = SJ.job_id
	Left Outer Join master.sys.server_principals JOB
		On JOB.sid = SJ.owner_sid
Go

This script is set to work out of the gate with SQL 2008.  Should you want it to work with SQL 2005 the change is simple.  Change the sysssispackages table to sysdtspackages90.  As you can see, the query joins the SSIS table to the jobsteps table with a pretty nasty string extraction.  There are other ways of extracting this information (I’m sure of it).  This works quite well for what it is intended.

Using this script, you can find out the jobowner, the packageowner, and the packagetype.  This is pretty good information to have on hand if you have several ssis packages that are stored in msdb and are run from a job.  One thing this script does not yet handle is if the SSIS file is stored on the file system.  Note that I only coded it so far to work with files stored in SQL.  When looking in the jobsteps table, you can tell the difference quickly by seeing that those stored in msdb have a /SQL at the beginning of the command string.  Those in the filesystem have a /FILE en lieu of that /SQL.

In a future revision I will work on parsing the package name out of that string that represents those stored in the file system.  And despite that nasty join, this runs quickly on my systems.  I am open to suggestions or other solutions that can provide this kind of insight.

«page 2 of 7»
Calendar
May 2012
M T W T F S S
« Apr    
 123456
78910111213
14151617181920
21222324252627
28293031  
Follow me on Google+

In 0 people's circles

Add to circlesi
Content
Categories

Categories

Now Reading

Now Reading

Planned books:

Current books:

  • ChiRunning: A Revolutionary Approach to Effortless, Injury-Free Running

    ChiRunning: A Revolutionary Approach to Effortless, Injury-Free Running by Danny Dreyer, Katherine Dreyer

  • Advanced Marathoning – 2nd Edition

    Advanced Marathoning – 2nd Edition by Peter Pfitzinger, Scott Douglas

  • SQL Server MVP Deep Dives

    SQL Server MVP Deep Dives by Nielsen Paul, Delaney Kalen, Machanic Adam, Tripp Kimberly, Randal Paul, Low Greg

  • A World Without Heroes (Beyonders)

    A World Without Heroes (Beyonders) by Brandon Mull

Recent books:

View full Library

SQLHelp

SQLHelp


Welcome , today is Thursday, May 17, 2012