Categories: SQLServerPedia Syndication

Performance Tuning – Over the Top Biz Logic

Comments: No Comments
Published on: September 19, 2011

This month, Robert Pearl is helping to host a Performance Story contest.  The contest is being sponsored by Safe Peak, and you can see more about it – here.

The timing on this contest actually fits well with something I have already been working on.  The only delay has been related to getting everything scrubbed and obfuscated.  There is a lot that could be included with this particular experience, I plan on touching on two of the key areas.

Backstory

I have been working a lot lately with building reports in SSRS.  These reports had been working great from IE hitting directly to the SSRS Server in one environment.  When we migrated the reports to the production environment – we started seeing some serious timeout issues.

There was one report in particular that was giving more grief than others.  I had already gone through everything I could find with regards to SSRS in trying to help speed these reports up to acceptable times.

Having gone through what could be done in SSRS, I next turned my attention to the main stored procedure creating the data-set for the report.

Stored Procedure

Although the report was not even making it to this particular stored procedure – I wanted to take a look at it and make any improvements there that I could.  It made sense to eliminate it as a possible source of the problem.

I opened up the stored procedure and found a fair amount if Business Logic.  I came to a section of the proc that contained code that at first sight, made my head spin a bit.  I thought for sure I was seeing double because of all of the logic.

Once I finally got my head wrapped around it, I noticed a lot of similarities and wanted to start from that particular section with my tuning.  Here is an obfuscated version of what I saw.

DECLARE @CompareParam INT = 200
 
		SELECT tccv.MyColumn
			FROM dbo.table1 AS cb
				INNER JOIN dbo.table2 tcfbt
					ON cb.column1 = tcfbt.column1fk
    			INNER JOIN dbo.table3 AS cpd
    				ON cb.column2 = cpd.column2fk
				INNER JOIN dbo.table4 AS tcfcpt
					ON cpd.column3 = tcfcpt.column3fk
				INNER JOIN dbo.table5 AS tcfcpct
					ON cpd.column4 = tcfcpct.column4fk
				INNER JOIN dbo.table6 cp
					ON cb.column5 = cp.column5fk
				INNER JOIN dbo.table7 cal
					ON cp.column6 = cal.column6fk
				INNER JOIN dbo.table8 tgmc
					ON cal.column7 = tgmc.column7fk
				INNER JOIN dbo.table9 tccv
					ON tgmc.column8 = tccv.column8fk
				INNER JOIN dbo.table10 fps
					ON cp.column9 = fps.column9fk
        WHERE
            cb.column5 = @CompareParam
	        and
	        (
	            tcfbt.CodeVal = 'In-Progress'   -- use cases 1,2,3
	            and
                (
                    ( -- use case 1
                        cp.column5fk = @CompareParam
                        and fps.CodeVal not in ('Val1','Val2')
                        and tccv.Date1 between cp.Date2 and ISNULL(cp.Date3, cp.Date4)
                    )
                    --use case 2
                    or
                    ( cp.column5fk = @CompareParam
						and tccv.Date1 < cp.Date2
						and not exists (
							SELECT 1
								FROM dbo.table11 cvcp
									INNER JOIN dbo.table6 prevcp
										ON cvcp.column5 = prevcp.column5fk
									INNER JOIN dbo.table10 prevfps
										ON prevfps.column9fk = prevcp.column9
								WHERE prevcp.column6 = cp.column6
									and prevcp.Date4 < cp.Date2
									and cvcp.MyColumnfk = tccv.MyColumn
									and prevfps.CodeVal in ('Val1','Val2')
						)
						and exists (
							SELECT 1
								FROM dbo.table6 AS pastcp
									INNER JOIN dbo.table10 AS pastfps
										ON pastcp.column9 = pastfps.column9fk
								WHERE pastcp.column6 = cp.column6
									and pastcp.Date4 < cp.Date2
									and tccv.Date1 between pastcp.Date2 and pastcp.Date4
									and pastfps.CodeVal in ('Val1','Val2')
						)
                    )
                )
             )
             or
             (
				-- same as the other side of 'OR' just above
                 tcfbt.CodeVal = 'Live'
				and not exists (
					SELECT 1
						FROM dbo.table12 cvh
						WHERE tccv.MyColumn = cvh.MyColumnfk
				 )
                 and
                 (
                    ( -- use case 1
                        cp.column5fk = @CompareParam
                        and fps.CodeVal not in ('Val1','Val2')
                        and tccv.Date1 between cp.Date2 and ISNULL(cp.Date3, cp.Date4)
                    )
                    --use case 2
                    or
                    (
						cp.column5fk = @CompareParam
						and tccv.Date1 < cp.Date2
						and not exists (
							SELECT 1
								FROM dbo.table11 cvcp
									INNER JOIN dbo.table6 prevcp
										ON cvcp.column5fk = prevcp.column5fk
									INNER JOIN dbo.table10 AS prevfps
										ON prevcp.column9 = prevfps.column9fk
								WHERE prevcp.column6 = cp.column6
									and prevcp.Date4 < cp.Date2
									and cvcp.MyColumnfk = tccv.MyColumn
									and prevfps.CodeVal in ('Val1','Val2')
						)
						and exists (
							SELECT 1
								FROM dbo.table6 AS pastcp
								INNER JOIN dbo.table10 AS pastfps
									ON pastcp.column9 = pastfps.column9fk
							WHERE pastcp.column6 = cp.column6
								and pastcp.Date4 < cp.Date2
								and tccv.Date1 between pastcp.Date2 and pastcp.Date4
								and pastfps.CodeVal in ('Val1','Val2')
						)
                    )
				)
	          )

I want you to look closely at both sides of that OR condition in the where clause before proceeding to look at the image of the execution plan that this query generates.

Again, my head started spinning when I first looked at the execution plan.  It was the size of the overall plan that was causing it this time.  Looking through it though, I quickly saw what I thought was the main point of slowness with this query (yes it was slow).  If you open the image of that execution plan and compare the red section to the yellow section, you will see that it is duplicated.  These sections also correlate to each side of the OR condition that was already mentioned.

When browsing this execution  plan in SSMS, I was also lured into these sections due to a bad estimate on the query cost (which you don’t see in this image).  All of the Index Spools and Index scans/seeks had a cost of 89% attached to them (within the red and yellow regions).  Seeing that both sections were identical – I was certain that the OR condition could be optimized.

DeDuping

Since the queries were soooo similar, I knew there had to be a way to combine both sides and make it less costly.  After working on it a bit, I came up with the following query.

DECLARE @CompareParam INT = 200
 
		SELECT tccv.MyColumn
			FROM dbo.table1 AS cb
				INNER JOIN dbo.table2 tcfbt
					ON cb.column1 = tcfbt.column1fk
    			INNER JOIN dbo.table3 AS cpd
    				ON cb.column2 = cpd.column2fk
				INNER JOIN dbo.table4 AS tcfcpt
					ON cpd.column3 = tcfcpt.column3fk
				INNER JOIN dbo.table5 AS tcfcpct
					ON cpd.column4 = tcfcpct.column4fk
				INNER JOIN dbo.table6 cp
					ON cb.column5 = cp.column5fk
				INNER JOIN dbo.table7 cal
					ON cp.column6 = cal.column6fk
				INNER JOIN dbo.table8 tgmc
					ON cal.column7 = tgmc.column7fk
				INNER JOIN dbo.table9 tccv
					ON tgmc.column8 = tccv.column8fk
				INNER JOIN dbo.table10 fps
					ON cp.column9 = fps.column9fk
        WHERE
            cb.calendarPeriodID = @CompareParam
	        and
	        (
	           ( tcfbt.CodeVal = 'In-Progress'   -- use cases 1,2,3
						OR (tcfbt.CodeVal = 'Live'
							and not exists (
								SELECT 1
									FROM dbo.table12 cvh
									WHERE tccv.MyColumn = cvh.MyColumnfk
							)
						)
					)
	            and
                (
                    ( -- use case 1
                        cp.column5fk = @CompareParam
                        and fps.CodeVal not in ('Val1','Val2')
                        and tccv.Date1 between cp.Date2 and ISNULL(cp.Date3, cp.Date4)
                    )
                    --use case 2
                    or
                    ( cp.column5fk = @CompareParam
						and tccv.Date1 < cp.Date2
						and not exists (
							SELECT 1
								FROM dbo.table11 cvcp
									INNER JOIN dbo.table6 prevcp
										ON cvcp.column5fk = prevcp.column5fk
									INNER JOIN dbo.table10 prevfps
										ON prevfps.column9fk = prevcp.column9
								WHERE prevcp.[calendarID] = cp.[calendarID]
									and prevcp.Date4 < cp.Date2
									and cvcp.MyColumnfk = tccv.MyColumn
									and prevfps.CodeVal in ('Val1','Val2')
						)
						and exists (
							SELECT 1
								FROM dbo.table6 AS pastcp
									INNER JOIN dbo.table10 AS pastfps
										ON pastcp.column9 = pastfps.column9fk
								WHERE pastcp.column6 = cp.column6
									and pastcp.Date4 < cp.Date2
									and tccv.Date1 between pastcp.Date2 and pastcp.Date4
									and pastfps.CodeVal in ('Val1','Val2')
						)
                    )
                )
             )

Now take a close look at the first part of the where clause.

WHERE
            cb.calendarPeriodID = @CompareParam
	        and
	        (
	           ( tcfbt.CodeVal = 'In-Progress'   -- use cases 1,2,3
						OR (tcfbt.CodeVal = 'Live'
							and not exists (
								SELECT 1
									FROM dbo.table12 cvh
									WHERE tccv.MyColumn = cvh.MyColumnfk
							)
						)
					)
	            and

That little change represents the only difference between the two sides of the Or condition in the prior query.  The result set is exactly the same.  Now we get a new execution plan that looks like this.

In this execution plan, you can see that there is now only one section matching the red and yellow sections from the previous query.  This is a very good thing.  We can see that the plan looks simpler than the last one.  What about if we execute the two queries in a single batch for comparison?

 

The improvements we see are substantial.  Look at the difference in cost comparison between the two!!  The old query consumes 97% of the cost.  To further illustrate the improvements by making this little change in logic, let’s take a look at the time.

Timecompare_withExecplan

Timecompare_withExecplan

Timecompare_withoutExecplan

Timecompare_withoutExecplan

 

 

 

 

 

 

 

 

Looking at the time it takes to run these queries should also be a strong indicator of the performance gains made by simplifying that where clause.  Let’s also take a look at another couple of screen-shots that helps to show some of the different improvements made by simplifying that “OR” condition.

Notice the old plan has an index scan, sort operation and 61 parallel operations.  The new plan has a key lookup.  Now look at the glaring difference between the two in estimated rows.  That is significant.

That is not all that was done with the query itself to improve performance.  Other things like index tuning and more work on the rest of the query was also done with some improvements in overall execution of the query.  But none were as significant as this.

TCP/IP

The other place where significant improvement was made in the performance of this query came from something outside of SQL Server.  A key part of this exercise has not yet been mentioned.  The reports were all taking 30-60 seconds to even load without any parameters having been selected.  Then they would take another 30 seconds or so after that to bring up the next parameter.

We ran a trace between the two servers to see what we could find.  After some more troubleshooting, we decided to try disabling the TCP Chimney.  This change had a significant impact in loading the reports from SSRS.  From within IE, these reports were now immediately loading and the prompt flow became very responsive.

Conclusion

Taking a little bit of extra time when writing out the logic for your queries can have a profound impact on the responsiveness of your queries.  In this case, simplifying the code but retaining the logic had a tremendous effect on overall performance.

In addition to what can be done in SQL Server, sometimes one needs to look outside of SQL to the network or disk layer for further tuning.  Working with the appropriate teams to accomplish those goals can go miles to having a positive effect on co-workers and application experience by the business users.

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.

Hustle and Bustle to Kick off September

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

September is kicking off with a boom for me.  In one week, it seems I will be here, there and everywhere.  Between SQL, PASS, Family, and User Group – this is shaping up to be quite the week.

Here’s a quick run down of this weeks agenda.

Cross Country

My oldest son is running varsity cross country.  This week there are two meets.  One is in-state and the other is in Idaho.  We are really looking forward to these meets.  He is enjoying the sport, team, atmosphere and success he is having.  The first meet is Wednesday and the second is this weekend.

Soccer

We also have a son starting soccer practice this week.  His first match is this weekend.  He has played before and really enjoys playing soccer.  Mostly, I think he likes kicking the ball hard.

Softball

I sponsor a co-ed softball team.  We just wrapped up the summer season finishing second in the season ending tournament and first in the regular season.  Fall season starts Wednesday and this should be a lot of fun.

Volleyball

My wife captains a co-ed volleyball team.  The league was created as a fundraiser for the high school volleyball team.  She has three matches on Wednesday.  This keeps her pretty busy while I am busy with the kids.  The league is about to wrap it up – so the schedule gets a little less congested after this.

PASS

24 Hours of PASS starts Wednesday.  I am scheduled to moderate a session for Allan Hirt that begins at 19H00 GMT.  I invite you to attend this session.  It should be a good session and well worth your time.  Now where is that record button?  Here is more info on that particular session.

You can find more about the rest of 24HOP here.  The schedule line up is pretty good.

S3OLV

Thursday evening (yes 24HOP is on Thursday too) we will be having our regularly scheduled monthly meeting for the Las Vegas SQL Users Group.  You can read more about that here.  This meeting is open to all wishing to get some more learning out of the day than what is available through the means of 24 Hours of PASS.

SQLSaturday 94

SQL Saturday 94 is being held this Saturday, September 10.  You can read more about what I have already blogged about it – here.  As you can see from the above entries, I already have conflicts on this day.  I am hoping that the timing works out to be available to all three things.

 

Precision and Scale

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

PrecisionAs is the case with many of my topics of late, I came across this one by helping somebody else.  In SQL, we should be well aware of Precision and Scale of certain datatypes.

The particular case I was working on was focused on the decimal datatype, and so we will work with that throughout this post explicitly.

What are these attributes?

According to MSDN, these attributes have the following definitions.

Precision – specifies the number of digits an object can hold

Scale – specifies the number of digits to the right of the decimal point that an object can hold.

Based on those definitions, it seems pretty straight forward, right?  Well, it is until you start doing a bit of math.  Microsoft has formulas for figuring out what the resultant precision and scale will be for various math operations.  You can read about that here.

Throughout our example, we will be focusing on multiplication and division.  We will demonstrate a few different results and configurations as well.

First, let’s get some formulas out of the way.  The formulas for precision and scale, as they show in MSDN at the link above, are as follows:

Multiplication
	Precision
	p1 + p2 + 1
	Scale
	s1 + s2
Division
	Precision
	p1 - s1 + s2 + max(6, s1 + p2 + 1)
	Scale
	max(6, s1 + p2 + 1)

As is described in the MSDN article, p represents precision and s represents scale.  The number annotations with p and s represent the corresponding expressions in the mathematical operation.  The equation that we will be trying to solve is as follows:

SELECT ROUND(((@numerator*@multiplier)/@divisor1)*@anothermultiplier,2)

ScaleBut for the majority of these exercises, we will be focusing on this part of the formula.

SELECT ((@numerator*@multiplier)/@divisor1)

This will provide us with ample example of the math involved when calculating the resultant precision and scale of a SQL math operation.

Here is an example of the above query with values.  This query results in a value that is consistent with such calculators as MS Excel ( ;0) ).

SELECT ((5000000000.00*2.0250000000)/111883775187.72)

However, if we use variables in lieu of those values, we start to see different results.  And thank goodness for that, because there wouldn’t be much to talk about otherwise.  So, let’s dump those values into some variables and see what starts happening.

DECLARE @divisor1	DECIMAL(18,6) = 111883775187.72
		,@numerator	DECIMAL(18,2) = 5000000000.00
		,@multiplier	DECIMAL(10,3) = 2.0250000000
		,@multiplier1	DECIMAL(18,3) = 2.0250000000
		,@stage1		DECIMAL(18,2)
		,@anothermultiplier	DECIMAL(18,2) = 932364.79

And the formula(s).  I say formulas, because I will be demonstrating two results here.  Notice quickly that I have two similar multiplier variables – they differ only in name and precision.

SELECT ((@numerator*@multiplier1)/@divisor1)
SELECT ((@numerator*@multiplier)/@divisor1)

If you execute those two queries, you should get very similar results.  Both should return 0.090xxx, but the second has more scale, extending the decimal out 8 places rather than 6 places.  For the second query our result is 0.09049569.  When you combine this difference at this point, it could make for some accounting nightmares.  Especially given this difference in result occurs early on in the equation.

Notice in my variables there is one called stage.  Let’s use that one now and see how using a staging variable plays into this.

DECLARE @divisor1	DECIMAL(18,6) = 111883775187.72
		,@numerator	DECIMAL(18,2) = 5000000000.00
		,@multiplier	DECIMAL(10,3) = 2.0250000000
		,@multiplier1	DECIMAL(18,3) = 2.0250000000
		,@stage1		DECIMAL(18,2)
		,@anothermultiplier	DECIMAL(18,2) = 932364.79
 
SELECT @stage1 = @numerator*@multiplier
SELECT @stage1/@divisor1
SELECT @stage1 = @numerator*@multiplier1
SELECT @stage1/@divisor1

Do you see what just happened?  Both multipliers now produce the same result.  How could that be?  Let’s look at that.  This time, let’s post calculations for precision and scale along-side each of those queries.

SELECT @stage1 = @numerator*@multiplier		--P = 18+10+1 = 29, S = 2+3 = 5
SELECT @stage1/@divisor1					--P = 18-2+6+max(6, 2+18+1) = 43, S = max(6, 2+18+1) = 21 --Actual P = 38 S = 16
SELECT @stage1 = @numerator*@multiplier1	--P = 18+18+1 = 37, S = 2+3 = 5
SELECT @stage1/@divisor1					--P = 18-2+6+max(6, 2+18+1) = 43, S = max(6, 2+18+1) = 21 --Actual P = 38 S = 16

Looking this over, you should be able to quickly pick out some anomalies.  Let’s start with the anomalies present in the calculations for the second query.  First, you can see that the value for p1 is 18.  One might fairly think that it should be the resultant precision of the first query.  But, the variable is created as Decimal(18,2) and that precision and scale is used in calculations involving that variable.

The second thing one should notice is that the resultant precision is 43.  Then why did I change it to 38 at the end?  Max precision is 38.  If the resultant precision of a mathematical operation exceeds 38, then it must be reduced to 38.  This has an impact on scale – which is the next item of note.  In the aforementioned MSDN article, scale is simply reduced by the difference between resultant(p) and final(p).  That simple calculation holds true for these particular queries.  But, if we look at the following queries, we can clearly see that it is behaving differently.

DECLARE @divisor1	DECIMAL(18,6) = 111883775187.72
		,@numerator	DECIMAL(18,2) = 5000000000.00
		,@multiplier	DECIMAL(10,3) = 2.0250000000
		,@multiplier1	DECIMAL(18,3) = 2.0250000000
		,@stage1		DECIMAL(18,2)
		,@anothermultiplier	DECIMAL(18,2) = 932364.79
 
SELECT (@numerator*@multiplier1)/@divisor1
SELECT (@numerator*@multiplier)/@divisor1

And the correlating notes regarding precision and scale calculations.

SELECT (@numerator*@multiplier1)/@divisor1	--P = 37-5+6+max(6, 5+18+1) = 62, S = max(6, 5+18+1) = 24 --Actual P = 38 S = 6
SELECT (@numerator*@multiplier)/@divisor1	--P = 29-5+6+max(6, 5+18+1) = 54, S = max(6, 5+18+1) = 24 --Actual P = 38 S = 8

Look at the final(s) for that first query.  Scale is actually 6, but that does not match the math.  Resultant(p) = 62, Final(p) = 38 and that means the difference is 24.  Resultant(s) is 24, from which I subtract 24 and should get 0.  Well, there is a part of that formula that needs better explanation maybe in the documentation.  The final(s) should actually include the max(6, Resultant(s) – (resultant(p) – final(p))).  The final(s) cannot be less than 6, and thus the reason that we see 6 digits to the right of the decimal in the result of that first query.

Now let’s change that divisor scale up a bit.  The requirements dictate that the divisor be a Decimal(18,2) – I used 18,6 as one of my test sets.  In this case, the only thing that changes is the final(s).  And in this particular case (though, I don’t recommend shortcutting – it just works for this case), we can simply add 4 to the final(s) of the second query.  The first remains unchanged.

Let’s look at the resulting value now.  This difference alone is cause enough for significant differences in the results of the larger formula.

First query = 0.090495, Second query = 0.090495695046.  To this point, I have shown why this happens.  The calculations performed exceed the limitations for precision which impacts scale – which affects accuracy of the formula.

I showed one method, without saying as much, on how to avoid this.  My use of an intermediary step to perform these calculations via variable helped to correct the precision/scale/accuracy problem.  Another viable option is to use appropriate precision and scale for the data being used.  Changing precision and scale to match expected data can have a significant impact on the resultant accuracy of the calculation.

I used two multipliers to demonstrate that last suggestion.  The more accurate result came from the second query which used a more appropriate precision and scale for the data (see the variable @multiplier).

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.

TSQL Sudoku

Comments: 8 Comments
Published on: August 17, 2011

I am a big Sudoku fan.  Typically if I need a break, I will break out a Sudoku puzzle from any of a number of different sources (Websudoku, Android Apps, Puzzle Books).  Over time, I have come across a solution here or there to solve these puzzles via TSQL.

There are a few of these solutions out there already, such as one by Itzik Ben-Gan (which I can’t get to download without the file corrupting so I still haven’t seen it), or this one on SSC (which works most of the time but does provide inaccurate results from time to time).  I still wanted something to do this via CTE (much like the solution by Itzik is described to be at the link provided – if you have that code, I want to SEE it).

Just a couple of years ago, there was a post at SSC asking for some help converting a solution from Oracle to TSQL.  I checked out that code and worked on it for a day or two.  Then I got busy with other work that replaced the pet project.  I hadn’t given the idea much thought until just a few days ago as I was browsing my Topic list I had been building for articles.

This solution stuck with me this time around and I wanted to finish it up.  The Oracle solution for whatever reason made a lot more sense to me this time around, and I made great progress quickly.  It was actually this project that I was working on that prompted another post.  While working through the solution, I learned a fair amount about both flavors of SQL.  So, in preface to continuing to read here, you may want to check out the other article real quick since it pertains to some of the conversions done in this project.

Problems First

The OP supplied the Oracle solution asking for help in creating a TSQL Solution.  Here is that Oracle version.

WITH x( s, ind ) AS
( SELECT sud, instr( sud, ' ' )
  FROM ( SELECT '53  7    6  195    98    6 8   6   34  8 3  17   2   6 6    28    419  5    8  79' sud FROM dual )
  UNION ALL
  SELECT substr( s, 1, ind - 1 ) || z || substr( s, ind + 1 )
       , instr( s, ' ', ind + 1 )
  FROM x
     , ( SELECT to_char( rownum ) z
         FROM dual
         connect BY rownum <= 9
       ) z
  WHERE ind > 0
  AND NOT EXISTS ( SELECT NULL
                   FROM ( SELECT rownum lp
                          FROM dual
                          connect BY rownum <= 9
                        )
                   WHERE z = substr( s, trunc( ( ind - 1 ) / 9 ) * 9 + lp, 1 )
                   OR    z = substr( s, mod( ind - 1, 9 ) - 8 + lp * 9, 1 )
                   OR    z = substr( s, mod( trunc( ( ind - 1 ) / 3 ), 3 ) * 3
                                      + trunc( ( ind - 1 ) / 27 ) * 27 + lp
                                      + trunc( ( lp - 1 ) / 3 ) * 6
                                   , 1 )
                 )
)
SELECT s
FROM x
WHERE ind = 0
/

If you read that other post I mentioned, you will quickly identify 5 functions/objects in use in this script that just don’t work in TSQL.  Those are:  dual, instr, substr, connect by, and trunc.  I did not mention mod in my other post, but mod is also done differently in TSQL than in Oracle.  I thought this one was a bit obvious and stuck with the top 5 ;) .

Solution

After figuring out some of the subtle differences between commands and the best way to approach this, I was able to come up with a TSQL solution that works.  Take not first of that last where clause in the CTE of the Oracle solution.  That clause is very similar to what I refer to as the train-stop method to get unique paths in a hierarchy.  There are several methods to do similar functionality – I have concatenated strings with Stuff as wells cast to produce this functionality.

So here goes with the first rendition of this query.

DECLARE @SudokuGivens VARCHAR(100)
SET @SudokuGivens = '53  7    6  195    98    6 8   6   34  8 3  17   2   6 6    28    419  5    8  79'
;
WITH
      E1(N) AS ( --=== Create Ten 1's
                 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 1 --10
               )
,dual(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT N)) FROM E1)
,x( s, ind ) AS
( SELECT CONVERT(VARCHAR(100),sud), CHARINDEX(' ',Ss.sud ) AS ind
	FROM
		( SELECT @SudokuGivens AS sud FROM dual ) Ss
  UNION all
  SELECT CONVERT(VARCHAR(100),SUBSTRING( s, 1, ind - 1 ) + z + SUBSTRING( s, ind + 1 ,LEN(s)))
       , CHARINDEX( ' ', s, ind + 1 ) AS ind
  FROM x
    CROSS APPLY ( SELECT CONVERT(VARCHAR(25), N ) z
         FROM dual
         WHERE N <= 9
       ) z
  WHERE ind > 0
  and not exists ( SELECT null
                   FROM ( SELECT N AS lp
                          FROM dual
                          WHERE N <= 9
                        ) ww
					WHERE z = SUBSTRING( s, ( ind - 1)% 9  - 8 + lp * 9, 1 )
						or    z = SUBSTRING( s, ( ( ind - 1 ) / 9 ) * 9 + lp, 1 )
						or    z = SUBSTRING( s, (( ( ind - 1 ) / 3 )%3) * 3
                                      + ( ( ind - 1 ) / 27 ) * 27 + lp
                                      + ( ( lp - 1 ) / 3 ) * 6
                                   , 1 )
                 )
)
SELECT DISTINCT s
	FROM x
	WHERE ind = 0

Notice that I have chosen to use an Itzik style numbers table/CTE.  This functions as my “dual” table translation and is necessary in the remainder of the query.  The final where clause of the CTE is simplified in TSQL by simply removing the TRUNC commands.  The original solution was merely removing the decimal precision.  In TSQL, the conversion is done to INT implicitly in this case.  I need to test a few more cases, but so far it works without error.

What this does not do…

This is the first rendition of the script.  Currently, it only returns the number sequence in one big long string.  I am working on modifying this script to produce a grid layout with the solution.  I envision this will require the use of PIVOT and possibly UNPIVOT to get me close.  In addition, I expect that further string manipulation will be needed – such as stuffing commas and then splitting it to make the PIVOT/UNPIVOT easier.  I’ll have to try some things and figure it out.  Also, I expect that some explicit conversions may be best in this query.  That could help improve performance a tad.

This, to this point, has been a fun diversion.  This has helped to learn a bit about Oracle, hierarchies, and to do a little math – all in one.  Better yet is that there is still work to be done on it and more learning.  If you have ideas how to make it better – I am very interested.

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.

Activity Monitor and Profiler

Tags: ,
Comments: 2 Comments
Published on: July 28, 2011

Today I came across a link to a neat little script for SQL 2005 / 2008 to help derive the head blocker in a blocking chain.  That script can be found here.  I was looking at the script and thought it looked pretty cool and also wondered why it might look a little familiar.  Not too big of a concern since many admin type scripts can look familiar.

Then I noticed that the title contained the words Activity Monitor.  I wondered to myself, was this taken from activity monitor?  So, I decided to play with Profiler for a bit to see if I could generate the same query.  The process really isn’t too difficult.  Here is what I did.

  1. Open Activity Monitor.  Simple enough to do.  Right click the instance (in SSMS) you wish to monitor, select Activity Monitor from the context menu.
  2. Open Profiler.  Again from within SSMS, click the tools menu and then select SQL Server Profiler.
  3. Start a New trace.  When starting the trace ensure that you are connecting to the same instance as you started Activity Monitor.  Select the TSQL-Replay template (other tsql will probably work, this is the one that I used).  Goto the events selection tab and click on the Column Filters button.  Select “LoginName” and then expand “Like” on the right hand side.  In the new text box, type the login you are using to authenticate to the SQL Instance in question (domain\user or SQLLogin).
  4. Click run and browse through the results for a bit.  It shouldn’t take too long for that query to pop up.
  5. Once you find the query, make sure you stop the trace.
What is the point in this?  Well, as explained in that other blog, sometimes it is just better to run a query than to run Activity Monitor.  Furthermore, you can take this method and trap many of the queries that Microsoft built into that tool.  You can either use them for learning or troubleshooting.  This is just one more tool to have in your arsenal in order to become better at your job.

July 2011 S3OLV Meeting

Tags: , ,
Comments: No Comments
Published on: July 6, 2011

We have had a bit of a lag between meetings for the User Group of Las Vegas.  In June, a meeting was scheduled but did not happen.  The cause of that was due to being locked out once again by our meeting place.  That should not happen this month – nor anytime in the near future.  The Venue has changed for our little group this month.

Starting with our July meeting, we will be holding our gatherings at M Staff Solutions &Training. They have been gracious enough to bring us on and they have provided us with a key.  Woot!!

Our topic for July will be the same as it was supposed to be for June.

For any who are interested, The S3OLV meeting will be held Thursday July 14 at 18h30 PDT.

This month Charley Jones will be test driving a new presentation.  He has been working feverishly to learn about cubes and how to create them.  He will be teaching us this month how to do this from the mind-set of an OLTP DBA in his presentation titled “OLTP Moving to OLAP.”  I think this will be a great opportunity for those of us who are weak or strong in OLAP to learn a bit and see how this transition may make sense from an OLTP perspective.

This month we will be conducting this meeting in person and also via livemeeting.  Here are the livemeeting details.

Attendee URL: https://www.livemeeting.com/cc/UserGroups/join?id=Z8DZWK&role=attend
Meeting ID: Z8DZWK

In other news, we have a newsletter that we are publishing thanks to the paper.li service.  You can find that newsletter here.  Beyond the distribution via twitter, we are looking to distribute weekly newsletters to the group.

«page 2 of 9»
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