Archive

Posts Tagged ‘t-sql’

Needing a change… Modify your Filename

February 3, 2011 Leave a comment

A friend of mine told me that he felt a need for a change. Without question I said cool. Change is always inviting as it keeps things interesting. Little did I know or realize he was referring to the changing of his database file name. So he issued the simple ALTER DATABASE MODIFY FILE statement and specified a new name for his database.

ALTER DATABASE AdventureWorks
MODIFY FILE (NAME = AdventureWorks_data, FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\DATA\AdventureWorks_data.mdf')
ALTER DATABASE AdventureWorks
MODIFY FILE (NAME = AdventureWorks_Log, FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\DATA\AdventureWorks_log.ldf')

and in the results were

The file "AdventureWorks_data" has been modified in the system catalog. The new path will be used the next time the database is started.
The file "AdventureWorks_Log" has been modified in the system catalog. The new path will be used the next time the database is started.

So he took this message as needing to restart the SQL Server Engine Service. Which he did; however, that did not satisfy the underlying issue. After refreshing the databases he attempted to expand the db and received a nice little error indicating that the database was not accessible. Which is when I received the call.

Having experienced this myself I remembered that:

1. the database needed to be taken offline

ALTER DATABASE AdventureWorks SET OFFLINE WITH ROLLBACK IMMEDIATE

2. he would need to physically rename the files accordingly

3. bring the database back online

ALTER DATABASE AdventureWorks SET ONLINE WITH ROLLBACK IMMEDIATE

Once these steps were completed he was back in business. On a side note this permitted the changing of the file names but you can also change the actual file path location for each of the files as well using the same syntax. If you wanted to update the logical name for both the data and log file below is the syntax for doing so.

ALTER DATABASE [AdventureWorks] MODIFY FILE (NAME=N'AdWks', NEWNAME=N'AdventureWorks_data')
ALTER DATABASE [AdventureWorks] MODIFY FILE (NAME=N'AdWks_log', NEWNAME=N'AdventureWorks_log')

Get row count from all tables

January 23, 2011 5 comments

I was reviewing some of my older scripts and I came across one in particular that stood out. I wrote it quite a while ago and looking back does indicate where I came from in terms of logic. To give a little background my script was written to grab the row counts from all tables within the given database. The problem… it created a temp table then loaded the temp table from querying the INFORMATION_SCHEMA.TABLES. Then it looped through each of the table names loading them it into a variable and then passed them to an update statement which updated the temp table. As you can see this was a very inefficient way to go about it.

I was reading up on catalog views and it seemed that I could query sys.sysobjects and join sys.sysindexes. So I figured I would get this verified. I posed the question and following are the responses I received.

Yep. sys.sysindexes is depreciated in Denali. Information here: msdn.microsoft.com

So here is my script. I originally left out line 15 and when I did I saw a lot of duplication in the results set. So I surfed around and found a post titled, “Display Row count for all tables” by Shyam Skj that used the indid field from sysindexes to filter the records. The closes thing I found was the index_id from sys.partitions and lo and behold using similar syntax the results returned nicely.

/* Check if object already exists */
IF OBJECT_ID('usp_GetTableRowCounts') IS NOT NULL
/* If available drop object */
BEGIN
DROP PROCEDURE usp_GetTableRowCounts
END
GO
/* Create procedure */
CREATE PROCEDURE usp_GetTableRowCounts
AS
SELECT o.name 'TableName', p.rows 'Rows'
FROM sys.objects o
JOIN sys.partitions p ON o.object_id = p.object_id
WHERE (o.type = 'U')
AND (p.index_id IN (0,1))
ORDER BY p.rows DESC

AdventureWorks Results

T-SQL Tuesday: Lucky 7-Resolutions

January 10, 2011 1 comment

Official TSQL2sday Logo

TSQL2sday

Every year I make a resolution list just like anyone else and often I am able to achieve them while other items are pushed aside for reasons beyond my control. This year however is slightly different now that I have completed my Bachelors of Science degree in Computer Information Systems with a focus on Database Management. This alone makes some of my resolutions much more attainable. With the added time that I have I can finally focus on certification studies among other things that pertain to personal and professional development. If I am able to juggle my time wisely I can achieve the ultimate ying-yang of personal time management.

So to get down to business here is my list of things I want to accomplish in 2011:

1. Health

Without no surprise in order to achieve my goals I need to be healthy. I dropped about 30 lbs during 2010 and have about 20 lbs to go to reach my ideal weight, which is 190. So before 2011 comes to a close I should be at or around 190 lbs. Chances are I will meet that goal before mid year. I am pretty confident it will be definitely before mid year.

2. Certifications

I currently hold three certifications, but it’s time to buckle down and concentrate on SQL Server centric certifications. Before the end of the first quarter I want to complete the MCITP Database Administrator certification for SQL Server 2008. Which means I started studying for exam 70-432 the beginning of this year. And in order to make things more realistic I am going to schedule for the test by this Friday (to be taken in the near future) which will add the much-needed pressure thus making accountability that more important.

3. ETL Stizzuf

There is just something about Business Intelligence that tickles my fancy. It’s hard to describe but I love reports, especially those I don’t have to hand write. 2011 will be the year I become a BI bad ass so-to-speak. To paint a picture I want to strive to become the west coast version of Brian Knight but in a smaller scale all while wearing Adidas kicks or sandals with socks on because I feel strange without socks… all other attire is optional. At one point or another within the year I want to actually spend a night in a warehouse while developing a data warehouse. I think that would be peachy! Icing on the cake if you will.

4. Get more involved

I touched the waters last year and started to participate where I could. To my surprise I found that people are actually willing to lend a helping hand and share the wealth of knowledge with one another. When I was handed the keys to manage the organization’s data I really didn’t know where to begin or who to consult in the event I ran into a brick wall. I picked up some books, started browsing blogs and joined 26 social networking sites. Then after learning more about social networking I found that twitter and facebook seem to have the highest concentration of SQL Socialites. I can’t say they accepted me, but I can say they have made an impact on my life professionally and personally. I won’t get into details but know that I am grateful.

5. MVP or runner up

This might be a long shot given the pool of MVPs out there. So the stakes are high and quite frankly I have no idea how to toss my hat into the ring. This is something I have only read about lightly and never really asked someone how they achieved MVP status. But nonetheless I need goals to keep me aiming high and moving in the right direction.

6. Organize a SQLSaturday

This might be cheating along with a bit of self promotion since we are slated to launch #sqlsat47 this February 19th, 2011 at Chandler-Gilbert Community College. This ties into list (Item 4) about getting more involved. I am a newbie in terms of event coordinating but I plan all with my co-organizer @coneybeer to deliver a successful SQLSaturday. There is still time to register for the event… I hope to see you there!!!!

7. Participate in the TSQL2sDay blog party

I have seen many of these pop up last year and I find myself very intrigued. I did some research about #TSQL2sDay (which was founded by Adam Machanic) and anyone can become a host provided they participate in at minimum two #TSQL2sDay events which are held monthly, keep an active blog followed up with an email top Adam Machanic. So with the list I have this year and the participation that I plan on doing I should be ready come next year to host a #TSQL2sDay event. Check out the

Format Seconds into HH:MM:SS

August 30, 2010 Leave a comment

Without having to use replication monitor I decided to put together a stored procedure that will return the same results. In the MSmerge_sessions table of the Distribution database the field duration is returned in raw seconds. Meaning that if the process took 2 mins and 2 secs it displayed as 122. Well I suppose I could live with it but I am hard-headed and wanted the outcome to look similarly formatted HH:MM:SS. Without really getting into the specifics on joining tables I used the MSmerge_Agents a and MSmerge_Sessions s joining on s.agent_id to a.id and added a.subscriber_name to my query to display the subscriber name but that is beside the point of this post.

Calculating Hours

The first thing you should know is how to calculate seconds into hours and minutes. Below outlines how to do each, but before we jump into that let’s try it manually.

There are 60 seconds in one minute and there are 60 minutes in one hour. So it is 60*60 which equals 3600. 3600 is the amount of seconds in one hour.

Now let’s take 3723 seconds and divide it by 3600.

What do we get?
3723/3600 = 1.03416 just a tad more than one hour

If you run the following in Management Studio you will notice that one is returned. You will not get (.03416) trailing the one. So calculating hours is easy since it returns whole numbers.

SELECT (3723/3600)

Calculating Minutes

In order to calculate minutes you will need to perform a calculation similar to the one above with the exception of asking for the remainder using the modulo operator. Which is stated as: (3723 % 3600). However this alone will not return the results you are looking for. You need to take it a step further and divide it by 60.

Now we have:
((3723 % 3600) / 60) = 2 minutes

If you run the following in Management Studio you will get the same result as I stated above.

SELECT ((3723 % 3600) / 60)

Calculating Seconds

Alright we are almost there. Now we need to get the seconds after everything is all said and done. This is another easy one. We take 3723 and get the remainder by using 3723 % 60.

SELECT (3723 % 60)

Run it together

Here is a quick and non-formatted way to see the results.

DECLARE @secs INT
SET @secs = 3723

SELECT (@secs / 3600)		'Hrs'
SELECT ((@secs % 3600)/60)	'Mins'
SELECT (@secs % 60)			'Secs'

Stop! Format Time!

The purpose of this post was to format this field appropriately and here is what I did.

DECLARE @secs INT
SET @secs = 3723

SELECT	
CONVERT(VARCHAR(10),CONVERT(datetime,CAST((@secs / 3600) AS VARCHAR) 
+ ':' + CAST(((@secs % 3600)/60) AS VARCHAR) 
+ ':' + CAST((@secs % 60) AS VARCHAR)),108)

Final Results Set

Pad then Add Leading Zeros

August 25, 2010 Leave a comment

In the event you need to add leading zeros to an integer, the process is pretty straightforward. I am using the AdventureWorks database and SQL Server 2008 R2. For this example I will use the Sales.SalesPerson table for their SalesPersonID field which contains 3 digit IDs. First let’s take a look at the data. As you can see the data looks fine so let’s get started.

SELECT SalesPersonID
FROM [AdventureWorks].[Sales].[SalesPerson]

Padding

The first thing we need to do is to pad the field with leading spaces using the STR function and specify 6 spaces.

SELECT STR(SalesPersonID, 6) 'SalesPersonID'
FROM [AdventureWorks].[Sales].[SalesPerson]

Replacing

Now we just need to replace those spaces with zeros using the REPLACE function

SELECT REPLACE(STR(SalesPersonID, 6),SPACE(1),'0') 'SalesPersonID'
FROM [AdventureWorks].[Sales].[SalesPerson]

That’s it… nothing to it.

Select Multiple Rows Into Multiple Columns

July 8, 2010 1 comment

Clearly I am having a sql brain fart but for some reason I cannot think of a better way to select data from a column and have it return in a single row. To better illustrate what I am looking to accomplish I will reference the sys.messages system view.

Here is an example dataset that I will be working with.

select * from sys.messages where (message_id = 204)

This is the route I came up with from the top of my head; however, something tells me I could do this differently but I cannot think of anything else.

DECLARE @MsgID INT
SET @MsgID = 204

SELECT m1.message_id 
			,m1.language_id
			,m1.severity
			,m1.text
			,m2.text
			,m3.text
			,m4.text
			,m5.text
			,m6.text
			,m7.text
			,m8.text
			,m9.text
			,m10.text
			,m11.text
FROM	sys.messages m1
			,sys.messages m2
			,sys.messages m3
			,sys.messages m4
			,sys.messages m5
			,sys.messages m6
			,sys.messages m7
			,sys.messages m8
			,sys.messages m9
			,sys.messages m10
			,sys.messages m11			
WHERE (m1.message_id = @MsgID and m1.language_id = 1033)
AND (m2.message_id = @MsgID and m2.language_id = 1031)
AND (m3.message_id = @MsgID and m3.language_id = 1036)
AND (m4.message_id = @MsgID and m4.language_id = 1041)
AND (m5.message_id = @MsgID and m5.language_id = 3082)
AND (m6.message_id = @MsgID and m6.language_id = 1040)
AND (m7.message_id = @MsgID and m7.language_id = 1049)
AND (m8.message_id = @MsgID and m8.language_id = 1046)
AND (m9.message_id = @MsgID and m9.language_id = 1028)
AND (m10.message_id = @MsgID and m10.language_id = 1042)
AND (m11.message_id = @MsgID and m11.language_id = 2052)

This is the desired results

If you know of a better method please elaborate, I beg you!

Concatenation Fields

June 1, 2010 2 comments

In some instances you are required to piece together data by slapping fields together. This is probably most common with names, addresses and such. Using the AdventureWorksLT database I will illustrate how to concatenate a few fields to comprise a single full name field. First we need to analyze the data to see what we have.

SELECT FirstName
       ,MiddleName
       ,LastName
       ,Suffix
FROM [AdventureWorksLT].[SalesLT].[Customer]

Results
01.SampleData

As you can see we have rows that contain only a first and last name, some that have a middle initial and some have a suffix. Unfortunately there was not a record that had a first, last and suffix so I modified record ID 12 because I wanted to touch base on all scenarios. I find it helpful.

Based on the data we don’t need to worry about the first and last name fields but we do need to be concerned with the middle initial and suffix since some are populated while others are not. So how are we going to handle this? Well there are a few ways but I will show you how to address this with SELECT CASE.

SELECT Firstname + space(1) +
       CASE
          WHEN MiddleName IS NULL THEN LastName
          ELSE MiddleName + space(1) + LastName
       END +
       CASE
          WHEN Suffix IS NOT NULL THEN space(1) + Suffix
          ELSE space(0)
       END 'FullName'
FROM [AdventureWorksLT].[SalesLT].[Customer]

Well let’s review before we move on to the results… line numbers 3 & 4 basically state that when you find a NULL value for the MiddleName just display the LastName instead otherwise display the MiddleName add a space then display the LastName.

Lines 7 & 8 is similar. When the Suffix field contains a value other than NULL add a space and display the Suffix otherwise show nothing.

Results
02.FieldsConcat

Now let’s put everything together to see how it all looks.

SELECT FirstName
       ,[MiddleName]
       ,[LastName]
       ,[Suffix]
       ,FirstName + space(1) +
       CASE
          WHEN MiddleName IS NULL THEN LastName
          ELSE MiddleName + space(1) + LastName
       END +
       CASE
          WHEN Suffix IS NOT NULL THEN space(1) + Suffix
          ELSE space(0)
       END 'FullName'
FROM [AdventureWorksLT].[SalesLT].[Customer]

Note:

The fact this dataset has explicit NULL values as opposed to blank/empty values for the MiddleName and Suffix columns made this easy. If it were the latter of the two the results would differ and require additional measures.

Results
03.ConcatFields

I modified CustomerID 20 by replacing NULL in the MiddleName field with a single space and CustomerID 22 by replacing NULL in the Suffix with a single space. Of course the MiddleName is more apparent as it spaces out the FirstName and LastName more than it should and the Suffix is less obvious but it does add a space after the LastName.

Results
04.NonNULLValues

I was able to address these by adding a two additional WHEN statements to the existing CASE statements.

SELECT FirstName
       ,[MiddleName]
       ,[LastName]
       ,[Suffix]
       ,FirstName + space(1) +
       CASE
          WHEN MiddleName IS NULL THEN LastName
          WHEN MiddleName = space(1) THEN LastName
          ELSE MiddleName + space(1) + LastName
       END +
       CASE
          WHEN Suffix = space(1) THEN space(0)
          WHEN Suffix IS NOT NULL THEN space(1) + Suffix
          ELSE ''
       END 'FullName'
FROM [AdventureWorksLT].[SalesLT].[Customer]

Results
05.NonNULLFixed

Another issue:

What if there were leading or trailing spaces in the FirstName and LastName fields? I modified Record ID 1 and added a 10 space before and after Orlando.

Results
06.Whitespace-FirstnameColumn

Well in this case I would keep the same syntax but would encase the fields within: RTRIM(LTRIM(FirstName)) respectively as it removes the whitespaces before and after.

SELECT FirstName
       ,[MiddleName]
       ,[LastName]
       ,[Suffix]
       ,LTRIM(RTRIM(FirstName)) + space(1) +
       CASE
          WHEN MiddleName IS NULL THEN LastName
          WHEN MiddleName = space(1) THEN LastName
          ELSE MiddleName + space(1) + LastName
       END +
       CASE
          WHEN Suffix = space(1) THEN space(0)
          WHEN Suffix IS NOT NULL THEN space(1) + Suffix
          ELSE ''
       END 'FullName'
FROM [AdventureWorksLT].[SalesLT].[Customer]

Results
07.Trimmed-Firstname

Formating Date and Time

May 29, 2010 1 comment

We synchronize GIS data differentially using a proprietary method. As part of the process a date-time parameter is passed and the system updates the GIS accordingly given the time stamp value. Since this is a differential sync only the changes that occurred within the range are applied to the subscriber. A specific format is required but none of the following formats in the example below meet the criteria exactly.

Format 101 addresses the needed date style (MM/DD/YYYY) but there really is not a time format that fits my exact needs. This is where I improvise a bit. Since I pull values from a table that have a “datetime” data type I will break the date and time into two pieces and address each as needed. In this case I only need to worry about the time.

SELECT CONVERT(varchar, GETDATE(), 100) 'Mon DD YYYY hh:miAM'
	  ,CONVERT(varchar, GETDATE(), 101) 'MM/DD/YYYY'
	  ,CONVERT(varchar, GETDATE(), 102) 'YYYY.MM.DD'
	  ,CONVERT(varchar, GETDATE(), 103) 'DD/MM/YYYY'
	  ,CONVERT(varchar, GETDATE(), 104) 'DD.MM.YYYY'
	  ,CONVERT(varchar, GETDATE(), 105) 'DD-MM-YYYY'
	  ,CONVERT(varchar, GETDATE(), 106) 'DD Mon YYYY'
	  ,CONVERT(varchar, GETDATE(), 107) 'Mon DD, YYYY'
	  ,CONVERT(varchar, GETDATE(), 108) 'hh:mi:ss' -- 24HR
	  ,CONVERT(varchar, GETDATE(), 109) 'Mon DD YYYY hh:mi:ss:mmmAM'
	  ,CONVERT(varchar, GETDATE(), 110) 'MM-DD-YYYY'
	  ,CONVERT(varchar, GETDATE(), 111) 'YYYY/MM/DD'
	  ,CONVERT(varchar, GETDATE(), 112) 'YYYYMMDD'
	  ,CONVERT(varchar, GETDATE(), 113) 'DD Mon YYYY hh:mi:ss:mmm' --24HR
	  ,CONVERT(varchar, GETDATE(), 114) 'hh:mi:ss.mmm' -- 24HR
	  ,CONVERT(varchar, GETDATE(), 120) 'YYYY-MM-DD hh:mi:ss' --24HR
	  ,CONVERT(varchar, GETDATE(), 121) 'YYYY-MM-DD hh:mi:ss.mmm' --24HR
	  ,CONVERT(varchar, GETDATE(), 126) 'YYYY-MM-DDThh:mi:ss.mmm'
	  ,CONVERT(varchar, GETDATE(), 127) 'YYYY-MM-DDThh:mi:ss.mmmZ'
	  ,CONVERT(varchar, GETDATE(), 130) 'DD Mon YYYY hh:mi:ss:mmmAM'
	  ,CONVERT(varchar, GETDATE(), 131) 'DD/MM/YY hh:mi:ss:mmmAM'

At first I spotted format 108 which provided hh:mi:ss but it lacked the AM/PM designation but appending it would be simple. So here is what I did.

SELECT [ModifiedDate]
       ,CONVERT(varchar,ModifiedDate,101) 'Date Only'
       ,CONVERT(varchar,ModifiedDate,108) 'Time Only'
       ,CONVERT(varchar,ModifiedDate,101) + ' ' +
       CASE 
	     WHEN CONVERT(varchar(2),ModifiedDate,108) > 12
	     THEN CONVERT(varchar,ModifiedDate,108)+ ' PM'
	     WHEN CONVERT(varchar(2),ModifiedDate,108) < 12
	     THEN CONVERT(varchar,ModifiedDate,108)+ ' AM'					
       END AS 'New ModifiedDate'
FROM [AdventureWorks].[Person].[Address]
WHERE [AddressID] IN (34,35)

Note: I had to update AddressID 35 for illustration purposes.

Outcome
FirstAttempt

The end results are close but not exactly there yet. The parameter only accepts time in a 12 hour format not 24. So I turned to my old scripts to see if I have done this before and I hadn’t but I did use DATEPART in one of them. After jumping into BOL and reading up on DATEPART I found that I could break apart the time into smaller segments (hours, minutes, seconds, etc…) and format them individually. It would require more syntax but the end results would be exactly what I needed.

SELECT [ModifiedDate]
       ,CONVERT(varchar,ModifiedDate,101) 'Date Only'
       ,CONVERT(varchar,ModifiedDate,108) 'Time Only'
       ,CONVERT(varchar,ModifiedDate,101) + ' ' +
       CASE 
	     WHEN CONVERT(varchar(2),ModifiedDate,108) > 12
	     THEN CONVERT(varchar,ModifiedDate,108)+ ' PM'
	     WHEN CONVERT(varchar(2),ModifiedDate,108) < 12
	     THEN CONVERT(varchar,ModifiedDate,108)+ ' AM'					
       END AS 'New ModifiedDate'
	   ,DATEPART(HH,ModifiedDate) 'HH'
	   ,DATEPART(MI,ModifiedDate) 'MI'
	   ,DATEPART(SS,ModifiedDate) 'SS'
FROM [AdventureWorks].[Person].[Address]
WHERE [AddressID] IN (34,35)

Outcome
Attempt2

Now the problem is that the MI column only displays a single digit when the value is less than 10. This would be the same behavior for the HH and SS columns. So I modified AddressID 36 to illustrate this as well.

Outcome
Attempt3

This means I need to do two things: 1) change the time to return in a 12 hr format and 2) pad the HH, MI & SS columns with a leading zero. I’ll tackle item 2) first by adding a space left of number…

SELECT [ModifiedDate]
       ,CONVERT(varchar,ModifiedDate,101) 'Date Only'
       ,CONVERT(varchar,ModifiedDate,108) 'Time Only'
       ,CONVERT(varchar,ModifiedDate,101) + ' ' +
       CASE 
	     WHEN CONVERT(varchar(2),ModifiedDate,108) > 12
	     THEN CONVERT(varchar,ModifiedDate,108)+ ' PM'
	     WHEN CONVERT(varchar(2),ModifiedDate,108) < 12
	     THEN CONVERT(varchar,ModifiedDate,108)+ ' AM'					
       END AS 'New ModifiedDate'
	   ,str(DATEPART(HH,ModifiedDate),2) 'HH'
	   ,str(DATEPART(MI,ModifiedDate),2) 'MI'
	   ,str(DATEPART(SS,ModifiedDate),2) 'SS'
FROM [AdventureWorks].[Person].[Address]
WHERE [AddressID] IN (34,35,36)

Outcome
Attempt4

Now I need to replace the space with a zero…

SELECT [ModifiedDate]
       ,CONVERT(varchar,ModifiedDate,101) 'Date Only'
       ,CONVERT(varchar,ModifiedDate,108) 'Time Only'
       ,CONVERT(varchar,ModifiedDate,101) + ' ' +
       CASE 
	     WHEN CONVERT(varchar(2),ModifiedDate,108) > 12
	     THEN CONVERT(varchar,ModifiedDate,108)+ ' PM'
	     WHEN CONVERT(varchar(2),ModifiedDate,108) < 12
	     THEN CONVERT(varchar,ModifiedDate,108)+ ' AM'					
       END AS 'New ModifiedDate'
	   ,REPLACE(str(DATEPART(HH,ModifiedDate),2),SPACE(1),0) 'HH'
	   ,REPLACE(str(DATEPART(MI,ModifiedDate),2),SPACE(1),0) 'MI'
	   ,REPLACE(str(DATEPART(SS,ModifiedDate),2),SPACE(1),0) 'SS'
FROM [AdventureWorks].[Person].[Address]
WHERE [AddressID] IN (34,35,36)

Outcome
Attempt5

Now we are looking better, but I still need to convert the 24 hr time to 12 hr time. So I’ll take the HH column and subtract 12 from it for any value greater than 12. If it is less than 12 then display as normal.

SELECT [ModifiedDate]
       ,CONVERT(varchar,ModifiedDate,101) 'Date Only'
       ,CONVERT(varchar,ModifiedDate,108) 'Time Only'
       ,CONVERT(varchar,ModifiedDate,101) + ' ' +
       CASE 
	     WHEN CONVERT(varchar(2),ModifiedDate,108) > 12
	     THEN CONVERT(varchar,ModifiedDate,108)+ ' PM'
	     WHEN CONVERT(varchar(2),ModifiedDate,108) < 12
	     THEN CONVERT(varchar,ModifiedDate,108)+ ' AM'					
       END AS 'New ModifiedDate'
	   ,CASE 
	      WHEN (DATEPART(HH,ModifiedDate) > 12)
	      THEN REPLACE(str(DATEPART(HH,ModifiedDate)-12,2),SPACE(1),0)
	      ELSE REPLACE(str(DATEPART(HH,ModifiedDate),2),SPACE(1),0)
	   END AS 'HH'
	   ,REPLACE(str(DATEPART(MI,ModifiedDate),2),SPACE(1),0) 'MI'
	   ,REPLACE(str(DATEPART(SS,ModifiedDate),2),SPACE(1),0) 'SS'
FROM [AdventureWorks].[Person].[Address]
WHERE [AddressID] IN (34,35,36)

Outcome
Attempt6

Zinga! Now the only thing left to do is to concatenate the Date Only field with the HH, MI and SS fields and append the AM/PM designation.

SELECT [ModifiedDate]
       ,CONVERT(varchar,ModifiedDate,108) 'Time Only'
       ,CONVERT(varchar,ModifiedDate,101) + ' ' +
       CASE 
	     WHEN CONVERT(varchar(2),ModifiedDate,108) > 12
	     THEN CONVERT(varchar,ModifiedDate,108)+ ' PM'
	     WHEN CONVERT(varchar(2),ModifiedDate,108) < 12
	     THEN CONVERT(varchar,ModifiedDate,108)+ ' AM'					
       END AS 'New ModifiedDate'
       ,CONVERT(varchar,ModifiedDate,101) + ' ' +
	   CASE 
	      WHEN (DATEPART(HH,ModifiedDate) > 12)
	      THEN REPLACE(str(DATEPART(HH,ModifiedDate)-12,2),SPACE(1),0)
	      ELSE REPLACE(str(DATEPART(HH,ModifiedDate),2),SPACE(1),0)
	   END + ':' +
	   REPLACE(str(DATEPART(MI,ModifiedDate),2),SPACE(1),0) + ':' +
	   REPLACE(str(DATEPART(SS,ModifiedDate),2),SPACE(1),0) +
	   CASE 
	      WHEN (DATEPART(HH,ModifiedDate) < 12)
	      THEN ' AM'
	      ELSE ' PM'
	   END AS 'Usable Date'
FROM [AdventureWorks].[Person].[Address]
WHERE [AddressID] IN (34,35,36)

Final Outcome
FinalAttempt

Using xp_cmdshell to pass parameters to vbs

May 27, 2010 1 comment

For an existing client running SQL Server 2005 Standard there was a need to take values from a result set and pass them to a vbs that resided on the server. In my post “Formating Date and Time” I ran through the steps of formatting a datetime data type to meet my needs.

I chose to pursue a pure tsql based option but there are other avenues which I could have followed such as using a pure VBS approach recommended by Robert Davis (blog | @sqlsoldier) which I have done in the past and worked quite well. Not sure why it didn’t come to mind as it is a viable solution first. Then Dave Levy (blog | @Dave_Levy) suggested powerShell. Which is something I actually haven’t worked with yet. Interesting as #powerShell sounds I had to produce something ASAP, but in the process I would research #powerShell nonetheless.

So I chose a TSQL path using the extended stored procedure xp_cmdshell to accomplish the task at hand. To get started xp_cmdshell needs to be enabled, but first you might want to turn on advanced options to see if its is already on.

TSQL

EXECUTE SP_CONFIGURE

Outcome
StandardOptions

As you can see the results set are limited so in order to see information about xp_cmdshell we need to switch on the advanced options… so let’s get to it.

EXECUTE SP_CONFIGURE 'show advanced options', 1
RECONFIGURE WITH OVERRIDE
GO
EXECUTE SP_CONFIGURE
GO

Being in alphabetic order you’ll need to scroll all the way down to see if the config_value is set to one. If it is set to zero then it’s disabled. In that event we need to enable it.

Outcome
02.AdvOptions_v0.01

That is done by setting the value to one. This is simply done by running the following:

EXECUTE SP_CONFIGURE 'xp_cmdshell', '1'
RECONFIGURE WITH OVERRIDE
GO
EXECUTE SP_CONFIGURE
GO

Now that xp_cmdshell is enabled we will see a value of one in the config_value field.

Outcome
03.xp_cmdshell_enabled

To test it let’s run the following:

DECLARE @results INT
DECLARE @cmd nvarchar(255)
SET @cmd = 'ping sqlsamson8' + ' -n 1 | find /i "reply"'

EXEC @results = master..xp_cmdshell @cmd
IF (@results = 0)
	SELECT 'ONLINE'
ELSE
	SELECT 'OFFLINE'

I don’t have a system called sqlsamson8 so ‘OFFLINE’ is returned.

Outcome
04.TestRun

Now let’s test this with a system that does exist.

DECLARE @results INT
DECLARE @cmd nvarchar(255)
SET @cmd = 'ping sqlsam7' + ' -n 1 | find /i "reply"'

EXEC @results = master..xp_cmdshell @cmd
IF (@results = 0)
	SELECT 'ONLINE'
ELSE
	SELECT 'OFFLINE'

Sure enough it’s ‘ONLINE’.

Outcome
05.TestRun

This is cool but I need some form of log to be generated. In short a file needs to be created and written to if the device was found on or offline.

DECLARE @results INT, @onLog NVARCHAR(50),@offLog NVARCHAR(50)
DECLARE @cmd NVARCHAR(255), @mn NVARCHAR(50), @cmd1 NVARCHAR(255)

-- Output results to file
SET @onLog = 'D:\xp_cmdshell\Online.txt'
SET @offLog = 'D:\xp_cmdshell\Offline.txt'
-- set target computer name
SET @mn = 'sqlsamson8'
-- set print process
SET @cmd = 'ping ' + @mn + ' -n 1 | find /i "reply"'
-- Execute ping process
EXEC @results = master..xp_cmdshell @cmd
IF (@results = 0)
	BEGIN
		-- Display online message to grid results
		SELECT @mn + ' IS ONLINE!'
		-- If device is online log computer name to output file
		SET @cmd1 =  'echo ' + @mn + ' >> ' + @onLog
		-- Execute statement
		EXEC master..xp_cmdshell @cmd1
	END
ELSE
	BEGIN
		-- Display offline message to grid results
		SELECT @mn + ' IS OFFLINE'
		-- If device is offline log computer name to output file
		SET @cmd1 =  'echo ' + @mn + ' >> ' + @offLog
		-- Execute statement
		EXEC master..xp_cmdshell @cmd1
	END	

Outcome
06.TestRun_WithOutputLog

Perfect. Exactly what I needed. However I need to run through a series of devices and having to manually specify a computer name is out of the question. I could use a CURSOR, but today I feel in a WHILE loop kind of mode…

DECLARE @Cnt INT, @mn NVARCHAR(50), @newmn NVARCHAR(50)
DECLARE @cmd NVARCHAR(255), @results INT, @cmd1 NVARCHAR(255)
DECLARE @offLog NVARCHAR(50), @onLog NVARCHAR(50)

SET @onLog = 'D:\xp_cmdshell\ONLINE.txt'
SET @offLog = 'D:\xp_cmdshell\OFFLINE.txt'
SET @mn = 'sqlsam'
SET @Cnt = 0

WHILE @Cnt < 10
BEGIN
	SET @Cnt = @Cnt + 1
	-- take @mn and append @Cnt (i.e. sqlsam + 1 = sqlsam1)
	SET @newmn = @mn + CONVERT(VARCHAR,@Cnt)
	SET @cmd = 'ping ' + @newmn + ' -n 1 | find /i "reply"'
	EXEC @results = master..xp_cmdshell @cmd
		IF (@results = 0)
			BEGIN
				-- Display online message to grid results
				PRINT @newmn + ' IS ONLINE!'
				-- If device is online log computer name to output file
				SET @cmd1 =  'echo ' + @newmn + ' >> ' + @onLog
				-- Execute statement
				EXEC master..xp_cmdshell  @cmd1
			END
		ELSE
			BEGIN
				-- Display offline message to grid results
				PRINT @newmn + ' IS OFFLINE'
				-- If device is offline log computer name to output file
				SET @cmd1 =  'echo ' + @newmn + ' >> ' + @offLog
				-- Execute statement
				EXEC master..xp_cmdshell @cmd1
			END
END

Great… loop and ping though the devices and record to logs accordingly. Bingo!

Outcome
07.TestRunLoop_WithOutputLog

Well there is one problem… if I run this again the results will be appended to the existing log essentially doubling the results with the same data. This is completely avoidable if I append a date-time stamp to the log file names!

DECLARE @Cnt INT, @mn NVARCHAR(50), @newmn NVARCHAR(50)
DECLARE @cmd NVARCHAR(255), @results INT, @cmd1 NVARCHAR(255)
DECLARE @offLog NVARCHAR(50), @onLog NVARCHAR(50),@dtStamp NVARCHAR(50)

SET @dtStamp =	CONVERT(varchar, GETDATE(), 112) + '_' + 
								REPLACE(CONVERT(varchar(5), GETDATE(), 108),':','')

SET @onLog = 'D:\xp_cmdshell\ONLINE_' + @dtStamp +'.txt'
SET @offLog = 'D:\xp_cmdshell\OFFLINE_' + @dtStamp +'.txt'

SET @mn = 'sqlsam'
SET @Cnt = 0

WHILE @Cnt < 10
BEGIN
	SET @Cnt = @Cnt + 1
	-- take @mn and append @Cnt (i.e. sqlsam + 1 = sqlsam1)
	SET @newmn = @mn + CONVERT(VARCHAR,@Cnt)
	SET @cmd = 'ping ' + @newmn + ' -n 1 | find /i "reply"'
	EXEC @results = master..xp_cmdshell @cmd
		IF (@results = 0)
			BEGIN
				-- Display online message to grid results
				PRINT @newmn + ' IS ONLINE!'
				-- If device is online log computer name to output file
				SET @cmd1 =  'echo ' + @newmn + ' >> ' + @onLog
				-- Execute statement
				EXEC master..xp_cmdshell  @cmd1
			END
		ELSE
			BEGIN
				-- Display offline message to grid results
				PRINT @newmn + ' IS OFFLINE'
				-- If device is offline log computer name to output file
				SET @cmd1 =  'echo ' + @newmn + ' >> ' + @offLog
				-- Execute statement
				EXEC master..xp_cmdshell @cmd1
			END
END

Outcome
08.TestRunLoop_WithOutputLog_TimeStamp

Now I only need to add the VBS path, execute it and then we are golden!

DECLARE @Cnt INT, @mn NVARCHAR(50), @newmn NVARCHAR(50), @vbscmd NVARCHAR(50)
DECLARE @cmd NVARCHAR(255), @results INT, @cmd1 NVARCHAR(255)
DECLARE @offLog NVARCHAR(50), @onLog NVARCHAR(50),@dtStamp NVARCHAR(50)

SET @dtStamp =	CONVERT(varchar, GETDATE(), 112) + '_' + 
								REPLACE(CONVERT(varchar(5), GETDATE(), 108),':','')

SET @onLog =  'D:\xp_cmdshell\ONLINE_' + @dtStamp +'.txt'
SET @offLog =  'D:\xp_cmdshell\OFFLINE_' + @dtStamp +'.txt'


SET @mn = 'sqlsamson'
SET @Cnt = 0

WHILE @Cnt < 10
BEGIN
	SET @Cnt = @Cnt + 1
	-- take @mn and append @Cnt (i.e. sqlsam + 1 = sqlsam1)
	SET @newmn = @mn + CONVERT(VARCHAR,@Cnt)
	SET @cmd = 'ping ' + @newmn + ' -n 1 | find /i "reply"'
	EXEC @results = master..xp_cmdshell @cmd
		IF (@results = 0)
			BEGIN
			SET @vbscmd = 'cscript /nologo D:\test.vbs ' + '"' + @newmn + '"'
				-- Display online message to grid results
				PRINT @newmn + ' IS ONLINE!'
				-- If device is online log computer name to output file
				SET @cmd1 =  'echo ' + @newmn + ' >> ' + @onLog
				-- Execute write statement
				EXEC master..xp_cmdshell @cmd1
				-- Execute vbs statement
				PRINT @vbscmd
				EXEC master..xp_cmdshell @vbscmd
			END
		ELSE
			BEGIN
				-- Display offline message to grid results
				PRINT @newmn + ' IS OFFLINE'
				-- If device is offline log computer name to output file
				SET @cmd1 =  'echo ' + @newmn + ' >> ' + @offLog
				-- Execute statement
				EXEC master..xp_cmdshell @cmd1
			END
END

Outcome
09.ParameterPassedToVBS

Of course this only works when the naming convention is standardized and the numbers are sequentially sound. I could create a temp table to pull in all the device names and add a ID field that auto increments by a value of one. Then run through the loop selecting the data from temp table where ID = @Cnt. That is a feasible option.

Get them Server Properties

Though this may be trivial it is always useful. There are many ways to determine what version, edition and service pack level of SQL Server you are running. So here is a one way to get the information you need.

TSQL

SELECT  RIGHT(LEFT(@@VERSION,25),15)        AS [Product]
	    ,SERVERPROPERTY('productversion')   AS [Version]
	    ,SERVERPROPERTY('edition')          AS [Edition]
	    ,SERVERPROPERTY('productlevel')     AS [Service Pack]
        ,SERVERPROPERTY('ServerName')       AS [ServerName]

Results

5-26-2010 10-42-34 PM

Note:

If the char length of SQL Server 2008 changes then (line 1) will need to be adjusted accordingly. I haven’t searched for a better solution; however, if you know of one please feel free to pass on the information. I would appreciate it.

Updated: 2010-06-05

Recently as I was working more with CHARINDEX I thought I might use to extract the product from @@VERSION. The number 15 is the char length of SQL Server 2008. Hence the 15 of course.

SELECT SUBSTRING(@@VERSION,CHARINDEX('SQL',@@VERSION),15) 'Product'

It will return SQL Server 2008. Again if the product name changes CHAR length then this will be off.

Follow

Get every new post delivered to your Inbox.