September 28th, 2009Sequencing Dates in SQL

“What were the dates of all the Mondays in the last 5 months?”
“How many Fridays were there between this date range?”
“What were the dates of the paydays in the last 10 months?”

Do any of these types of questions sound familiar? If you’ve ever worked with these types of date time questions in SQL, you would know that it can be VERY challenging.

At the hospital we use Cisco Unified Communications Manager (a.k.a CallManager).

The directors desired a report to break down various call statistics to one of their clinics. They were hoping this information would give them insight into how to handle call load.

One particular question was a tricky one: “What is the average call per days of the week, per hour, for the last three months?”.

For example: “What was the average call count, per hour, for all the Mondays in the last 3 months?”

Thanks to newly discovered dimensional tables, I had the breakdown per hour figured out, however, I was stumped on how to group it for average Mondays, Tuesdays, Wednesdays, etc.

That’s why I was thrilled to discover this solution by Peter Larsson, titled How Many More Mondays Until I Retire?

Essentially, Peter suggests creating a scalar SQL function that returns a table of dates between a specified date range. This opens a range of possibilities as far as dealing with tricky date queries.

Here is Peter’s function:

CREATE FUNCTION dbo.fnSeqDates
(
    @LowDate DATETIME,
    @HighDate DATETIME
)
RETURNS @Dates TABLE
        (
            SeqDate DATETIME
        )
AS

BEGIN
    DECLARE @Temp DATETIME

    IF @LowDate > @HighDate
        SELECT    @Temp = @LowDate,
                  @LowDate = DATEADD(day, DATEDIFF(day, 0, @HighDate), 0),
                  @HighDate = DATEADD(day, DATEDIFF(day, 0, @Temp), 0)
    ELSE
        SELECT    @LowDate = DATEADD(day, DATEDIFF(day, 0, @LowDate), 0),
                  @HighDate = DATEADD(day, DATEDIFF(day, 0, @HighDate), 0)

    INSERT    @Dates
              (
                  SeqDate
              )
    VALUES    (
                  @LowDate
              )

    WHILE @@ROWCOUNT > 0
        INSERT     @Dates
                   (
                       SeqDate
                   )
        SELECT     DATEADD(dd, n.Items, d.SeqDate)
        FROM       @Dates d
        CROSS JOIN (
                        SELECT COUNT(SeqDate) Items
                        FROM   @Dates
                   ) n
        WHERE      DATEADD(dd, n.Items, d.SeqDate) <= @HighDate

    RETURN
END

I used the function in my stored procedure like this:

	@StartDate datetime,
	@EndDate datetime,
	@Extensions varchar(50)
AS
BEGIN
	-- SET NOCOUNT ON added to prevent extra result sets from
	-- interfering with SELECT statements.
	SET NOCOUNT ON;

DECLARE @Time_Value TABLE (
RangeName varchar(18) UNIQUE,
Value int UNIQUE
)
-- This section is for setting up the dimensional table for the hours breakdown
INSERT INTO @Time_Value VALUES ('00',0)
INSERT INTO @Time_Value VALUES ('01',1)
INSERT INTO @Time_Value VALUES ('02',2)
INSERT INTO @Time_Value VALUES ('03',3)
INSERT INTO @Time_Value VALUES ('04',4)
INSERT INTO @Time_Value VALUES ('05',5)
INSERT INTO @Time_Value VALUES ('06',6)
INSERT INTO @Time_Value VALUES ('07',7)
INSERT INTO @Time_Value VALUES ('08',8)
INSERT INTO @Time_Value VALUES ('09',9)
INSERT INTO @Time_Value VALUES ('10',10)
INSERT INTO @Time_Value VALUES ('11',11)
INSERT INTO @Time_Value VALUES ('12',12)
INSERT INTO @Time_Value VALUES ('13',13)
INSERT INTO @Time_Value VALUES ('14',14)
INSERT INTO @Time_Value VALUES ('15',15)
INSERT INTO @Time_Value VALUES ('16',16)
INSERT INTO @Time_Value VALUES ('17',17)
INSERT INTO @Time_Value VALUES ('18',18)
INSERT INTO @Time_Value VALUES ('19',19)
INSERT INTO @Time_Value VALUES ('20',20)
INSERT INTO @Time_Value VALUES ('21',21)
INSERT INTO @Time_Value VALUES ('22',22)
INSERT INTO @Time_Value VALUES ('23',23)

SELECT
CASE
	WHEN DATEPART(weekday,ReportShortDateTime) = 1 THEN 'Sunday'
	WHEN DATEPART(weekday,ReportShortDateTime) = 2 THEN 'Monday'
	WHEN DATEPART(weekday,ReportShortDateTime) = 3 THEN 'Tuesday'
	WHEN DATEPART(weekday,ReportShortDateTime) = 4 THEN 'Wednesday'
	WHEN DATEPART(weekday,ReportShortDateTime) = 5 THEN 'Thursday'
	WHEN DATEPART(weekday,ReportShortDateTime) = 6 THEN 'Friday'
	WHEN DATEPART(weekday,ReportShortDateTime) = 7 THEN 'Saturday'
END AS Day_Name, RangeName AS Hour_Range,
COUNT(ReportShortDateTime) AS Calls,
(ROUND((CAST(COUNT(ReportShortDateTime) AS Float) / CAST((SELECT COUNT(SeqDate) FROM dbo.fnSeqDates(@StartDate, @EndDate) WHERE DATEPART(dw, SeqDate) = DATEPART(weekday,ReportShortDateTime)) AS Float)), 1)) AS Average_Calls
FROM [CALLMANAGER] INNER JOIN
@Time_Value ON DATEPART(hour, ReportDateTimeOrigination) = Value
WHERE ReportShortDateTime BETWEEN @StartDate AND @EndDate AND
originalCalledPartyNumber IN (Select Convert(varchar(50),Value) FROM dbo.fnSplit(@Extensions, ','))
GROUP BY DATEPART(weekday,ReportShortDateTime), RangeName, Value
ORDER BY DATEPART(weekday,ReportShortDateTime), Value
END

As you can see in the query, I use the function like this:

SELECT COUNT(SeqDate) FROM dbo.fnSeqDates(@StartDate, @EndDate) WHERE DATEPART(dw, SeqDate) = DATEPART(weekday,ReportShortDateTime)

In other words, I’m saying: “Give me the count of days, between this specified date range, that are Mondays, Tuesdays, Wednesdays, etc.”

September 21st, 2009Grouping Data Into Time Ranges

Today, on the Interwebs, I found a great way to group data into ranges. I modified it quite a bit to fit my needs, however, I imagine you could apply this to any type of ranges you might want to use.

In this case, I wanted to group a dataset of information into time ranges (4 hour groups).

Essentially, I create a temporary dimensional table and join to it. Simply brilliant! I’m ashamed to admit I’ve never used these types of dimensional temp tables in my queries. However, I’m learning it won’t be my last.

DECLARE @Time_Ranges TABLE (
RangeName varchar(18) UNIQUE,
Minimum int UNIQUE,
Maximum int UNIQUE
)

INSERT INTO @Time_Ranges VALUES ('12:00 AM - 3:00 AM',0,3)
INSERT INTO @Time_Ranges VALUES ('4:00 AM - 7:59 AM',4,7)
INSERT INTO @Time_Ranges VALUES ('8:00 AM - 11:59 AM',8,11)
INSERT INTO @Time_Ranges VALUES ('12:00 PM - 3:59 PM',12,15)
INSERT INTO @Time_Ranges VALUES ('4:00 PM - 7:59 PM',16,19)
INSERT INTO @Time_Ranges VALUES ('8:00 PM - 11:59 PM',20,23)

SELECT RangeName, COUNT(*) AS Total
FROM [YOURSQLTABLE] INNER JOIN
@Time_Ranges ON DATEPART(hour, DateTimeField) >= Minimum AND
DATEPART(hour, DateTimeField) <= Maximum
GROUP BY RangeName, Minimum
ORDER BY Minimum

I wish I could say that my code is always bug free.

Developing applications within the health care environment doesn’t afford myself or my users this luxury.
Often times, I test as much as I can and release my little baby into the wild.

Sure, I would love to implement a rigorous SDLC, but if I did that, my projects would take well over a year each.  I’m required to be far more agile than that.

So I would do what any other good programmer would do.  I wrote the best code I could and when users complained I’d pull out my box of Band-Aids (they have “try/catch” written on them).

Unfortunately, users aren’t always the best people to troubleshoot with.  It would go something like this:

User: “The program isn’t working.”
Me: “Okay, I’m sorry about that.  Can you describe the problem you’re having?”
User: “It won’t work when I click the button.”
Me: “Okay.  What page are you on?”
User: “It’s the blue one.  You know the button on the bottom of the blue page?”
Me: ” Well….They’re…kinda…all blue.”
User: “This program is slow…”

During my studies for the 70-562 exam I discovered the proper use of the Global.asax file and how to deal with unhandled exceptions globally within my applications.

I’m not above admitting that any ASP.NET developer, worth his/her salt, should know all about this.  But, then again, I’m not a developer worth my salt.

<shame>I was concerned about logging errors, but most often I would be doing it from code-behind files; storing errors in a database or the Event Viewer</shame>.

So I have now crafted some code to be used in the Application_Error event of the global.asax file.

This will generate a nice HTML and plain-text e-mail detailing all the information about the unhandled error.  In this case, I have these e-mails sent directly to me.

Finally, the code clears the server error so that it can redirect the user to a nice error page, telling them I screwed up.

Most often, the errors that I receive are minor.  It’s been fun to call users and let them know I fixed an error they had only moments ago.  Their reaction is, needless to say, positive.

Here is a sample of what the HTML version of the e-mail looks like:

Screenshot of the HTML e-mail

Screenshot of the HTML e-mail

And here is the code to be placed in the code-behind of your Global.asax file (I’ve omitted the other events for brevity):

using System;
using System.Net.Mail;
using System.Net.Mime;
using System.Web;

/// <summary>
/// Summary description for Global
/// </summary>
namespace YourProjectName
{
        protected void Application_Error(object sender, EventArgs e)
        {
            MailMessage iMsg = new MailMessage();
            SmtpClient SMTP = new System.Net.Mail.SmtpClient("<your Email Server>");
            iMsg.From = new MailAddress("<yourApplicationName>_500_Error@yourdomain.com");
            iMsg.Priority = MailPriority.High;
            iMsg.Subject = "There was a Error Message with <yourApplicationName>";
            //Create the plain text version of the e-mail
            iMsg.Body = "An Unhandled Exception Error occurred with <yourApplicationName> on: " + DateTime.Now.ToString() + "\n\n" +
                "====================\n" +
                "Inner Exception \n" +
                "====================\n\n" +
                 Server.GetLastError().InnerException + "\n\n" +
                "====================\n" +
                "Error Message \n" +
                "====================\n\n" +
                 Server.GetLastError().Message + "\n\n" +
                "====================\n" +
                "Source \n" +
                "====================\n\n" +
                 Server.GetLastError().Source + "\n\n" +
                "====================\n" +
                "Stack Trace \n" +
                "====================\n\n" +
                 Server.GetLastError().StackTrace + "\n\n" +
                "====================\n" +
                "Target Site \n" +
                "====================\n\n" +
                 Server.GetLastError().TargetSite;

            //Create the alternate HTML view
            String HTMLBody = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">" +
                "<html><head><meta http-equiv=Content-Type content=\"text/html; charset=iso-8859-1\">" +
                "</head><body style='font-family:Arial; background-color:#FFFEF5;'>" +
                "<table style='font-family:Arial'><tr><td style='color:Red; font-size:16pt;'>There was a 500 error with *YourApplicationName* on: <td>" + "<td><b>" + HttpUtility.HtmlEncode(DateTime.Now.ToString()) + "</b></td></tr></table><br />" +
                "<div style='font-weight:bold; font-size:14pt; font-family:Tahoma; width:100%; background-color:#077BAD; border-bottom:solid 2px #044062; border-top:solid 2px #044062; color:White'>Inner Exception</div><br />" +
                "<div style='font-family:Courier; font-size:11pt;'>" + HttpUtility.HtmlEncode(Server.GetLastError().InnerException.ToString()) + "</div><br />" +
                "<div style='font-weight:bold; font-size:14pt; font-family:Tahoma; width:100%; background-color:#077BAD; border-bottom:solid 2px #044062; border-top:solid 2px #044062; color:White'>Error Message</div><br />" +
                "<div style='font-family:Courier; font-size:11pt;'>" + HttpUtility.HtmlEncode(Server.GetLastError().Message.ToString()) + "</div><br />" +
                "<div style='font-weight:bold; font-size:14pt; font-family:Tahoma; width:100%; background-color:#077BAD; border-bottom:solid 2px #044062; border-top:solid 2px #044062; color:White'>Source</div><br />" +
                "<div style='font-family:Courier; font-size:11pt;'>" + HttpUtility.HtmlEncode(Server.GetLastError().Source.ToString()) + "</div><br />" +
                "<div style='font-weight:bold; font-size:14pt; font-family:Tahoma; width:100%; background-color:#077BAD; border-bottom:solid 2px #044062; border-top:solid 2px #044062; color:White'>Stack Trace</div><br />" +
                "<div style='font-family:Courier; font-size:11pt;'>" + HttpUtility.HtmlEncode(Server.GetLastError().StackTrace.ToString()) + "</div><br />" +
                "<div style='font-weight:bold; font-size:14pt; font-family:Tahoma; width:100%; background-color:#077BAD; border-bottom:solid 2px #044062; border-top:solid 2px #044062; color:White'>Target Site</div><br />" +
                "<div style='font-family:Courier; font-size:11pt;'>" + HttpUtility.HtmlEncode(Server.GetLastError().TargetSite.ToString()) + "</div><br />" +
                "</body></html>";

            //Add the alternate view
            iMsg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(HTMLBody, null, MediaTypeNames.Text.Html));

            //Finish sending the message
            iMsg.To.Add(new MailAddress("<your_email@your_domain.com>"));
            SMTP.Send(iMsg);
            //Clear the error so that you can redirect to your error page
            Server.ClearError();
            Response.Redirect("~/Error.aspx");
        }
    }
}

This is one of those ideas I wish I was smart enough to come up with myself.  But my hat is off to Alex Arkhipov who came up with a nifty solution to prevent the dreaded double-postback.

You might remember that this issue has been a common theme for me.

It seems that no matter how quick and well designed my UI is, users still find ways to insert multiple records into my database by double-clicking.  It never used to be an issue when the entire page would post back, however, with the latest AJAX tools and functionality, it happens more and more.

In any event, Alex came up with this simple solution that appears to be working great!

Essentially, he adds client-side page event handlers that disable the calling button during an initializeRequest event and re-enables it during an endRequest event.  Simply brilliant!

I modified the code a bit to remove the styling from CSS. I just needed the button to be disabled.
Then, I placed this code inside of my master page so all of my child pages could take advantage of this functionality.


<script type="text/javascript">
function pageLoad(sender, args) {
     var rm = Sys.WebForms.PageRequestManager.getInstance();
     rm.add_initializeRequest(initializeRequest);
     rm.add_endRequest(endRequest);
}
function initializeRequest(sender, args) {
     //Disable button to prevent double submit
     var btn = $get(args._postBackElement.id);
     if (btn) {
          btn.disabled = true;
     }
}
function endRequest(sender, args) {
     //Re-enable button
     var btn = $get(sender._postBackSettings.sourceElement.id);
     if (btn) {
          btn.disabled = false;
     }
}
</script>

I don’t know if you are like me, but I hate dealing with session variables. They look terrible in code, they are difficult to manage and organize, and overhauling them can be a nightmare.

I’m working on a nursing productivity tool here at work. It essentially assists nurse managers in scheduling resources to maintain ratios, provide superior care, and protect our financial bottom-line.

The challenge with this tool is that I’ve well over 20 session variables that I write to.  Additionally, I read from these variables in over seventy locations.

So, I did some Googling and discovered a blog post, written by raj, that details creating a SessionHandler class.

It’s a great way to manage and organize all of your session variables outside of your working code.  Plus, the class protects from NullReferenceException and type cast errors.

Here’s and example:

One of the session variables I’m using stores the user’s full name.  In this case, I create a SessionHandler class file (SessionHandler.cs) and add a public static property called “User Full Name”.

public static class SessionHandler

    {
        private static string _userfullnamekey = "UserFullName";

        // Gets or Sets the user's full name.
        public static string UserFullName
        {
            get
            {
                //Check for null first
                if (HttpContext.Current.Session[SessionHandler._userfullnamekey] == null)
                {
                    //Return an empty string if session variable is null
                    return string.Empty;
                }
                else
                {
                    return HttpContext.Current.Session[SessionHandler._userfullnamekey].ToString();
                }
            }
            set
            {
                HttpContext.Current.Session[SessionHandler._userfullnamekey] = value;
            }
        }

So now, if I want to access the value of this variable from my working code, I do this:


SessionHandler.UserFullName = "Travis Lowdermilk";

Label1.Text = SessionHandler.UserFullName;

You’ll notice that the SessionHandler class ensures that I do not get NullReferenceException or invalid cast errors.

No more repeatedly checking for null values or casting my session variables in working code.


© 2007 travislowdermilk.com | iKon Wordpress Theme by TextNData | Powered by Wordpress | rakCha web directory