•  
  • Archives for TSQL Tuesday (39)

T-SQLTuesday #42! The Long and Winding Road

Comments: No Comments
Published on: May 14, 2013

 

 

 

TSQL2sDay150x150The Long and Winding Road

It is time for another installment in the monthly blog party for SQL Server professionals known as TSQL Tuesday.

This month we have the pleasure of being hosted by Wendy Pastrick (blog | twitter).  The topic for the month requires a bit of introspection (almost like the self-evaluation piece of an annual review).  Quoting direct from her blog, here is the gist of the topic:

Here’s what I thought it would be fun to share with the community this time around – we all experience change in our work lives. Maybe you have a new job, or a new role at your company. Maybe you’re just getting started and you have a road map to success in mind. Whatever it is, please share it next week, Tuesday May 14th. Make sure you note what technologies you find are key to your interests or successes, and maybe you will inspire someone to look down a road less traveled.

longuphillbw2For me, this is an interesting topic.  It was my theme of choice last month with a major announcement (see here).  And because of that, I am even using the same image – slightly changed.  Only this time, I will go back a bit further into my career and the road I traveled to get to today.

I am going to go back to a decision point in my career that had a huge impact on where I am now.  That decision point was shortly after having moved to Las Vegas about four years ago.  After having moved to Las Vegas, I made the decision to become more active in the SQL Community.  The first step was to regularly attend the user group meetings.

Prior to moving to Las Vegas, I was a member of PASS.  I had been to SUMMIT.  I knew of the local user group meetings in the Salt Lake City area.  I just never forced the issue due to timing etc.  This was something that I felt needed to change.

By making that conscientious decision, I became more involved in the online community. I soon started presenting.  And before long, I was involved in the scheduling of speakers for the Las Vegas UG.

By becoming more active in the community, my skillset started to rapidly grow.  I found myself blogging more and researching more about SQL Server.  I really started to learn about SQL thanks to that decision.  Prior, I feel I was good.  Now, I feel I am much better because I invested more time and effort and I am trying to share the skills that I have learned.

I have said it before and it is worth saying again.  If you really want to learn a technology, try teaching it to somebody.  By taking on that extra step, you will find yourself researching a bit more and you will find that you may have to answer questions about it that you had never considered until you tried to teach it.  Being active in the community has helped me to become better at my trade.  I am sure it will help others as well.

 

T-SQL Tuesday #040: File and Filegroup Wisdom

Comments: 3 Comments
Published on: March 11, 2013

Backstory

Each month the SQL community comes together for an important party.  This is the blog party that was the brain child of Adam Machanic (Twitter) known as T-SQL Tuesday.

The party is a very good collaboration among data professionals on a pre-determined topic.  This month, for TSQL Tuesday #40, the topic is on Files and Filegroups.  The host du mois is Jen McCown (Twitter).

This month, I had the luck of encountering something this past week that is right up the alley of this topic.  I love it when sysadmins help create learning opportunities for me (e.g. blog material).

Production Down

I was recently given the following concerning a client server issue:

The log file for database ‘xxx’ is full. Back up the transaction log for the database to free up some log space.

That was followed by a short description stating that the sysadmin had tried to expand the log file and that they also tried to run a full backup.  The output of the full backup was as follows.

BACKUP DATABASE [xxx] To Disk=’blah’ WITH NOFORMAT, NOINIT, NAME = N’blah’, SKIP, REWIND, NOUNLOAD, STATS = 10
” failed with the following error: “The backup of the file or filegroup “sysft_FTS” is not permitted because it is not online.
BACKUP can be performed by using the FILEGROUP or FILE clauses to restrict the selection to include only online data.

Now this makes things more interesting.  The sysadmin at least tried to do a full backup and then handed off when it got too deep.

The client server is a SQL 2005 box.  Fulltext was enabled for the database on that box. And we have seen plenty of issues related to Fulltext in SQL 2005.  Somehow, I feel that none of them really pertained to this opportunity.  From all appearances, there was either a disk issue (no history in the logs but client said there was) or somebody deleted the directory (there was a login at the time the issue started and there was a service restart at that time).  In either case, the folder for the fulltext filegroup was no longer present.  But I am getting a little ahead of myself.

When querying the sys.database_files catalog view, I was able to confirm the directory path that should have been in place for the filegroup and that the filegroup was indeed OFFLINE.  Results and query to follow, with filepaths redacted intentionally.

SELECT FILE_ID,type_desc,name
		,physical_name --intentionally omitted in result set
		,state_desc
	FROM sys.database_files;
file_id type_desc name state_desc
1 ROWS Somefile ONLINE
2 LOG Somefile_log ONLINE
3 ROWS Somefile_data ONLINE
4 ROWS Somefile_index ONLINE
65537 FULLTEXT sysft_FTS OFFLINE

So, indeed I do have a problem with the filegroup and I need to get it back online in order to resume backups and get this database back online and able to perform backups.

Some suggestions out there would be to rebuild the fulltext catalog in order to bring it back online.  Well, the files are no longer present on the filesystem, so this didn’t work too well.

ALTER FULLTEXT CATALOG [FTS] REBUILD
/*
Full-text catalog 'FTS' is in an unusable state. Drop and re-create this full-text catalog.
*/

The notes in the code block represent the outcome.  And the output makes sense if you ask me.  But when trying to drop and recreate, I ran into some more fun.

DROP FULLTEXT CATALOG [FTS]
--or
SP_FULLTEXT_DATABASE @ACTION= 'disable'
 
/*
Cannot drop full-text catalog 'FTS' because it contains a full-text index.
*/

Once again, that makes sense.  I had hoped that it would drop everything for me.  So, time to try dropping the indexes and recreating them.  For this, I took screenshots of each index in question.  Then tried to drop them.  Once again – another error.

property fulltextindexsize is not available

Despite that error, the indexes were gone and the catalog dropped.  Since I had disabled FT on the database, I needed to re-enable it in order to recreate the catalog and indexes (I had scripts for the catalog and screenshots for the indexes).

SP_FULLTEXT_DATABASE @ACTION= 'enable'

Now issuing a rebuild against that catalog works as expected.  Additionally, backups work as expected.  And to confirm that all is well, query sys.database_files once again to see that the filegroup is online.

file_id type_desc name state_desc
1 ROWS Somefile ONLINE
2 LOG Somefile_log ONLINE
3 ROWS Somefile_data ONLINE
4 ROWS Somefile_index ONLINE
65537 FULLTEXT sysft_FTS ONLINE

Between the Lines

I breezed through what got this filegroup back online so database activity could resume.  One thing that I skipped over was a step I took trying to recover without dropping and recreating.  Since the directory was not present, and there was a full backup from the same day that had the filegroup in a working state, I tried to recover the filegroup manually.  Restore the database, copy the folder structure into the appropriate filepath and run an alter database statement.  Since it didn’t work, I am not going into deep details on it.  The short of it is that since the structure disappeared off disk, there was some corruption related to it internally in the database.  That needed fixed and in this case it meant to drop the indexes and catalog in order to recreate it.

T-SQL Tuesday #38 Recap

Comments: No Comments
Published on: January 15, 2013

This is the wrap up for TSQL Tuesday #38.  We had a good turnout this month with some new faces and with some regulars.  In total, we had 22 entries.  All of the entries this month were very good.

The topic was on “Standing Firm” and can be found by clicking the image to the left.

Stay tuned to twitter for future TSQL Tuesday announcements and news via the #tsql2sday hash tag.

 

Rob Farley (blog | twitter) - Running Goals: In his opening paragraph Rob says “Peer pressure can be useful at times, but I also find that it can make me even more stubborn.”  WOW!  I can really relate to that.  Then he proceeds to recount a story from his past that would be hard for a lot of people to overcome.  I know I would have a hard time with not being able to lift my children.  Now Rob is running and plans to keep going and improving.  Excellent story!

Jim McLeod (blog | twitter) - Environment: Jim also decided to write about fitness and exercise, specifically cycling.  Jim ties planning into his goal to cycle more and stick with it.  Then he ties that into SQL Server.  ”Put together an environment that supports and encourages you to stick to your resolution.”  Plan ahead whether it be with certification or cycling, and not just for the happy times but for the rough times as well.

Thomas Stringer (blog | twitter) - Lucky 13:  Like many cross country runners, Thomas wants to hit it hard.  Then he wants to maintain the pace.  The principle is to set out to do something and build up a routine.  Routine becomes habit – and eventually becomes easier.  Thomas is setting out to learn something new every day.  That is a good goal.

Robert Davis (blog | twitter) - Disaster Recovery Resolutions:  This month Robert happens to be providing a month long series on Disaster Recovery.  In keeping with that theme, he has offered up several resolutions every DBA should make.

Koen Verbeeck (blog | twitter) - Resolving an SSIS Performance Problem:  Koen reached Defcon 2 (not really – but you should read it) when presented an opportunity to troubleshoot performance problems in an SSIS package.  The problem seems pretty typical – package works and then starts slowing down over time.  Part of the reason for that is due to four bullet points that Koen discusses.

John Sansom (blog | twitter) - Take More Ownership:  John hits on a big button in his contribution this month.  There are problems within our database environments.  Often times these problems are allowed to coexist.  While they cost time and money and raise frustration levels, how many times do we step up and offer a solution to the real problem?  It’s a good story – check it out.

Matt Velic (blog | twitter) - Stuck:  Matt is a thinker and he was stuck in a rut of late.  Matt enjoys thinking about decisions – a lot.  This was contributing to him being stuck.  He reveals some quick tips about how to get unstuck and to enjoy life just a bit more.

Steve Jones (blog | twitter) - Resolute:  Many people have a hard time standing up for what they want or believe.  They have a hard time saying “no.”  We sometimes joke about DBAs being a stop in the flow in work because we say “no” too much.  I think we probably do not say “no” enough.  And Steve points out plenty of ways that we need to learn to say no more often.  It is all about life balance.  How many times do you say “no” to a new project when you already have a full plate?

Julie Koesmarno (blog | twitter) - Tribute to Mum:  Julie, like her mother, is a WIT/Engineering.  We learn   about some of the personal life of Julie as she gives credit to her mother for being a role model as she grew up.  Now, Julie wants to help be an inspiration to others as her mother was for her.

Chris Fradenburg (blog | twitter) - Avoiding the Repetitive Mess of a Disaster:  Chris is the first of the first timers that participated this month (we had a few – woohoo).  Besides the bottle of gel soap that must be used every time he washes his hands, he is trying to improve his environment by reducing the manual repetitive tasks.  This is a good story about a disaster encountered on the first day on the new job.

Wayne Sheffield (blog | twitter) - Learning:  I did a fair bit of arm twisting to get Wayne to participate this month.  He was having a bit of writers block.  Then a topic fell into his lap and it should provide a month of writing for him.  Wayne discusses how is looking to learn and also provide a series of articles on PoSH to help others learn.  It should be out soon!

Alan Dykes (blog | twitter) - Solid Skills:  Alan is pretty much a self taught SQL Developer.  He has recently resolved to sharpen his skillset.  He learned from reading another post about how performance can vary by using a different tsql solution (e.g. NOT EXISTS versus LEFT OUTER JOINS).

Robert Pearl (aka Bobby Tables) (blog | twitter) - HealthySQL:  Some more arm twisting on this one.  Bobby has an excellent idea and it is something we should resolve to do every year as Data professionals.  Too often we get into the break/fix mode rather than the preemptive strike mode.  Regular maintenance (like you should do with your car) keeps things running longer and with less overall cost.

Hemanth.D (blog | twitter) – Firmness of Purpose:  Hemanth is the second of our first timers to participate this month.  Hemanth goes back and explores the past a little and how community ties in with the #SQLFamily.  Hemanth wants to be more resolute in the upcoming year when faced with an issue and ensuring the issue is resolved.

Mickey Stuewe (blog | twitter) – Spoons:  Mickey introduces us first to Neo and his effort at bending a spoon.  Then she correlates that to her writing ability.  For her to write is like trying to bend a spoon with her mind.  So she uses other tools to accomplish the goal.  She shared a couple of tools of which I think I will try the mind mapping tool.  That might mean some bizarre maps though.

Ed Watson (blog | twitter) – Speaking of Resolve:  Another first timer to the party (iirc), Ed shares his resolution to start speaking more at the local level.  Ed was more prone to flight than fight when it came to public speaking.  He talks about his experience and what he did in 2012 to start speaking publicly.

Jeffrey Verheul (blog | twitter) – Standing Firm:  Jeff had already planned out his goals by the time TSQL Tuesday rolled around.  After he had written some goals, he ran into a rough patch.  Based on that week, he has chosen to stand more firm and improve all that much more.

Ana Mihalj (blog | twitter) – Getting Involved:  Another warm welcome to another first timer.  Ana is from Bosnia and Herzegovina and has been working on blogging and getting involved in the community for the past year and a half.  Now she wants to step up her efforts.

Ken Watson (blog | twitter) – Not an Average Accidental DBA:  Ken shares a story on how he transitioned from a jack of all trades to being a DBA.  Ken is not enamored with being an average DBA and is working at something about which he is passionate.  He chased a dream and it is working out for him.

Tim Ford (blog | twitter) – Bring out your Dead:  You’ll get it once you read his post.  Tim is bringing back #Learn365.  If there were two themes this month it was exercise and learning.  Learn365 is right up that alley with learning.  Go check it out.

Chris Yates (blog | twitter) – Hoops and Databases:  We have a baller among us.  Chris talks about the resolve he had as a High School athlete to be a better ball player and make it to the Collegiate game.  That same resolve is applicable to being an Exceptional DBA.  Practice your craft and stand firm in your determination to be an Exceptional DBA.

My entry (twitter) – A Firm Foundation:  I shared a story that relates to disaster recovery and database consistency.  In that article, I also explored the immediate impact of changing the page verify for your database (plan cache).

 

A Firm Foundation

Last week I sent out an invite for the monthly TSQL Tuesday party.

The theme for the party is a take on the words resolve or resolution.  I was hoping the theme would encourage some reflection and sharing of real life experiences that have led to a difference being made.

I have resolved on two stories to share.  Both are rather short and simple in nature.

 

This arch (in Arches National Park, Ut.) has stood RESOLUTE for milennia

Story the First

Near the end of the year in 2012, I inherited a database that had not had a consistency check done on it – ever!  In checking the page_verify setting, I found that it was set to none as well.  Both of these should be alarming to any DBA – unless you are completely unconcerned by corrupt data and the potential for corrupt data.  Never-mind the potential business repercussions of having corrupt or lost data.

To find what level of page verification you have enabled, it is a matter of a quick script like the following.

SELECT name, page_verify_option_desc
	FROM sys.databases;

You can have any one of three settings for your page_verify.  The recommended option is to have CHECKSUM enabled.  If you see NONE or TORN_PAGE_DETECTION, you really need to consider changing that.  Keep in mind if you are still running SQL 2000, CHECKSUM is not an option and the query provided will fail.

Changing the verify option is very simple as well.  It only requires an Alter Database to be run such as the following.

ALTER DATABASE [msdb]
	SET PAGE_VERIFY CHECKSUM;

You will probably notice that I am using the msdb in my sample script.  There is a reason for this that will be shown later.  Just keep in mind that msdb should not need to be changed because it should already be using the CHECKSUM option.

What if you have numerous databases that are not using the CHECKSUM method?  It can become rather tedious to change each of those manually.  That is why we might come up with a cursor such as the following.

DECLARE
	@DBName SYSNAME,
	@SQL    VARCHAR(512);
 
DECLARE dbchecksum CURSOR
	LOCAL STATIC FORWARD_ONLY READ_ONLY
	FOR SELECT name
		FROM sys.databases
		WHERE name not in ('tempdb')
			AND state_desc = 'online'
			AND page_verify_option_desc <> 'Checksum';
 
OPEN dbchecksum;
FETCH NEXT FROM dbchecksum INTO @DBName;
WHILE @@FETCH_STATUS = 0
BEGIN
SET @SQL = 'ALTER DATABASE [' + @DBName +'];' +CHAR(10)+CHAR(13)
SET @SQL = @SQL + 'SET PAGE_VERIFY CHECKSUM;' +CHAR(10)+CHAR(13)
 
EXECUTE (@SQL);
SET @SQL = ''
 
FETCH NEXT FROM dbchecksum INTO @DBName;
END
CLOSE dbchecksum;
DEALLOCATE dbchecksum;

This script is only checking for databases that are not using CHECKSUM.  Then it loops through and changes the setting to use CHECKSUM.

I strongly caution about running this in production without an outage window!  I make that recommendation for very simple reasons.  First, the change is to a production system.  Second, the change can have a temporary adverse effect.  Now before you get too excited about it, I have a short demonstration.

Here is a script broken out into three sections.

SELECT TOP(10) [type] AS [Memory Clerk Type], SUM(single_pages_kb) AS [SPA Mem, Kb] 
FROM sys.dm_os_memory_clerks WITH (NOLOCK)
GROUP BY [type]  
ORDER BY SUM(single_pages_kb) DESC OPTION (RECOMPILE);
SELECT DB_NAME(database_id) AS [DATABASE Name],
COUNT(*) * 8/1024.0 AS [Cached SIZE (MB)]
FROM sys.dm_os_buffer_descriptors
WHERE database_id <> 32767 -- ResourceDB
--AND database_id > 4 -- system databases
GROUP BY DB_NAME(database_id)
ORDER BY [Cached SIZE (MB)] DESC OPTION (RECOMPILE);
 
SELECT DB_NAME(dbid) AS DbName,dbid,SUM(size_in_bytes)/1024/1024 AS TotalPlanCacheSize_in_MB
FROM sys.dm_exec_cached_plans cp WITH (NOLOCK)
CROSS APPLY sys.dm_exec_sql_text(plan_handle) st
GROUP BY dbid
ALTER DATABASE [msdb]
		SET PAGE_VERIFY CHECKSUM;
SELECT TOP(10) [type] AS [Memory Clerk Type], SUM(single_pages_kb) AS [SPA Mem, Kb] 
FROM sys.dm_os_memory_clerks WITH (NOLOCK)
GROUP BY [type]  
ORDER BY SUM(single_pages_kb) DESC OPTION (RECOMPILE);
SELECT DB_NAME(database_id) AS [DATABASE Name],
COUNT(*) * 8/1024.0 AS [Cached SIZE (MB)]
FROM sys.dm_os_buffer_descriptors
WHERE database_id <> 32767 -- ResourceDB
--AND database_id > 4 -- system databases
GROUP BY DB_NAME(database_id)
ORDER BY [Cached SIZE (MB)] DESC OPTION (RECOMPILE);
 
SELECT DB_NAME(dbid) AS DbName,dbid,SUM(size_in_bytes)/1024/1024 AS TotalPlanCacheSize_in_MB
FROM sys.dm_exec_cached_plans cp WITH (NOLOCK)
CROSS APPLY sys.dm_exec_sql_text(plan_handle) st
GROUP BY dbid

Sections one and three are the same.  This script is used to measure various memory components within SQL Server.  The second section is the change we will make to the msdb database.  The queries in the first and third section perform the following: retrieve memory clerk usage (aggregated to memory clerk type), retrieve total data pages stored in cache (aggregated by database), and retrieve the plan cache use (aggregated by database).

Now on to some pre and post change results.  First with what my results were prior to the change.

Memory Clerk Usage
Memory Clerk Type SPA Mem, Kb
CACHESTORE_SQLCP 156184
CACHESTORE_PHDR 45904
CACHESTORE_OBJCP 20664
USERSTORE_DBMETADATA 8472
USERSTORE_SCHEMAMGR 6376

 

Pages in Cache
Database Name Cached Size (MB)
msdb 12.265625

 

Plan Cache
DbName dbid TotalPlanCacheSize_in_MB
NULL 32767 42
NULL NULL 150
msdb 4 13
ReportServer$ADMIN 5 0
MDW 28 8
AdminDB 14 0

And the following are the post change results.

Memory Clerk Usage
Memory Clerk Type SPA Mem, Kb
CACHESTORE_SQLCP 109160
CACHESTORE_PHDR 36744
CACHESTORE_OBJCP 9152
USERSTORE_DBMETADATA 8472
USERSTORE_SCHEMAMGR 6296

 

Data Pages in Cache
Database Name Cached Size (MB)
msdb 12.265625

 

Plan Cache
DbName dbid TotalPlanCacheSize_in_MB
NULL 32767 36
NULL NULL 104
ReportServer$ADMIN 5 0
MDW 28 8
AdminDB 14 0

First observation I want to point out is with the second result for both the pre and post run.  Making this change will not affect the pages in cache.  This goes along with what we have been taught by Paul Randal – that a CHECKSUM is not performed immediately (I paraphrased).  You can read more about the CHECKSUM and some misconceptions about it here.

If we now turn our attention to the first and third result sets, we will see that there are changes in the memory clerks used and the plan cache.  Starting with the the third result set (both pre and post) we see that the ResourceDB decreased in total plan cache size.  The NULL item (adhoc queries not associated to a specific database) also decreased.  After that, the only change in size is the msdb database – disappeared from the results due to no plan cache in use associated to this database.  (Starting to see why I chose the msdb database for this demo?)

If you now look closer at the results for the first query on both sides of the change, you will see correlating changes to the plan cache.  Notice that CACHESTORE_SQLCP dropped by about 46MB (correlates to the null entry from query 3).  But of those clerks listed, you will see that only USERSTORE_DBMETADATA did not change in size.

Looking at these results should demonstrate why this change should be performed during a maintenance window.  There will be an effect on performance and I would rather you let the business know what is coming down the pipe.  This change is akin to running DBCC FLUSHPROCINDB(<db_id>);.  There are other database settings that will have the same effect.  You can read a little about that from Kalen Delaney – here.

Story the Second

This story is far less interesting and a whole lot shorter.  This falls into the category of professional development and fine tuning my skills.  I took the MCM lab exam during the PASS Summit.  I failed, not unlike many who have attempted it.  That is all fine and well. I learned some things about myself and I learned some areas that may need some resolution (sharpened focus).

So as more of a resolution upon which I have greater resolve than a New Years resolution, I will be retaking the Lab exam.  And I will be getting my MCM in the near future.  Just sayin’!

T-SQL Tuesday #38 – Standing Firm

Comments: 29 Comments
Published on: January 2, 2013

Introduction

Welcome back for the 38th installment in the wildly popular blog party for the SQL Server community.  This is the party that happens on the second Tuesday of each month.  The party was started by Adam Machanic (B|T) just over three years ago.

Each month a new host selects a theme and announces it about a week in advance.  And this month I will be hosting.

Theme

To kick off the new year (2013), we must first adhere to a little tradition.  This is not a T-SQL Tuesday tradition.  It is more of an annual tradition for all to welcome the new year.  Feel free to click the link and sing along!!

Now for the theme.  A common thing for many people to do this time of year is to do a little self reflection.  Some set meaningful goals for themselves.  Fewer actually accomplish those goals or even follow-up after initially setting the goal.

We are not going to set goals as a part of this T-SQL Tuesday – unless you want to.  I want to take a little different spin on the New Year’s “resolution” tradition.  So the theme this month is “Standing Firm.”

The idea for this theme is to start with a little self reflection.  Then to come up with a story relating to one of these words: resolve, resolution, or resolute.  Here are some examples of how these stories may be portrayed.

  • Resolve:  A system outage occurred and you “resolved” it.
  • Resolute:  You made an executive decision and did not waver from it.
  • Resolution:  You discovered a bug and documented a work-a-round resolution for it.
  • Resolution:  You have discovered certain T-SQL skills are fuzzy and want to sharpen your ability in that area.
  • Resolute:  You are determined to improve performance in your application.

All of these words are very closely related.  It is up to you to determine how you would like to apply them to your T-SQL world.  Your experiences and stories can be loosely or tightly coupled to T-SQL, it is up to you.

And since the theme requires a little bit of self-reflection first, bonus kudos to those that can tie a past experience to a future plan.

The Rules

I know, parties are not supposed to have rules.  Sadly, all parties have some rules – you just may not know them.  These rules are very simple.

  1. Your post must go live between 00:00:00 GMT on Tuesday the 8th of January 2013 and 00:00:00 GMT on Wednesday the 9th.
  2. Your post has to link back to the hosting blog, and the link must be anchored from the T-SQL Tuesday LOGO (found above) which must also appear at the top of the post.
  3. Trackbacks should work. However, all comments and trackbacks are moderated, so give me a few minutes. If you think you waited long enough and still don’t see yours, leave a comment below.
  4. Tweet your post to the #TSQL2sDay hash tag.
  5. Have Fun writing and participating.

Follow these rules, and your post will be included in the roundup that will be posted on the 15th or 16th of January.

Let’s Talk About Joins

Comments: 1 Comment
Published on: December 11, 2012

T-SQL Tuesday #37

This month please join us in the TSQL blog party that happens on the second tuesday of the month.  It is hosted this month by Sebastian Meine (@sqlity).

Sebastien has a month long blog going this month all about Joins.  You can read all about that fun in his post titled A Join A Day – Introduction.

 

This is a good topic.  And I pondered what to write this month for a bit.  I immediately went to a topic that I had on my toblog list.  Unfortunately I had already covered that topic once upon a time.  But with it being at the first thing my mind went to when thinking of this topic, I feel it would be good to re-hash it.  I may change it up a bit, I may not – we’ll just have to see where this post takes us.  So jump on board the Crazy Train for a little Salt N Peppa remix – Let’s Talk About Joins.

Business Requirement

I have some tables that I need to query.  One of the tables has lookup information with a bitmask applied to the id field.  Another table references this table but the ids can be a direct match or an indirect match to the id of the lookup table.  For this case, they will always only differ by the value of 1 if the two values do not directly match.  No other match possibility is considered for this example.

Based on this need, the solution dictates some sort of bitwise math.  I have several examples of how a join could be written to accomplish this primary objective.  I am only going to show the possible ways of performing this.  In my environment these all yield the same results and the data is unique and large enough (4.2 million records).  I will compare performance of these different queries in a later post as I demonstrate a query tuning method to drive the query down from nine seconds to 100ms or less.  For the record, I would choose any of queries 5, 6, or 7 for this particular need based on plan cost and performance.

The JOINS

First up is the ANSI style INNER Join using addition in one of the conditions as well as an OR to fulfill the second portion of the business requirement.

PRINT 'Query 1 -- Join with Or and source+1'
------
SELECT TOP 1000
		PPV.RumorID,PPV.PersonRumorID,PPV.PersonID
		,US.Source,Us.SourceID, PPV.SourceID
	FROM	RumorView PPV
		INNER Join SourceType US
			ON (PPV.SourceID = US.SourceID
				Or PPV.SourceID = US.SourceID+1)

This is probably the easiest to understand and it performs well enough.  Until running into this business requirement, I hadn’t considered putting an OR in the JOIN conditions.  But it makes sense considering that an AND can be used there.

Next is a NON-ANSI style of JOIN.

PRINT 'Query 2 -- Non-Ansi Join with Or and source+1'
------
SELECT TOP 1000
		PPV.RumorID,PPV.PersonRumorID,PPV.PersonID
		,US.Source,Us.SourceID, PPV.SourceID
	FROM	RumorView PPV, SourceType US
	WHERE (PPV.SourceID = US.SourceID
				Or PPV.SourceID = US.SourceID+1)

Through 2008 R2, this works just as well as the ANSI JOIN already shown.  I haven’t tested in SQL 2012 but I do know that the NON-ANSI syntax of *= (for example) no longer works.  I am not a big fan of this style JOIN because it is far too easy to end up with a Cartesian product.

Another type of JOIN that I like is the use of APPLY.

PRINT 'Query 3 -- Cross Apply with Or and source+1'
------
SELECT TOP 1000
		PPV.RumorID,PPV.PersonRumorID,PPV.PersonID
		,US.Source,Us.SourceID, PPV.SourceID
	FROM	RumorView PPV
		Cross Apply SourceType US
	WHERE (PPV.SourceID = US.SourceID
				Or PPV.SourceID = US.SourceID+1)

This particular code segment is the equivalent of the first query shown.  This is the last in the set of using basic math and an OR in the JOIN conditions.  The remaining queries all rely on bitwise operations to perform the JOIN.  Again, until this particular need, I had never even considered using a bitwise operation in a JOIN.  First in this series is the NON-ANSI style JOIN.

PRINT 'Query 4 -- Non-Ansi with COALESCE and Bit compare'
------
SELECT TOP 1000
		PPV.RumorID,PPV.PersonRumorID,PPV.PersonID
		,US.Source,Us.SourceID, PPV.SourceID
	FROM	RumorView PPV, SourceType US
	WHERE (PPV.SourceID|1 = COALESCE(US.SourceID|1,Us.SourceID))

The big change here is in the where clause.  Notice the use of COALESCE and the first comparison value in that COALESCE.  This is called a BITWISE OR.  From MSDN: “The bits in the result are set to 1 if either or both bits (for the current bit being resolved) in the input expressions have a value of 1; if neither bit in the input expressions is 1, the bit in the result is set to 0.”

So I am comparing the bit values of 1 and the SourceID.  The SourceID from RumorView will create a match meeting the requirements put forth thanks in large part to the BIT OR operation being performed on both sides of the equality in the WHERE clause.  It is also worth mentioning that the COALESCE is completely unnecessary in this query but it I am leaving it as a pseudo reference point for the performance tuning article that will be based on these same queries.

Next on tap is the CROSS Apply version.

PRINT 'Query 5 -- Cross with Bit compare'
------
SELECT TOP 1000
		PPV.RumorID,PPV.PersonRumorID,PPV.PersonID
		,US.Source,Us.SourceID, PPV.SourceID
	FROM	RumorView PPV
		Cross Apply SourceType US
	WHERE (PPV.SourceID|1 = US.SourceID|1)

And the last two queries that the optimizer equate to the same query.

PRINT 'Query 6 -- Join with Bit compare'
------
SELECT TOP 1000
		PPV.RumorID,PPV.PersonRumorID,PPV.PersonID
		,US.Source,Us.SourceID, PPV.SourceID
	FROM	RumorView PPV
		INNER Join SourceType US
			ON (PPV.SourceID|1 = US.SourceID|1)
------
PRINT 'Query 7 -- Join with ISNULL and Bit compare'
------
SELECT TOP 1000
		PPV.RumorID,PPV.PersonRumorID,PPV.PersonID
		,US.Source,Us.SourceID, PPV.SourceID
	FROM	RumorView PPV
		INNER Join SourceType US
			ON (PPV.SourceID|1 = ISNULL(US.SourceID|1,Us.SourceID))

The query optimizer in this case is smart and eliminates the ISNULL.  These two queries use the same exact plan, have the same cost and the same execution statistics.  The version with COALESCE is considered more expensive and takes longer to run than these queries.  It is also important to note that the Cross Apply Join also produces the exact same plan as these two queries.

Conclusion

So there you have it.  Many different ways to write the JOIN for this little query.  Performance and results may vary.  It is good to have a few different ways of writing this particular JOIN.  During my testing, it was evident that various methods performed better under different circumstances (such as how the indexes were configured – which will be discussed in the follow-up article).

Yes, I did re-use my previous post on this particular topic.  That said, I want to add another tidbit.

This Post just Goes On and On

When Joining objects, you should take care as to the data type used in the Join.  Without going into implicit conversions in the Joins, I just want to discuss briefly the impact of data type choice in a Join.  This is where knowing your Data, the workload, and usage patterns is going to be an absolute necessity.

That said, you should not be surprised by a change in the performance of your queries if you were to change from a varchar() datatype to a varbinary() datatype.  I’m not saying that the query is going to perform better – but that the performance may change.  As an example, I was effectively able to turn a well performing query into a very poor performing query by changing from varchar to varbinary.  On the flipside, I have seen the reverse also become true.  It all boils down to proper data types for the data.

High Energy Plankton – T-SQL Tuesday #35

Tags:
Comments: No Comments
Published on: October 9, 2012

High Energy Plankton

In 2022 humans will finally have become their own worst enemies/frenemies.  No matter how hard Gore works to convince us that the greenhouse effect is killing us, we won’t believe it until 2022.  It is at that time we will finally turn to alternative energy sources.  These alternative energy sources include the food source derived from high energy plankton.

You may already have heard of this product, but in case you have not, you can get a glimpse of it now and maybe prevent our impending doom.

Hurry now while supplies last – get your box of Soylent Green crackers.  This product is available for purchase here.  (Image displayed is TM & © of Turner Entertainment Co. and is only hyperlinked here).

This high energy food source was the brainchild of the movie that is the source of this months blog party hosted by Nick Haslam (T|B).  This blog party is also known as T-SQL Tuesday and you can read the invite here.

Now, Soylent Green may or may not be that cool granola type food you are looking for.  But I really hope the by-product of Soylent Green is nothing you want.

Good Intentions

“So, what I’d like to know is, what is your most horrifying discovery from your work with SQL Server?”

I thought about this quite a bit.  The reason being that I really wanted to include some code that would classify this entry as Soylent Green.  Many examples came to mind, but I am not sure I can share any of that code.  However, I do have a few stories to share.  In no particular order, here you go.

  1. Dealing with a particular application we noticed that performance was far less that optimal (big surprise there I know ;) ).  While monitoring and through performing traces to find the pain points I discovered something very peculiar.  The peculiarity came in the form of having multiple plans cached for the same procedure call.  That may or may not be peculiar just yet – but when I add the info that the procedure call was to perform the login process for the application – that is what becomes peculiar.  Using the same parameters the application had called the login stored procedure four different ways.
    They did a fine job by using a stored procedure to perform the logon process.  Unfortunately they did not wrap that into an object within the application and instead called the logon process differently each different module that called it.
    You can see similar activity by doing the following against the Adventureworks database.
    SELECT TOP (1) p.Name
    FROM Production.Product AS p
    JOIN Production.TransactionHistory AS th 
        ON th.ProductID = p.ProductID
    ORDER BY p.Name;
    GO
    SELECT TOP (1)
        p.Name
    FROM Production.Product AS p
    JOIN Production.TransactionHistory AS th ON
        th.ProductID = p.ProductID
    ORDER BY
        p.Name;
    GO
     
    SELECT TOP (1)  p.Name
    FROM Production.Product AS p
    JOIN Production.TransactionHistory AS th 
    	ON th.ProductID = p.ProductID
    ORDER BY p.Name;   
    GO
     
    SELECT TOP (1)  p.Name
    FROM Production.Product AS p
    Join Production.TransactionHistory AS th 
    	ON th.ProductID = p.ProductID
    ORDER BY p.Name;
    GO
     
    SELECT qp.query_plan,cp.plan_handle,cp.usecounts,st.TEXT
    	FROM sys.dm_exec_cached_plans cp
    	CROSS APPLY sys.dm_exec_query_plan(plan_handle) qp
    	CROSS APPLY sys.dm_exec_sql_text(plan_handle) st;
  2. Much earlier in my career I had the distinct pleasure of working for a bank as a DBA.  This was a short lived experience due in large part to this experience.  We were being audited and I was newly hired.  One of the first requests was for a temporary change to be made in the database while the auditor was present.  And the request also made it clear that it was to be changed back once that auditor left. My immediate response was “NO.”  There were many colorful arguments made as to why I should immediately make that change.  That was a great experience (shutter).
  3. More recently I inherited an environment that was experiencing pretty bad performance.  Hardware was thrown at it to try and alleviate the problems.  After some discovery it was found that no cluster indexes could be found in any table in any database.  The reason given was that the Cluster Indexes would change query results and they couldn’t take that risk.  After much work, many clustered indexes were placed into the environment with no negative impact.  I worked very hard on that environment to get it purring.  I found that after I left it only took a few months for things to start falling apart.  Some of the problems include the DBAs refusing to perform the database backups insisting that the systems admins do that for them.  OUCH!
  4. This last example is a prime example of Soylent Green.  I was recently called to assist with a production outage issue.  Tempdb was full but had not completely filled the drives.  People were unable to get work done.  To get things moving, I expanded tempdb (the log file) and then began querying to find what was going on.  Within minutes I had found multiple instances of the same query running that had been running for nearly 10hrs.  The query was causing significant spooling and should not have been this detrimental.  I killed the query in question and got a little more info on it.  In this case the developer decided to test against production.  He had taken a query that had been finishing in under five minutes, tweaked it, broke it and then brought down production.
    Some interesting figures besides the 10 hours to run and not complete is that it had read 2 billion records by the time it was killed and the largest table in the database is only 20 million records.  No table in this query exceeded one million records.

T-SQL Tuesday #028 – Jack of All Trades, Master of None?

Comments: 3 Comments
Published on: March 13, 2012

Another month and another opportunity to write about an interesting topic.  This month hosting TSQL Tuesday is Argenis Fernandez (Blog | Twitter).

This month, Argenis has invited us to talk about demons from our past.  Ok, not necessarily demons but at least share why you might be a Jack of All Trades or a Master of something or nothing.

Thinking about the topic, I thought of some very good stories.

Jack of All Trades

Back in the day, I worked in a one-man IT shop.  On any given day, my duties involved configuring SOHO routers and firewalls as well as higher end Cisco equipment.  I was also responsible for Active Directory, pc maintenance,printer repair, Exchange, domain registrations and all things SQL.

My least favorite duty was that of Janitorial Engineer.  It was amongst my duties to ensure the restrooms were stocked and that the toilets were free-flowing.  I can’t necessarily say that this skill helped advance my career.  I can’t say that it was even helpful at home.

I can say that this duty did help me make the decision to specialize more in SQL Server – though I was already headed in that direction.

Master, erm…

Like Argenis said in the TSQL Tuesday announcement, I don’t much consider myself an expert or master of anything.  I do think I am rather proficient and I do recognize many shortcomings within the vast technology, we love, called SQL Server.

I aligned myself with this technology because of the constant challenge and opportunity to learn.  I enjoy working with SQL Server.  I still do not find as much pleasure in plumbing as I do in SQL Server.

TSQL Tuesday #26 or #23 – Identity Crisis

Comments: 2 Comments
Published on: January 10, 2012

The first opportunity of this New Year to participate in TSQLTuesday, we have been invited by David Howard (blog) to take a second shot at a previous TSQLTuesday.

This second shot is giving me fits.  I have no clue if it is TSQLTuesday 26 or if it is TSQLTuesday 23.  Is it some sort of amoeba of both TSQLTuesdays combined?

While I try to figure that out, how about we look at some pictures of what the New Year might have looked like as it was rung in.  Let’s begin with Paris.

Oooh…Aaaaahhh…Those are quite impressive.

Next up on our tour is where a twin resides for a famous lady.  How did they ring in the New Year in New York City?

 

 

 

And now, let’s DBCC Timewarp to the other side of the world.  Here is what you might have seen if you were in Sydney Australia.

Ahhh.  Yes, that did the trick.  This little diversion sure gave me enough time to think about which TSQLTuesday this is.  Et merci a Stuart Ainsworth (Blog | Twitter).  Le Sujet qu’il a propose est celui laquelle dont je vais parler ce mois.  Dans le TSQLTuesday qu’il a organise, il nous a invite de parler a propos des JOINS.

J’ai completement rate cet occasion de parler a propos de “Joins” en participantes dans TSQLTuesday.  Voyez, TSQLTuesday 23 etait tenu pendant le premier semain au lieu de deuxieme semain ce fois ci.  Et, maintenant, je vais terminer cet article en Anglais.  I was going to write the whole thing in French, but will save that for another time.  I should have a second chance to do that someday.

This second chance, gives me the opportunity to finally talk about a topic that has been on my to-blog list for quite some time.  I hope this post will show some different ways of joining in TSQL.  They are certainly methods I had never considered – until it was required.

Business Requirement

I have some tables that I need query.  One of the tables has lookup information with a bitmask applied to the id field.  Another table references this table but the ids can be a direct match or an indirect match to the id of the lookup table.  For this case, they will always only differ by the value of 1 if the two values do not directly match.  No other match possibility is considered for this example.

Based on this need, the solution dictates some sort of bitwise math.  I have several examples of how a join could be written to accomplish this primary objective.  I am only going to show the possible ways of performing this.  In my environment these all yield the same results and the data is unique and large enough (4.2 million records).  I will compare performance of these different queries in a later post as I demonstrate a query tuning method to drive the query down from nine seconds to 100ms or less.  For the record, I would choose any of queries 5, 6, or 7 for this particular need based on plan cost and performance.

The JOINS

First up is the ANSI style INNER Join using addition in one of the conditions as well as an OR to fulfill the second portion of the business requirement.

PRINT 'Query 1 -- Join with Or and source+1'
------
SELECT TOP 1000
		PPV.RumorID,PPV.PersonRumorID,PPV.PersonID
		,US.Source,Us.SourceID, PPV.SourceID
	FROM	RumorView PPV
		INNER Join SourceType US
			ON (PPV.SourceID = US.SourceID
				Or PPV.SourceID = US.SourceID+1)

This is probably the easiest to understand and it performs well enough.  Until running into this business requirement, I hadn’t considered putting an OR in the JOIN conditions.  But it makes sense considering that an AND can be used there.

Next is a NON-ANSI style of JOIN.

PRINT 'Query 2 -- Non-Ansi Join with Or and source+1'
------
SELECT TOP 1000
		PPV.RumorID,PPV.PersonRumorID,PPV.PersonID
		,US.Source,Us.SourceID, PPV.SourceID
	FROM	RumorView PPV, SourceType US
	WHERE (PPV.SourceID = US.SourceID
				Or PPV.SourceID = US.SourceID+1)

Through 2008 R2, this works just as well as the ANSI JOIN already shown.  I haven’t tested in SQL 2012 but I do know that the NON-ANSI syntax of *= (for example) no longer works.  I am not a big fan of this style JOIN because it is far too easy to end up with a Cartesian product.

Another type of JOIN that I like is the use of APPLY.

PRINT 'Query 3 -- Cross Apply with Or and source+1'
------
SELECT TOP 1000
		PPV.RumorID,PPV.PersonRumorID,PPV.PersonID
		,US.Source,Us.SourceID, PPV.SourceID
	FROM	RumorView PPV
		Cross Apply SourceType US
	WHERE (PPV.SourceID = US.SourceID
				Or PPV.SourceID = US.SourceID+1)

This particular code segment is the equivalent of the first query shown.  This is the last in the set of using basic math and an OR in the JOIN conditions.  The remaining queries all rely on bitwise operations to perform the JOIN.  Again, until this particular need, I had never even considered using a bitwise operation in a JOIN.  First in this series is the NON-ANSI style JOIN.

PRINT 'Query 4 -- Non-Ansi with COALESCE and Bit compare'
------
SELECT TOP 1000
		PPV.RumorID,PPV.PersonRumorID,PPV.PersonID
		,US.Source,Us.SourceID, PPV.SourceID
	FROM	RumorView PPV, SourceType US
	WHERE (PPV.SourceID|1 = COALESCE(US.SourceID|1,Us.SourceID))

The big change here is in the where clause.  Notice the use of COALESCE and the first comparison value in that COALESCE.  This is called a BITWISE OR.  From MSDN: “The bits in the result are set to 1 if either or both bits (for the current bit being resolved) in the input expressions have a value of 1; if neither bit in the input expressions is 1, the bit in the result is set to 0.”

So I am comparing the bit values of 1 and the SourceID.  The SourceID from RumorView will create a match meeting the requirements put forth thanks in large part to the BIT OR operation being performed on both sides of the equality in the WHERE clause.  It is also worth mentioning that the COALESCE is completely unnecessary in this query but it I am leaving it as a pseudo reference point for the performance tuning article that will be based on these same queries.

Next on tap is the CROSS Apply version.

PRINT 'Query 5 -- Cross with Bit compare'
------
SELECT TOP 1000
		PPV.RumorID,PPV.PersonRumorID,PPV.PersonID
		,US.Source,Us.SourceID, PPV.SourceID
	FROM	RumorView PPV
		Cross Apply SourceType US
	WHERE (PPV.SourceID|1 = US.SourceID|1)

And the last two queries that the optimizer equate to the same query.

PRINT 'Query 6 -- Join with Bit compare'
------
SELECT TOP 1000
		PPV.RumorID,PPV.PersonRumorID,PPV.PersonID
		,US.Source,Us.SourceID, PPV.SourceID
	FROM	RumorView PPV
		INNER Join SourceType US
			ON (PPV.SourceID|1 = US.SourceID|1)
------
PRINT 'Query 7 -- Join with ISNULL and Bit compare'
------
SELECT TOP 1000
		PPV.RumorID,PPV.PersonRumorID,PPV.PersonID
		,US.Source,Us.SourceID, PPV.SourceID
	FROM	RumorView PPV
		INNER Join SourceType US
			ON (PPV.SourceID|1 = ISNULL(US.SourceID|1,Us.SourceID))

The query optimizer in this case is smart and eliminates the ISNULL.  These two queries use the same exact plan, have the same cost and the same execution statistics.  The version with COALESCE is considered more expensive and takes longer to run than these queries.  It is also important to note that the Cross Apply Join also produces the exact same plan as these two queries.

Conclusion

So there you have it.  Many different ways to write the JOIN for this little query.  Performance and results may vary.  It is good to have a few different ways of writing this particular JOIN.  During my testing, it was evident that various methods performed better under different circumstances (such as how the indexes were configured – which will be discussed in the follow-up article).

Notes: Names have been concealed to protect the innocent ;) .  Also, the fireworks images are all links to external sites.  I have no affiliation with those sites…disclaimer yada yada yada…I am not responsible for content on those sites but they can have the credit for the images.

T-SQL Tuesday #025 – Holiday Gifts

Comments: No Comments
Published on: December 13, 2011

Tis the Season

It is the season for TSQL Tuesday.  More importantly it is the season for giving and reflection.  And whether you celebrate Christmas or Chanukkah or Kawanzaa or TSQLTuesday because you believe or simply because of tradition, it is a good time to reflect and help somebody else.

Amidst the hustle and bustle, Allen White (Blog|Twitter) is hosting at least one party during this season.  And with all of our hustle and bustle, many of us will be attending at least one party this year (as evidenced by this post).

Allen’s party theme is an invitation to “Share Your Tricks.”  Well, in the spirit of the Holidays, I want to share some tricks and tips.  They can be my gifts to you during the holidays.  And maybe they can help you give to somebody else.

Tricks

ObjectProperty()

I find myself using this frequently in queries.  A popular use for myself is to use it to find the value of ‘IsMSShipped’.  But that is only one possible use for this function, there are many many more.  Check out MSDN to see more power!!

Here is a relatively meager example.

SELECT OBJECT_NAME(OBJECT_ID) AS TabName,create_date,type,type_desc
	FROM sys.objects
	WHERE OBJECTPROPERTY(OBJECT_ID,'isMSShipped') = 0
		AND OBJECTPROPERTY(OBJECT_ID,'isTable') = 1

ServerProperty()

I like to use this function regularly as well.  Sometimes it is handy to break out information about the server into a columnar set.  I can find useful information from Service Pack level to whether the Instance is Clustered or not.  Here is a quick sample.

SELECT 	SERVERPROPERTY('ProductVersion') AS ProductVersion
		,SERVERPROPERTY('ProductLevel') AS SPLevel
		,SERVERPROPERTY('Edition') AS Edition
		,SERVERPROPERTY('ResourceVersion') AS ResourceDBVersion
		,SERVERPROPERTY('InstanceName') AS InstanceName
		,SERVERPROPERTY('IsClustered') AS Clustering
		,SERVERPROPERTY('LicenseType') AS LicenseType
		,SERVERPROPERTY('NumLicenses') AS NumLicenses
		,SERVERPROPERTY('Collation') AS CollationLevel

Primes

Now for a little bit of fun stuff.  It was suggested on Twitter last night that I show a TSQL solution for generating prime numbers.  Thanks to Adam Mikolaj (Twitter) for this suggestion.  I am not going to explain it other than to say that this will help generate the prime numbers between 1 and 1000.

WITH Units ( nbr ) AS ( 
	    SELECT Number AS nbr
			FROM (VALUES (0),(1),(2), (3), (4), (5), (6), (7),(8), (9) )AS X(number))
, nums (Number) AS (    
	SELECT u3.nbr * 100 + u2.nbr * 10 + u1.nbr + 1 
      FROM Units u1, Units u2, Units u3 
     WHERE u3.nbr * 100 + u2.nbr * 10 + u1.nbr + 1 <= 1000)
 
SELECT 1 AS Primes
	UNION All
SELECT 2
	UNION All
SELECT n.Number AS Number
	FROM nums c,nums n
	WHERE c.Number < n.Number
        AND c.Number <> n.Number
        And c.Number between 2 and 35
    GROUP BY n.Number
    HAVING MIN(n.Number % c.Number) > 0
    ORDER BY 1

Tips

No Changes

Don’t plan any rollouts during the holiday season.  Try to have a production freeze implemented.  For many companies this is a busy season.  To further the point, many employees like to take a vacation during this time period.  With a lighter staff, key personal may be out of touch should an emergency occur due to a rollout.  So minimize your stress and minimize the chances of an emergency and don’t do any changes (excepting emergency fixes) during this time of year.  It is a good time to catch up on other items on your to-do list.

Take a Time-Out

Don’t be too busy for the important things in life.  Some of these things include self and family.  Don’t get yourself going too fast for too long that you miss out on the good stuff.  Don’t be soooo busy that you have no time for yourself to relax.  Recharge your batteries by taking a time-out.  Yes!  Give yourself a timeout.  Spend time with your spouse and kids.  If you don’t have a spouse or kids, then spend some time with a friend or #sqlfriend or #sqlfamily.

Help somebody in need

This is closely intertwined with the previous topic.  Give something of yourself to help somebody else.  A little joke, a little smile, a little service will go a long way (just as much for you as the other person).  If it means donating a toy, some food, some cash – do it.

page 1 of 4»
Calendar
May 2013
M T W T F S S
« Apr    
 12345
6789101112
13141516171819
20212223242526
2728293031  
Content
Now Reading

Now Reading

Planned books:

Current books:

  • SQL Server 2012 T-SQL Recipes: A Problem-Solution Approach

    SQL Server 2012 T-SQL Recipes: A Problem-Solution Approach by Jason Brimhall

Recent books:

View full Library

Categories

Categories

SQLHelp

SQLHelp


Welcome , today is Tuesday, May 21, 2013