Bluedog67

Random Thoughts About SQL Server and My Dog Blue

About Bluedog67

My name is Stephen Horne. I am a well-trained owner of an Australian Cattle Dog named Blue. I also develop software mainly using SQL Server, ASP.NET, and C#. I love data, databases, T-SQL, and turning raw data into actionable information. My plan for this blog is to write about SQL Server and sometimes my dog Blue. Please contact me at stephen at bluedog67 dot com. Follow me on Twitter at bluedog67.

SQL Server Logon Trigger: Restricting Access By IP To Your SQL Server Instance

On Twitter today @anonythemouse tweeted the following "any good #sql links for login triggers to block hostnames from logging in? #sqlhelp". Looked like an interesting question - I thought I would give the question a try and see if I could come up with an answer.

Following scripts are based upon researching the following links from the SQL Server 2008 Books Online (BOL):

Below are two possible options to restrict access by IP to SQL Server (developed and tested on SQL Server 2008 R2 Ent Eval Ed CTP Nov only):

Blacklist Option (Clients that are blocked):

CREATE TRIGGER tr_logon_hostname_blacklist
ON ALL SERVER WITH EXECUTE AS 'sa'
FOR LOGON
AS
BEGIN
  DECLARE @ClientHost nvarchar(max);
  SELECT @ClientHost = EVENTDATA().value('(/EVENT_INSTANCE/ClientHost)[1]','nvarchar(max)');
  -- ClientHost gives IP except if the connecting to SQL Server from instance machine.
  -- Do NOT put '<local machine>' in this otherwise you will block client access from instance machine.
  IF @ClientHost IN ('10.0.2.53'
                    ,'10.0.2.54'
                    ,'10.0.2.55'
                    ,'10.0.2.56')
    ROLLBACK;
END;

Whitelist Option (Clients that are allowed access):

CREATE TRIGGER tr_logon_hostname_whitelist
ON ALL SERVER WITH EXECUTE AS 'sa'
FOR LOGON
AS
BEGIN
  DECLARE @ClientHost nvarchar(max);
  SELECT @ClientHost = EVENTDATA().value('(/EVENT_INSTANCE/ClientHost)[1]','nvarchar(max)');
  -- ClientHost gives IP except if the connecting to SQL Server from instance machine.
  IF @ClientHost NOT IN ('<local machine>' -- client is on the instance box (Do NOT change or remove!)
                        ,'10.0.2.54'
                        ,'10.0.2.55'
                        ,'10.0.2.56')
    ROLLBACK;
END;

Please note that the '<local machine>' string should NOT be modified or removed from the Whitelist trigger - you want to continue to be able to connect to your SQL Server instance from the instance box itself correct!?! Before running these queries please make sure that you have an open connection in another Query window in SSMS with the following script to drop the Logon Trigger just in case you make a mistake. You do not want to break your instance!

DROP TRIGGER tr_logon_hostname_blacklist
ON ALL SERVER;

DROP TRIGGER tr_logon_hostname_whitelist
ON ALL SERVER;

Fair Warning: Please note that Logon Triggers can be potentially instance breaking things. Especially if they include a ROLLBACK as the two scripts above include. Strongly recommend testing retesting and reretesting scripts fully on a Test box before even considering getting near a Production box. Make sure you have a separate open query window in SSMS or preferrably an already open SQLCMD session with the DROP TRIGGER script at the ready just in case you break things.

Please share any feedback or ideas that you may have. Thanks!

Categories: SQL Server
Permalink | Comments (1) | Post RSSRSS comment feed

Core SQL Server DBA Skills and My Home SQL Server DBA Learning Lab

I recently completed 14 days of SQL Server MCITP Bootcamp in both Admin and Dev in Atlanta. The course went well. I learned a great deal. I plan to write more about the experience soon. The most important thing that I learned was how much there is to learn in the SQL Server DBA world (and how little I really knew about that side of the house). Being an experienced SQL Server developer, the Dev coursework was very familiar (except some of the XML-related stuff!). Going into the training, the Dev coursework and certification was my focus - the Admin stuff was a nice-to-have but wasn't my real interest. But I found that learning more about the DBA world opened my eyes. My interest in possibly doing that type of work went from effectively zero to now where it is a real consideration.

I knew when I got back home from the training that if I were going to pursue this idea of possibly doing SQL Server DBA work that I needed to set up a home lab to learn more. I have started this process and had some success (probably small to an experienced DBA but not small to me!). On my Windows 7 system using Virtual Box (thanks for recommendation @cfrandall and @venzann), I set up a simple network containing 3 VMs (all Windows Server 2008 R2) - one VM the Domain Controller & DNS box, the second a SQL Server 2008 R2 box, and the third another SQL Server 2008 R2 box (for testing replication, mirroring, linked servers, etc). I got things working - likely not optimally - but working. I will likely start over from and scratch and really take my time stepping through the process to make sure I understand it and try and get things more correct. I am in the process of writing this up and will share shortly.

I have some ideas about what to work on and learn using my home SQL Server DBA "Learning Lab", including things like backup and recovery, rebuilding indexes, understanding the various log files, monitoring, the SQL Agent, replication, mirroring, and clustering. I wanted to ask the SQL Server DBA community for some specific ideas about what the "Core SQL Server DBA Skills" are. I plan to work on this skills using my home lab. I also plan on sharing these ideas and what I have learned via this blog.

Thanks in advance for your ideas about "Core SQL Server DBA Skills" and my home SQL Server DBA "Learning Lab". Please share ideas, recommendations, links, etc that might help me or other readers to learn more about becoming a SQL Server DBA. Any useful criticism or suggestions or other feedback would be greatly appreciated. Thanks!

Permalink | Comments (4) | Post RSSRSS comment feed

Cell Phone Frustration and Finding the Solution

Was a bit disappointed a few weeks ago when Apple announced the iPad. Was a bit underwhelmed. I have an iMac that is my primary email, web surfing, twitter, and hulu.com machine. I also have an iPod Touch (or an iTouchMyself as a friend calls it). So I am somewhat of a Mac fan (I am also a huge Microsoft and Windows fan - my work is on Windows machines, plus I think Windows 7 is great). I was hoping to hear from Apple that they were opening up the iPhone to different carriers (in particular, Verizon Wireless) but that didn't happen. So I am stuck with my Verizon cheapo phone and my iTouchMyself on WiFi which works for me.

Have had the cheapest phone that Verizon sold for a couple of years. My contract was up for renewal. Verizon reminded me almost daily via emails and snail mailings that I should renew my contract and get a new phone for free or really cheap. My old phone could take pictures but I couldn't transfer them to my Mac/PC (no USB port). So I went into the local Verizon store to ask about cheapo phones with cameras and USB port. I explicitly asked and re-asked if the camera phone supported downloading pictures to my PC via USB cable - the salesguy repeatedly answered "yes". Of course they tried to upsale me, but I stuck to my guns and bought a LG VX5500 which with the contract renewal rebate I will get for free. The LG VX5500 is a no-frills phone with a 1 mexapixel camera with a mini-USB port. They tried to also sell me a USB cable but it was like $20. I declined. I went on Amazon and bought a set that included USB cable, wall charger, car charger, and protective case for $5 including shipping (isn't Amazon just grand?).

Then the problems started. And the frustration level started to rise. I could connect the LG VX5500 to my Mac and had visibility of the phone through the System Profiler utility. But it wasn't accessible through Finder. I mistakenly thought that it would behave like an SD card and allow me to download photos. Definite frustration. Did research on web. On LG's site. On Verizon's site. On the plethora of cell phone sites. Vented at friends. Contacted customer service. Nothing helped solve problem. Made decision to return phone, go back to old phone, cancel new contract, and go back to month to month.

Then I took a chill pill. I realized that I wasn't the only person experiencing this frustration. A phone with a mini-USB port. A phone that I can see on my Mac in System Profiler. There are some smart people in this world. There has got to be a piece of software out there that solves this problem. Fairly quickly I found a solution: DataPilot. I bought the Mac version, downloaded and installed the software, downloaded and installed the update, and tested. Works great! Can easily move photos from phone to Mac. Slow and photo quality is not great but I am happy!

I am probably violating Verizon's agreement is some way... whatever. Verizon's salesperson shouldn't have told me that I could download photos to my PC/Mac. I really like Verizon's service. I really like Verizon's customer service - usually. If you guys get the iPhone, I am there! I recommend Verizon Wireless to anyone.

Permalink | Comments (0) | Post RSSRSS comment feed

What Three Events Brought You Here

Recently Paul Randal (Blog|Twitter) blogged "What three events brought you here?" He tagged other SQL Server professionals to share the same information. Several others did including 2 people that really inspired me to write my own blog post - Ted Krueger (Blog|Twitter) and Jeremiah Peschka (Blog|Twitter).

This is likely to get long and pretty heavy. Sorry in advance. 8)

An Accidental Life. An Incidental Life? A Miraculous Life?


Consider some facts about the recent tragedy of the earthquake in Haiti. The earthquake lasted seven seconds. Seven seconds of destruction, devastation, and death. If a person near Port-Au-Prince was inside a building during these seven seconds they are most likely one of three things: dead, hurt, or very very lucky. If a person was outside during these seven seconds odds are that they are alive. It is difficult for me to wrap my head around how arbitrary this seems - Inside equals dead or hurt. Outside equals alive. The accidental nature of life makes it seem both incidental and miraculous. An individual human being is a tiny insignificant speck of dust in both time and space compared to the universe while at the same time during his or her short existence is probably the object in the known universe with the lowest entropy/least amount of chaos (conversely with the greatest order or the most complex system). How is this seeming paradox possible? Is everything already planned out? Or is everything just pure random chance? Does it really matter? I am not a big fan of the idea of destiny or fate or things being preordained. I prefer the idea of free will and that I have some degree of control (even if slight) over my future. Am I kidding myself?

The Haiti tragedy also reminded me of an episode of Charlie Rose with Auschwitz survivor and author Elie Wiesel (I attempted to quickly find exact episode but haven't yet). In the interview Wiesel talked about the tragedy of the Nazi Holocaust and the millions of Jews killed. The loss of all that humanity is a great sadness. That it was caused by man's evil makes it worse. Wiesel talked about an added dimension that sometimes gets forgotten in the tragedy - the extraordinary loss of human potential. Within the millions that were slaughtered or their descendants was there another Mozart, Shakespeare, or Einstein? We will never know. In the Haiti tragedy it is unclear how many tens of thousands of people perished (possible as many as or more than 200,000). This loss of humanity combined with the unknowable loss of human potential should give pause to all and reason for self-reflection. As should all loss of life whether by accident, violence, or disease. Whether if just one person or millions.

Event #1 - Actually a Series of Events: Some Family History


I am named after my great grandfather, Stephen Horn. At some point an "e" was added to our surname. He was in his 20s when the Civil War broke out. He and his brothers lived in Virginia. The old homeplace was less than 2 miles from the Kentucky border. They joined the Confederate Army. If they had lived just a few miles to the north or west they would have likely joined the Union army. They didn't own any slaves. They were just poor dirt farmers and joining up to fight in the war was just what you did. Thankfully my family wasn't one of those where one brother joined the Union army while another joined the Confederate army. That happened with great frequency with families on both sides of the border between the Union and Confederate states. My great grandfather joined the Virginia 51st Infantry Regiment and fought in several battles throughout West Virginia and Virginia. The Virginia 51st fought directly alongside the cadets of VMI (Virginia Military Institute) at the Battle of New Market. It is believed that my great grandfather Stephen was at this battle. Shortly after the Battle of New Market, my great grandfather was captured by the Union army and sent to a Prisoner of War camp on the Delaware peninsula. He spent the last year and a half of the Civil War in a POW camp. When Lee surrendered at Appomattox and the war ended, Stephen was released and he walked back across the state of Virginia to his home. He returned to farming and also became a teacher. He started a family and had a son when he was almost 50, my grandfather.

For my grandfather a hunting rifle was a constant companion. My father tells stories that he was a very good shot and very good in the woods and as a tracker - a real-life Daniel Boone. When World War I started my grandfather joined the US Army and became a sniper. He fought in France in the trenches against the Germans. My grandfather was supposed to be pretty nasty and fearless. I don't know if the stories my father used to tell me are true (probably half-truths) but think Brad Pitt in Legends of the Fall. My father did say that my grandfather respected the Germans - the French? Not so much. My grandfather survived the war and came back home to Virginia and started a family. My father was born in 1928 when my grandfather was in his 40s. My father turned seventeen in 1945 and missed World War II. What if he had been born a year or two earlier like some of my mother's brothers one who flew over 100 B-25 missions out of Burma (the other two in training ready to be shipped out to South Pacific for invasion of Japan)? My father eventually spent time during the Korean War in the Army stateside. I was born when my father was almost 40.

It all just seems so accidental. How did my great grandfather survive numerous battles? How did he survive POW camp? How did my grandfather survive France? How did my father luck out and completely miss World War II? Why did he luck out and not get shipped to Korea? Do all these seemingly random events "culminate" in "Me"? I don't know about my fellow SQL Server professionals but compared to previous generations I sometimes feel like a big wuss. I sit on my ass, stare at a computer screen and play with a keyboard and mouse. I don't think I am the culmination of anything. Fortunately I only feel this way sometimes. But it still all seems very accidental. And not the least bit miraculous.

Event #2: Back To Work and Adding Value Day One


I have written about the following before here and here. My mother died of breast cancer in 2004. My father started to have problems - dementia probably Alzheimer's. In early summer 2004 I went back home to Virginia to help him. I didn't work for 3.5 years. When I did go back to work as an ASP.NET developer (ha!) I found that the one area that I could immediately add value to my new employer was on the SQL Server side. Pretty much everything else I knew about application development had changed. But SQL Server, SQL, Transact-SQL, tables, rows, columns, SELECTs, INSERTs, UPDATEs, DELETEs, Stored Procedures, and Views - pretty much was the same. I wasn't completely worthless. It was like riding a bike. This core stuff was the same 10 years ago and will be the same 10, 20, 50 years from now (sorry NoSQL, Value Pair, Object DB people - while your ideas have merit - the Relational Database Management System and hopefully SQL Server will be the data storage and retrieval solution for today, tomorrow, and the foreseeable future). I am deeply cynical about the software development industry's never-ending chase for the silver bullet - whether it be a new language, a new API, a new methodology, a new whatever. There. Is. No. Silver. Bullet. I don't want to stop new ideas and innovation. I think ASP.NET MVC is brilliant. I think ORMs like LINQ to SQL are too. jQuery is fantastic. But why does there have to be 18 billion ways to do the same thing? And why create yet another new and "better" way to do the same thing? I believe at some point businesses and developers are going to say enough is enough - innovation for innovation's sake is not what we value most. What we value most is delivering solutions to our customers - we want our knowledge/skills/experience to persist for longer - we want technologies to become more well-baked rather than be replaced by the new. For me personally, narrowing my focus to SQL Server development is how I am making my knowledge/skills/experience "persist". The fact that I could come back after 3.5 years and add value on day one to my new employer in the SQL Server realm, convinces me of this. I won't stop learning (heck, I start a 15 day SQL Server MCITP Bootcamp admin & dev in a couple of days) but what I know today about RDBMSes and SQL will apply tomorrow, 5 years from now, 25 years from now. That fact that I love SQL Server and Transact-SQL helped me make the decision too!

Event #3: Getting Involved in SQL Server Community


I lost my ASP.NET/C# job at the end of September. I have been out of work since then. I started blogging and Twittering to possibly help with my job search - it has turned into much more. In the short time that I have been involved in the SQL Server community I have been blown away by the kindness, caring, sharing, consideration, and friendships that surround and make up the community. The screenshot below contains a short Twitter discussion that spoke to me - maybe at the time I was feeling alone in a room with no one else other than my dog Blue. People like Andy Leonard (Blog|Twitter) are inspiring, especially with posts like this. The SQL Server community has convinced me to focus my career on SQL Server development.

Thanks for reading my novel. Hopefully didn't bore you completely! And as I said before I start a Bootcamp in a couple of days. Twitter and blogging will be curtailed. Wish me luck!


Permalink | Comments (3) | Post RSSRSS comment feed

Yet Another FizzBuzz Solution: This Time Using T-SQL CTE (Common Table Expression) and spt_values

Insomnia triumphs over all. Sleep schedule is whacked. Saw this tweet from Jeff Atwood (Blog|Twitter) that referenced his post entitled "Why Can't Programmers.. Program?" located here. I thought I would accept the FizzBuzz challenge and try and program something in Transact-SQL.

Following is my attempt developed and tested on SQL Server 2008 Dev Ed. This solution uses a recursive CTE (Common Table Expression). You can find tons more about recursive CTEs here.

Please note that I know that CASE statement logic could be simplified - you can rely on WHEN expression order for evaluating which THEN clause is used for a given row. My preference is that with IF ELSE ELSE structures that the options be mutually exclusive.

;WITH cteCounter(number)
AS
(
SELECT 1 number
UNION ALL
SELECT (cteCounter.number + 1)  number
FROM cteCounter
WHERE (cteCounter.number + 1) <= 100
)
-- Switch out the SELECT clauses to see basic recursive CTE behavior and possible FizzBuzz solution.
-- SELECT cteCounter.number
SELECT CASE WHEN ((cteCounter.number % 3 = 0) AND (cteCounter.number % 5 = 0)) THEN 'FizzBuzz'
            WHEN ((cteCounter.number % 3 = 0) AND (cteCounter.number % 5 <> 0)) THEN 'Fizz'
            WHEN ((cteCounter.number % 3 <> 0) AND (cteCounter.number % 5 = 0)) THEN 'Buzz'
            ELSE CAST(cteCounter.number AS varchar(8)) END number
FROM cteCounter
ORDER BY cteCounter.number;

Dave Ballantyne (Blog|Twitter) publicly shamed me on Twitter for using a CTE to generate a range of number - thanks Dave <grin>! He rightly suggested using master.dbo.spt_values instead. I swore I would never use a CTE to implement a range of numbers! Following is the updated code.

SELECT CASE WHEN ((V.number % 3 = 0) AND (V.number % 5 = 0)) THEN 'FizzBuzz'
            WHEN ((V.number % 3 = 0) AND (V.number % 5 <> 0)) THEN 'Fizz'
            WHEN ((V.number % 3 <> 0) AND (V.number % 5 = 0)) THEN 'Buzz'
            ELSE CAST(V.number AS varchar(8)) END number
FROM master.dbo.spt_values V
WHERE V.type = 'P'
AND V.number between 1 and 100
ORDER BY V.number;

Categories: SQL Server
Permalink | Comments (1) | Post RSSRSS comment feed

Fiscal Week Scalar Value Function

This post is a quick and dirty solution to a question that came up on Twitter on the #sqlhelp hash tag from Barnaby Self (Blog|Twitter). @B4PJS wrote up a related blog post here. Please note that this possible solution has some potentially significant limitations: First, the possible solution assumes that the Fiscal Year begins on same date every year (some companies like Cisco have 52 week Fiscal Years). Second, the solution assumes that the Fiscal Year start date is Day 1 of Week 1 regardless of the day of the week. In other words, this solution is NOT affected by the SET DATEFIRST command. If SET DATEFIRST needs to impact Fiscal Week determination, this solution will need to be modified. Third, using scalar-valued UDFs (User Defined Functions) can cause significant performance problems if used in WHERE or GROUP BY clauses (nod to @DaveBally) - alternate solutions may yield better results. A possible better performing solution might be using the UDF described below in conjunction with either a persisted computed column or an indexed view.

-- Developed and tested only on SQL Server 2008 Dev Ed.
-- Assumes Fiscal Year begins July 1st.
-- Assumes day 1 of week 1 is July 1st regardless of day of week.
CREATE FUNCTION dbo.udf_GetFiscalYearWeek(@InputDate date)
RETURNS int
AS
BEGIN
  DECLARE @Result int
  
  DECLARE @FiscalYear int;
  -- Following logic will need to change if Fiscal Year start date different from YYYY0701.
  IF (MONTH(@InputDate) >= 7)
  BEGIN
    SET @FiscalYear = YEAR(@InputDate) + 1;
  END
  ELSE
  BEGIN
    SET @FiscalYear = YEAR(@InputDate);
  END

  DECLARE @FiscalYearStartDate date;
  -- Change '0701' as required.
  SET @FiscalYearStartDate = CAST((CAST((@FiscalYear - 1) AS varchar(4)) + '0701') AS DATE);

  DECLARE @FiscalYearWeek int;
  SET @FiscalYearWeek = DATEDIFF(d, @FiscalYearStartDate, @InputDate) / 7 + 1;

  SET @Result = @FiscalYearWeek;
  
  RETURN @Result
END
GO

Thanks for reading. Any feedback you have will be appreciated.

Categories: SQL Server
Permalink | Comments (7) | Post RSSRSS comment feed

T-SQL Tuesday #002: Challenge: Create Unbeatable Tic-Tac-Toe Game in T-SQL

This is my post for Adam Machanic's T-SQL Tuesday #002. Thanks for reading! Feedback is appreciated.

Can you create a game in SQL Server?

A few years back at the Austin Game Developers Conference in Texas I got to meet and talk with the CEO and the lead database developer of the Icelandic company (CCP Games) that created EVE Online - a scifi MMORPG. Some nice and very sharp guys. They had been eCommerce web/database developers plus gamers. They looked at MMORPGs and said to themselves, "these are just huge/high transaction database applications - we can do this!" And that is what they did. They designed and built the database in SQL Server and wrote tons of stored procedures. They made it so you could "play" the game by calling the stored procedures. And then they got a few artists and a couple of programmers to build a beautiful front end for their database application <grin>. The rest is history - EVE Online exploded in popularity and the company became very successful. They proved you can write a game in SQL Server.

Now back to the original question: Can you create a game in SQL Server?

That is The Challenge: Write a game in SQL Server. Not just any game but an Unbeatable Tic-Tac-Toe Game in SQL Server using Transact-SQL.

I have done this in C, C++ and Visual Basic, but never in Transact-SQL. It should be possible! I am accepting this challenge too!

Starting out there is a problem with SQL Server in that the primary UI for manipulating the database and writing T-SQL (SSMS) don't really support the requirements for a computer game.

Following is a basic description (highly over-simplified) of the steps of a computer game:

Infinite Loop (aka Game Loop) that does following over and over and over:
  • User Input: Polls for user input (keyboard, mouse, joystick). User moves, fires gun, buys sword, saves girl, etc.
  • AI Actions: Makes "choices" using some kind of AI for non-player characters (NPCs) - movement, firing gun, etc.
  • Resolve Game World: Determines results of player and AI actions (collisions, hits/misses, increase score, affect of "gravity", decrease health of player/NPCs, etc).
  • Draw Game World: Computer redraws world and presents to player. Likely includes music/sounds.
SSMS and the SQL Server database engine support the required functionality of a computer game when examined individually but not when mashed together. SSMS doesn't support the polling/acceptance of user input when running a T-SQL script - so no Game Loop combined with the other 4 steps of the Game Loop in SSMS. There is a workaround if you want to "play" your game within SSMS - you as the user of SSMS can be responsible for the Game Loop and make the calls for User Input, AI Actions, Resolve Game World, and Draw Game World.

Let's get to the rules!
  1. Follows standard Tic-Tac-Toe rules. X goes first.
  2. Computer can either be Xs or Os. Set when you start new game.
  3. Game continues until one of the following occurs:
    • Player or computer wins.
    • No more open slots and no winner - this is a tie.
    • If you wish, you can short-circuit game and declare a tie if win is not possible. [Bonus points if you implement this.]
  4. Computer must always win if it can.
  5. Computer must tie if win not possible.
  6. Computer must never lose.
  7. Game "state" must be stored in the database (table, temporary table, TVP, variable, whatever).
  8. Input validation, in particular cheating prevention, must be done in the T-SQL.
  9. Determination of game board status must be down in database/T-SQL (that is, win state, tie state, game over state, etc.)
  10. Determination of computer's next move must be done in database/T-SQL.
If you wish you can implement the Game Looping, User Polling, World Redraw functionality in Powershell or in a .NET application or something similar but the core of the game must be a T-SQL game.

Post your setup and sample gameplay code here or on your own blog and a link here. Would love to see some collaboration going on!

Personal Aside: As I said before, I am accepting this challenge too. Following are some quick and dirty ideas that I might test out. I am not sure if the following ideas are a reasonable start of a solution or a dead end (likely a dead end!). T-SQL doesn't support arrays. I am not sure how I will represent the Tic-Tac-Toe board with nine slots (3 X 3). Maybe as 9 separate variables each representing a different slot on the board or maybe a 3 column 3 rows temporary table? But I think maybe a series of stored procedures to "play" a game. Maybe something like the following (somewhat mapped to the steps of the Game Loop)?
  • usp_StartGame(@ComputerPlays char(1), @GameID int OUTPUT) - This stored procedure starts a new game. The @ComputerPlays input is either 'X' or 'O' to set what the computer is playing. The OUTPUT parameter @GameID returns a new unique value representing the identifier for the game.
  • usp_ShowStatus(@GameID int) - This shows current state of the @GameID game. Redraws board possibly as formatted ASCII. Shows who is playing Xs and Os. Shows who's turn it is. Shows if Win/Loss/Tie/Game Over.
  • usp_PlayerTurn(@GameID int, @Position int) - Represents player move. @Position would represent one of the nine positions on the board. If the player attempted to cheat this would be caught here.
  • usp_ComputerTurn(@GameID int) - Represents computer move. This represents the meat of the coding. How do you code this so that the computer never loses. Hmmm. There are several ways to do this - recursively, brute force, data-driven, others? After the choice is made, this procedure possibly calls usp_ShowStatus to display board back to player showing computer's choice and results.
Please let me know if I need to add or clarify anything. Please let me know if you have any ideas or other feedback. Thanks for reading! Looking forward to seeing people's solutions and ideas!

Categories: SQL Server
Permalink | Comments (8) | Post RSSRSS comment feed

SQL Server Books Online As PDF Files

I would love to see Microsoft come out with the SQL Server Books Online (SQL BOL) as PDF files. The main reason is eBook readers like Amazon's Kindle. It would be great to have SQL BOL on the Kindle.

SQL BOL has been an excellent learning and reference tool for many years. I expect that SQL BOL will be continue to be my primary learning and reference resource in my career as a SQL Server professional. You can get access to SQL BOL several ways, including as part of a SQL Server installation, as part of the locally installed MSDN Library, as a separate downloadable installation file, and via the online MSDN Library or through TechNet both on Microsoft's website. But SQL BOL currently isn't available via PDF files. Arguably you could turn the HTML pages of SQL BOL into PDF files - but that would be a huge task. So getting SQL BOL onto your Kindle is a big challenge.

Let's contrast Microsoft's SQL Server documentation with Oracle's documentation. You can find Oracle's documentation here as both HTML and PDF files. You can download the Oracle PDF files either individually or as zipped collections. The Oracle PDF files play well with the Amazon Kindle. I'd love it if SQL BOL did as well!

I have created on the Microsoft SQL Server Connect website the following suggestion to make the SQL BOL available as PDF files. If you agree please up-vote on the Microsoft Connect website. If you have any ideas or feedback please share in the comments below. Thanks for reading!

Categories: SQL Server
Permalink | Comments (3) | Post RSSRSS comment feed

Strange INSTEAD OF Trigger Side Affect

In previous post here I described a seemingly strange side affect of an INSTEAD OF trigger. Within the INSTEAD OF trigger I didn't update any records - I just returned an error. No rows are actually being affected. However, SSMS (SQL Server Management Studio) shows records *are* being affected. The count is equal to the count that would have been affected had the INSTEAD OF trigger not existed or if it had been disabled.

I wanted to see if this was just SSMS behavior. I created a simple C# Console Application project in Visual Studio 2008 against a SQL Server 2008 Dev Ed database.

Following is database setup code.

CREATE DATABASE MyDB;
GO

USE MyDB
GO

CREATE TABLE dbo.MyTable
(
ID int IDENTITY(1, 1) NOT NULL PRIMARY KEY,
MyColumn varchar(50) NOT NULL
);
GO

-- sample data
INSERT INTO dbo.MyTable (MyColumn) VALUES ('One');
INSERT INTO dbo.MyTable (MyColumn) VALUES ('Two');
INSERT INTO dbo.MyTable (MyColumn) VALUES ('Three');
INSERT INTO dbo.MyTable (MyColumn) VALUES ('Four');
INSERT INTO dbo.MyTable (MyColumn) VALUES ('Five');
INSERT INTO dbo.MyTable (MyColumn) VALUES ('Six');
INSERT INTO dbo.MyTable (MyColumn) VALUES ('Seven');
INSERT INTO dbo.MyTable (MyColumn) VALUES ('Eight');
INSERT INTO dbo.MyTable (MyColumn) VALUES ('Nine');
INSERT INTO dbo.MyTable (MyColumn) VALUES ('Ten');
GO

CREATE TRIGGER dbo.tr_MyTable_StopDataMods
ON dbo.MyTable
INSTEAD OF INSERT, DELETE, UPDATE
AS
BEGIN
  
  SET NOCOUNT ON;
  
  RAISERROR(
  N'The data in table %s is static. All data modification operations are blocked. Disable the trigger %s to modify table data.', -- Message text.
  16, -- Severity
  1, -- State,
  'dbo.MyTable', -- first argument (table name)
  'dbo.tr_MyTable_StopDataMods' -- second argument (trigger name)
  );
  
END
GO

Test with the following code.

UPDATE MyTable SET MyColumn = 'X';

Should see expected error message and the "10 row(s) affected" message. If you do a SELECT * FROM MyTable; you should see that the data was not modified.

Now let's switch gears from T-SQL over to C# and .NET. Following is complete code for the application. You may have to modify the Connection String as required. The database UPDATE operation is the same as above that you executed in SSMS that resulted in a error, no rows being updated, but SSMS showing "10 row(s) affected."

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      string strConnection = "Data Source=localhost;Initial Catalog=MyDB;Integrated Security=True";
      using (SqlConnection cn = new SqlConnection(strConnection))
      {
        cn.Open();

        using (SqlCommand cmd = new SqlCommand())
        {
          cmd.Connection = cn;
          cmd.CommandType = CommandType.Text;
          cmd.CommandText = "UPDATE MyTable SET MyColumn = 'X';";
          int RowsAffected = cmd.ExecuteNonQuery();
          Console.WriteLine("Rows Affected: " + RowsAffected.ToString());
        }
      }

      string x = Console.ReadLine(); // Keep Console from closing
    }
  }
}

Run the application in debug mode. Should error out on the cmd.ExecuteNonQuery command with an unhandled SqlException with the expected error message: "The data in table dbo.MyTable is static. All data modification operations are blocked. Disable the trigger dbo.tr_MyTable_StopDataMods to modify table data." See the screenshot below. In the locals window take a look at cmd.Non-Public members._rowsAffected. Shows a value of 10 (ten). Now isn't that interesting. This side affect associated with the INSTEAD OF trigger that updates zero rows is also seen when making calls through other APIs. To confirm this behavior you could possibly do additional testing using other INSERT, UPDATE, and DELETE commands.



Any feedback or ideas that you have will be appreciated. Thanks!

Update:

I modified the INSTEAD OF trigger and removed the RAISERROR command. Now the INSTEAD OF trigger just has the SET NOCOUNT ON command.

ALTER TRIGGER dbo.tr_MyTable_StopDataMods
ON dbo.MyTable
INSTEAD OF INSERT, DELETE, UPDATE
AS
BEGIN

SET NOCOUNT ON;

END
GO

When you rerun the C# Console app, it runs successfully without error and shows the following.



If you again look at the underlying data in the table - it is unaffected by the UPDATE statement. No rows were actually updated.

To further confirm disable the trigger and rerun the C# Console app. It should again show Rows Affected: 10. This time when you query the data in the table - the data will have been updated.

According to the MSDN documentation located here, the System.Data.SqlClient.SqlCommand.ExecuteNonQuery() method "Executes a Transact-SQL statement against the connection and returns the number of rows affected." The documentation also states "For UPDATE, INSERT, and DELETE statements, the return value is the number of rows affected by the command. When a trigger exists on a table being inserted or updated, the return value includes the number of rows affected by both the insert or update operation and the number of rows affected by the trigger or triggers. For all other types of statements, the return value is -1. If a rollback occurs, the return value is also -1." The documentation is not very helpful.

It is clear that when INSERT, UPDATE, or DELETE statements are executed against a table or view with an INSTEAD OF trigger, regardless of what code is actually executed within the INSTEAD OF trigger, the following is occuring: A SELECT statement is being run against the table or view to determine the number of rows that would have been affected had the trigger not existed. This value is being passed back to the process/application requesting the INSERT, UPDATE, or DELETE operation as the count of rows being affected even though the actual number of rows affected could be zero, the same, or something completely different.

Again, thanks for reading and any feedback is appreciated. Thanks!

Categories: SQL Server
Permalink | Comments (7) | Post RSSRSS comment feed

Blocking Data Modifications Version 2

This is a variation of the code shared in the this post here. Mladen Prajdic (Blog|Twitter) suggested I use an INSTEAD OF trigger and eliminate the ROLLBACK. The following code shows this idea. Thanks for the suggestion Mladen!

There appears to be an annoying side affect associated with INSTEAD OF triggers. When performing INSERT, UPDATE, and DELETE operations against MyTable when the INSTEAD OF trigger is enable, you see the standard "X row(s) affected" messages if NOCOUNT is set to OFF (default). This message is showing counts greater than zero (0) even when no records are affected. It is showing the counts that would have been affected if the trigger didn't exist or were disabled. Interesting behavior. Might possibly need to expand the error message in some way to communicate this side affect so that users attempting data modifications don't think their operations succeeded even though they get the "data modifications blocked" error message.

Please share any ideas or feedback that you may have. Thanks!

CREATE TRIGGER dbo.tr_MyTable_StopDataMods
ON dbo.MyTable
INSTEAD OF INSERT, DELETE, UPDATE
AS
BEGIN

SET NOCOUNT ON;

RAISERROR(
N'The data in table %s is static. All data modification operations are blocked. Disable the trigger %s to modify table data.', -- Message text.
16, -- Severity
1, -- State,
'dbo.MyTable', -- first argument (table name)
'dbo.tr_MyTable_StopDataMods' -- second argument (trigger name)
);

END
GO

-- Code to temporarily disable trigger
ALTER TABLE dbo.MyTable DISABLE TRIGGER tr_MyTable_StopDataMods;
GO

-- Code to reenable trigger
ALTER TABLE dbo.MyTable ENABLE TRIGGER tr_MyTable_StopDataMods;
GO

Code tested only on SQL Server 2008 Dev Ed.

Permalink | Comments (4) | Post RSSRSS comment feed