My article, ANSI Padding, Trailing Whitespace, and Variable Length Character Columns, is featured in today’s SQLServerCentral newsletter. Check it out!
Friday, July 16, 2010
Thursday, September 10, 2009
What happened to that email?
This question:
Created script to send mails using sp_send_dbmail- working like a charm.
Now searching for a way to get result code of sent mail (like Success = Recipient got it,
Failure = Did not get regardless of the reason).
I mean SP return codes 0 (success) or 1 (failure) refer to correct mail Profile, not missing Recipient, etc.
Frankly not sure this is possible as it looks like outside Sql Server authority/responsibility?!
asked in this thread on SQLServerCentral prompted me to do some research into Database Mail. The result of the research is that there is no way to get this information from SQL Server.
Basically the way Database Mail/sp_send_dbmail works is that the message is placed in a Service Broker queue (sp_send_dbmail returns success), the external Database Mail executable reads the queue and sends the message to the designated SMTP mail server. If the mail server accepts the message then Database Mail is done and the status is set to sent. So, if you have an incorrect email address or the receiving server refuses it, SQL Server has no way to know. In order to find this out you would need to use a valid Reply To or From email address and monitor that mailbox.
Here’s the query I use for checking Database Mail:
SELECT
SEL.event_type,
SEL.log_date,
SEL.description,
SF.mailitem_id,
SF.recipients,
SF.copy_recipients,
SF.blind_copy_recipients,
SF.subject,
SF.body,
SF.sent_status,
SF.sent_date
FROM
msdb.dbo.sysmail_faileditems AS SF JOIN
msdb.dbo.sysmail_event_log AS SEL
ON SF.mailitem_id = SEL.mailitem_id
Let me know if you have any better ways to find errors for Database Mail.
Monday, August 24, 2009
Types Matter!
Look if the column is supposed to store dates use the appropriate DATE data type, most RDBMS products have a few, and if it is supposed to store numeric data use the proper NUMERIC data type. So if you are storing a person’s age you don’t need BIGINT or DECIMAL, SMALLINT will work fine. This is one of the things a database is designed to help with, verification that data is of the correct type, don’t keep the database engine from doing what it is designed to do!
Thanks for your suppport!
Wednesday, August 12, 2009
No Training Budget Still No Excuse
When I first started by career in IT and SQL Server about 10 years ago I lived in a small town in northern New Hampshire. I used my location as an excuse not to attend user groups and other events, unless the boss was willing to foot the bill. Now I realize my mistake, I’m in charge of my professional development, and now, location is no excuse. Here’s a list of just some of the FREE training available online:
- JumpstartTV - short (3-5 minute), specific how-to videos for SQL Server and .NET.
- Channel 9 – MSDN’s video training library. This has videos on just about everything microsoft. Over 200 on SQL Server 2008 alone.
- PASS - the Official Professional Association of SQL Server web site. PASS offers Virtual Chapters (user groups that meet online monthly). Be sure to check out the upcoming 24 hours of PASS
- Quest Software – Quest offers free webcasts on several technologies including SQL Server and some on Quest products but many general ones. Check out Quest Connect
- SQLServerPedia - has video podcasts as well as blogs, wiki, and articles.
- SQLServerCentral – forums, articles, scripts, blogs, and videos. What more can you ask for?
I’m sure there are other resources as well.
Now on to the FREE events.
- The biggest one for SQL Server, well, at least in my opinion, is the SQLSaturday franchise. There have been about 20 over the last 2 years from Florida to Oregon. I should make a disclaimer that I have spoken at 2, attended another, and the franchise is owned by my friend, Andy Warren and his partners. Actually the first SQLSaturday event was when I met Andy, actually the day before at the Day with Celko event done in conjunction with the SQLSaturday. It’s a full day of free SQL Server training with some national speakers along with the best local speakers.
- Code Camps – these one day events for .NET are what SQLSaturday is based on, so if you need .NET training here’s a way to get it.
- PASS – offers a SQLSaturday like event support with it’s Community Connections
- User Groups – just BING or GOOGLE for [your technology and location] User Group. I’m sure you’ll find something in your area. PASS has a list of official chapters for SQL Server. I know of Sharepoint, Java, .NET, and Agile user groups. I’m sure there are others.
As you can see there are plenty of training options that will only cost you time and maybe travel expenses and that’s really an investment in you.
Friday, July 31, 2009
Article re-featured on SQLServerCentral
The second article I had published on SQLServerCentral, ANSI PADDING, Trailing Whitespace, and Variable Length Character Columns, was re-featured in the newsletter and on the home page today.
My favorite part of being published is the discussion that hopefully follows. Even if there are some negative comments, I just consider that an extension of the learning environment.
Friday, July 24, 2009
Learning some javascript
As part of my work I need to be a “Jack of all trades” meaning I not only need to be able to do SQL Server DBA and development tasks, but I also need to be capable of doing .NET and VFP development. Yes, I did say VFP (Visual FoxPro for those who are too young to know what it is).
The major project in which I am involved is using SQL Server and ASP.NET (C#) MVC. Fortunately I had downloaded Scott Guthrie’s NerdDinner tutorial when it first came out and have just finished going through it. In the tutorial I came across some code like this:
if (item.Description !== undefined) {
description = item.Description;
}
Well, I had never seen the !== operator before and assumed that it was a typo and was supposed to be:
if (item.Description != undefined) {
description = item.Description;
}
since I know that == the equality comparison operator and != is the inequality comparison operator. Well, then I found the same operator later in the code, so I thought, “Hmmm, maybe this isn’t a typo, but a real operator?”, so off to www.google-vs-bing.com to see what I could find. Well, I didn’t find anything helpful on page 1 of either search engine’s results. Next a quick question on Twitter. Ahhh, several replies that, “Yes !== and === are operators and they compare type AND value”. Having always programmed in strongly typed languages this is new and interesting to me. So basically in javascript 1 == “1” is true, but 1 === “1” is false.
I still would rather be in SQL, but at least now I am starting to understand more about javascript.
Wednesday, April 15, 2009
Introduction to Profiler published on SQLServerCentral
My article Introduction to Profiler has been published on SQLServerCentral today. This is part of one of a series that I am working on and covers the basics of Profiler. The rest of the series will build to more complex uses of Profiler.
Tuesday, April 14, 2009
The Deception of IsNumeric()
Has anyone else ever used the IsNumeric() function to try to eliminate this error (or similar):
Msg 8114, Level 16, State 5, Line 1
Error converting data type varchar to numeric.
yet still get the error? Isn’t that what the function is for? Here’s what is says in BOL:
ISNUMERIC returns 1 when the input expression evaluates to a valid numeric data type; otherwise it returns 0
But run this code (generates a list of ASCII Characters):
;WITH cteNumbers AS
(
SELECT TOP 255
ROW_NUMBER() OVER(ORDER BY NAME) AS n
FROM
sys.all_columns AS AC
)
SELECT
n AS ASCII_code,
CHAR(n) AS [character],
ISNUMERIC(CHAR(n)) AS is_numeric
FROM
cteNumbers
WHERE
n <=255 AND
ISNUMERIC(CHAR(n)) = 1
And you’ll see that the first 10 rows and last 8 rows returned are not what I would consider numeric. Or how about this:
SELECT
ISNUMERIC('20,00')
GO
SELECT
CONVERT(DECIMAL(9,2), '20,00')
The first statement returns 1, while the second fails.
Here is the last interesting behavior of IsNumeric() in relation to Cast/Convert provided by Frank Kalis on this SQLServerCentral thread:
SELECT ISNUMERIC(0X0e) AS E, ISNUMERIC(0X0f) AS F
E F
----------- -----------
0 0
(1 row(s) affected)
WHILE
SELECT CAST(0X0e AS INT) AS E, CAST(0X0f AS INT) AS F
E F
----------- -----------
14 15
The moral of the story is that IsNumeric() <> CanBeConvertedToNumeric().
So what is the answer? I don’t know. You would need to customize the solution to meet your specific situation. Jeff Moden suggests an IsAllDigits function in this thread on SQLServerCentral for instances where you want to eliminate rows with non-numeric characters.
Do you have anything that you recommend?
Thursday, April 9, 2009
Deploying Custom SSIS Components
This post is really for me. I have written 1 custom component for SSIS 2005 and wrote an article on it for SQLServerCentral that was recently re-published. In the discussion someone asked for specific steps for installing and using it. Well, since it has been about a year, I couldn’t remember all the specifics so I’m blogging the steps so I have place to go the next time I need it. So on with the steps:
- Register the component in the GAC (Global Assembly Cache). There are 2 ways to this:
- Drag and drop the component into C:\Windows\Assembly
- Use the GAC utility that that is part of the Visual Studio SDK and the Windows SDK. Here is the syntax:
- gacutil.exe /if Path\AssemblyName
- Depending on which one you installed it will be located in one (or both) of these locations if you used the default install:
- C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin
- C:\Program Files\Microsoft SDKs\Windows\v6.0\Bin
- On your development machine put the component in one of the folders under C:\Program Files\Microsoft SQL Server\90\DTS (default install location):
- \Connections
- \ForEachEnumerators
- \LogProviders
- \PipelineComponents
- \Tasks
The component I created is a Pipeline Component so it belongs in the PiplelineComponents folder. Once you have put it here you can add it to your toolbox by right-clicking on the toolbox, selecting “Choose Items”, select the SSIS Data Flow Items tab (in this case), and select the Error Details component.
This is really a summarization of this blog post by Matthew Roche that you can read for more details.
Friday, April 3, 2009
Error Handling in SSIS re-featured on SQLServerCentral
I am honored that my first ever published article, Error Handling in SSIS, was re-featured on SQLServerCentral today. I don’t know how Steve selects articles to feature a second time, but I assume it was because they were popular. Here are a couple of earlier blog posts about this article/subject:
Thursday, April 2, 2009
Update on my 2009 Goals
- Learn SQL Server 2008, particularly policy-based management, resource governor, and service broker.
- Not a whole lot of progress here. I do have it installed on my laptop, but I have not done much with it, nor have I bought any books. I really need to get started.
- Write 1 Article per quarter for SQLServerCentral.
- Well, I did get an article submitted in the first quarter which will be posted early in the second quarter so I am on target, especially since I have the information for at least 2 more articles already, so I “just” need to write them.
- Record and submit 1 video per quarter for JumpstartTV
- Again I did get one done in the first quarter so I am on target. I also have some ideas for others so it is all about getting them scripted and recorded.
- One Blog post per week
- Another one I am meeting with 19 posts in the first quarter. I have to admit to this being a struggle for me. I really want the posts to have good content and not just be announcements and recaps of meetings I have been to. There is nothing wrong with those posts, but I’d like to be sharing something others can learn from more often. I think that as I write more, it will get easier and better, not just to write, but also to find topics.
- Become more involved in leadership in my local user group (OPASS).
- I’m trying to do make the time to do this, but I need to be more purposeful about it. I am trying to help out by pursuing speakers from within my network. Of course, a bigger network would make it easier. Time is limited and whatever I do try to do I want to do well. Also I have become more involved in PASS as my friend Andy Warren (the OPASS president) is on the board of directors, so I’m trying to help out there as well.
- Speak at 2 community events.
- I have a submitted a session to SQLSaturday – Pensacola and will be submitting at least one session for the PASS Summit, so I am making some effort. I do need to contact some of the other User Groups within driving distance (Tampa, Jacksonville) so I can get more experience. Odds are I will put together another presentation for OPASS this year and at the next SQLSaturday – Orlando.
- Attend the PASS Summit
- I am working on this one. I have spoken to my boss about possibly getting some funds for it, but, regardless, I plan on going. As I already mentioned I will also be submitting at least one session. This is probably the easiest one to do as all it takes is a little money and some vacation time.
Wednesday, March 25, 2009
What’s ownership chaining?
Edit: While the code section is not as nice as I would like it is better. Thanks for sticking with me.
I recently answered a question on SSC about how you granting execute rights on a stored procedure allows you to access tables without having any permissions granted on the tables. The way that SQL Server implements this it through ownership chaining. Here is the BOL (SQL Server 2005) definition of Ownership Chains:
When multiple database objects access each other sequentially, the sequence is known as a chain. Although such chains do not independently exist, when SQL Server 2005 traverses the links in a chain, SQL Server evaluates permissions on the constituent objects differently than it would if it were accessing the objects separately. These differences have important implications for managing security.Prior to SQL Server 2005, objects were owned by users, and, in most cases, designers and developers used the dbo user to own common objects. In my experience it was very rare for objects to be owned by a “real” user. Thus if the customers table and orders table are both owned by dbo, then you can create a customer_orders_get stored procedure owned by dbo, grant user Joe execute permissions on the stored procedure, and Joe would be able to successfully use the stored procedure to query the customers and orders tables without having select permissions on the tables. This is pretty easy to understand, as long all the objects are dbo.object_name I can access them using ownership chaining.
Ownership chaining enables managing access to multiple objects, such as multiple tables, by setting permissions on one object, such as a view. Ownership chaining also offers a slight performance advantage in scenarios that allow for skipping permission checks.
How Permissions Are Checked in a Chain
When an object is accessed through a chain, SQL Server first compares the owner of the object to the owner of the calling object. This is the previous link in the chain. If both objects have the same owner, permissions on the referenced object are not evaluated.
SQL Server 2005 introduced schemas which removed the use of users as owners. Now objects belong to schemas, not users. From BOL:
Beginning in SQL Server 2005, each object belongs to a database schema. A database schema is a distinct namespace that is separate from a database user. You can think of a schema as a container of objects. Schemas can be created and altered in a database, and users can be granted access to a schema. A schema can be owned by any user, and schema ownership is transferable.The question now is, how does ownership chaining work when using schemas? As the last line of the BOL quote states, a schema is owned as well, and ownership chaining in SQL Server 2005 and greater is based on schema ownership. So if schema A is owned by dbo then you would be able to use ownership chaining to access objects in dbo from schema A and vice versa. I think the best way to explain is with an example:
USE [master] GO -- drop the DB if it exists IF EXISTS ( SELECT name FROM sys.databases WHERE name = N'oc_test' ) DROP DATABASE [oc_test] GO USE [master] GO -- create the test database CREATE DATABASE [oc_test] ON PRIMARY ( NAME = N'oc_test', FILENAME = N'C:\Data\oc_test.mdf' , SIZE = 194560KB, MAXSIZE = 1024000KB, FILEGROWTH = 10240KB ) LOG ON ( NAME = N'oc_test_log', FILENAME = N'C:\Logs\oc_test_log.ldf', SIZE = 204800KB, MAXSIZE = 204800KB, FILEGROWTH = 10240KB ) GO -- switch to the new databse USE oc_test ; GO -- drop the user if it exists IF EXISTS ( SELECT 1 FROM sys.database_principals AS DP WHERE DP.[name] = 'test_user' ) BEGIN DROP USER test_user ; END -- create our test user CREATE USER test_user WITHOUT LOGIN WITH DEFAULT_SCHEMA = dbo ; GO -- drop proc and schema IF OBJECT_ID('ownership_chaining.table_one_ins') IS NOT NULL BEGIN DROP PROCEDURE ownership_chaining.table_one_ins ; END GO IF SCHEMA_ID('ownership_chaining') IS NOT NULL BEGIN DROP SCHEMA ownership_chaining ; END GO -- create our test schema, notice it is "owned" by dbo CREATE SCHEMA ownership_chaining AUTHORIZATION dbo ; GO -- drop our test table if it exists IF OBJECT_ID('dbo.table_one') IS NOT NULL BEGIN DROP TABLE dbo.table_one ; END /* create a test table NOTICE it is in the dbo schema which is owned by dbo */ CREATE TABLE dbo.table_one ( column_one INT IDENTITY(1, 1), column_two VARCHAR(10) NOT NULL ) ; GO /* Create the test procedure in the ownership_chaining schema which is owned by dbo */ CREATE PROCEDURE ownership_chaining.table_one_ins @column_two VARCHAR(10) AS INSERT INTO dbo.table_one (column_two) VALUES (@column_two) ; RETURN ; GO /* give out test user execut permissions on the procedure */ GRANT EXECUTE ON ownership_chaining.table_one_ins TO test_user ; GO -- change user context EXECUTE AS USER = 'test_user' ; -- execute the procedure EXEC ownership_chaining.table_one_ins @column_two = 'Test 1' ; -- go back to my login Revert ; -- verify that it worked which it should SELECT * FROM dbo.table_one AS _TO ; /* Now change the owner of the ownership_chaining schema so that it is no longer owned by dbo thus breaking the chain */ ALTER AUTHORIZATION ON SCHEMA::ownership_chaining TO db_owner ; -- change user context EXECUTE AS USER = 'test_user' ; /* using try - catch so I get the revert as I expect failure */ BEGIN TRY EXEC ownership_chaining.table_one_ins @column_two = 'Test 1' ; END TRY BEGIN CATCH -- show the error message SELECT ERROR_MESSAGE() AS error ; -- return to my user revert ; END CATCH -- verify data SELECT * FROM dbo.table_one AS _TO ;
Monday, February 9, 2009
Script featured on SQLServerCentral
The script uses the default trace to show who has logged into SQL Server. It is, of course, limited by the data contained in the Trace.
Tuesday, January 13, 2009
Forum Etiquette Rant
"I found this as I worked on a problem and reading it is a waste of my time, you guys don't know what you are talking about"Now maybe I shouldn't be irritated by this, but, as a frequent contributor, it bothers me when someone doesn't offer anything constructive to the thread and basically calls me an idiot at the same time.
Hey, I realize I don't know it all, and when someone posts a better solution than I did I think it's great and I use it myself.
If you find yourself on a forum and don't find it helpful, don't criticize without posting a better solution. Remember most of the contributors are doing it because they want to help, not because they are getting paid.
Remember what your mother said,
"If you can't say something nice, don't say anything at all"
Tuesday, January 6, 2009
How's your commentary?
There are generally 2 camps for commenting code:
- My code is self-documenting.
- I want the next person to know what I was doing and why. Oh, and I want to be able to remember when I go back in 6 months.
People in this camp usually have more verbose method, property, and variable names.
People in this camp sometimes over comment. This folks will take a Insert Into table Select From tableB and put this comment at the top "this inserts a row into tableA from tableB".
- Comment why I am doing what I am doing and why I am doing it this particular way. Any good developer should be able to figure out what the code does, but not necessarily the why. Thanks Roger Plowman and those who seconded his comment.
- Comment so a novice, either in programming or business knowledge, can understand what the code does and why. Several people mentioned it in the thread.
- Create the why and how comments BEFORE beginning. Really this is putting together a plan, and will probably be the hardest one for me as I like to "dive" right in. Thanks Jay Holovacs and others who mentioned this.
- Push for and get an intranet Wiki or Knowledge Base. My first job had one and it was great. Where I'm at now does not, which means the developer who wrote it, supports it. Not that I don't want to support my code, but I don't need to be the only one who can do it. Thanks again to Roger Plowman and others.
So now you know that my "commentary" is mediocre at best, but how's yours?
Tuesday, December 23, 2008
Simple, but effective code example
SELECT TOP 366
IDENTITY(INT, 1, 1) AS n
INTO
#nums
FROM
sys.all_objects
SELECT
DATEADD(DAY, n, '1/1/2008')
FROM
#nums
WHERE
DATEPART(weekday, DATEADD(DAY, n, '1/1/2008')) =
CASE @@DateFirst
WHEN 7 THEN 4
WHEN 6 THEN 5
WHEN 5 THEN 6
WHEN 4 THEN 7
WHEN 3 THEN 1
WHEN 2 THEN 2
ELSE 3
END
DROP TABLE #nums
The first section just creates a temporary numbers table and you can do that several ways, I just opted for a simple one. If you don't have a numbers table already, you should as there are many good uses for it.
Then I just run a simple select against the temporary numbers table adding each number to the first day of the year. The WHERE clause then just compares the value returned from the DATEPART function (weekday) to the value for Wednesday based on the @@Datefirst value. According to Books on line @@Datefirst:
SET DATEFIRST indicates the specified first day of each week. The U.S. English default is 7, Sunday.
Language settings affect date information.
This can also be affected by using SET DATEFIRST if you want it to be different from the default for the language. This case statement is important if you expect the code to work on any database in any SQL Server install.
Ideally to solve a problem like this you would have a dates table as Jonathan recommends in the thread.
For more information on a numbers table check out these articles:
http://www.sqlservercentral.com/articles/TSQL/62867/
http://www.sqlservercentral.com/articles/Advanced+Querying/2547/
Thursday, December 11, 2008
Kerberos Authentication and SQL Server
As always, it is a good idea to check out the discussion as well, as questions you may have are probably in there and answered.
Tuesday, December 2, 2008
Script published on SQLServerCentral
Sunday, October 26, 2008
SQLSaturday #8 - Orlando Recap
My Profiler session went well and seemed to be well-received. There were no empty seats, nor was there anyone standing. I forgot to look at the session evaluations as I was quickly moving on to let the next speaker get setup and to get to my next task. I think I could have done a little more prep, especially for the trace I used for my replay definition. The next step would be to do an advanced presentation that spends more time on server-side tracing and querying and interpreting trace information.
I also went to the after party which was a lot of fun. It's great to sit and relax with other geeks, talk shop, sports, politics, and just goof around a bit. It looked like about 30-35 people went to the after party which was a decent turnout.
The next SQLSaturday is SQLSaturday #10 - Tampa 2009 January 24, 2009. I won't be able to make it as I'll be freezing in New Hampshire for a few months, but I bet it will be a good event to attend.
Tuesday, October 14, 2008
Introduction to DML Triggers published on SQLServerCentral
You can also follow any discussion of the article here.