Showing posts with label SQL. Show all posts
Showing posts with label SQL. Show all posts

Tuesday, July 28, 2015

TSQLUnit_Job

TSQLUnit_Job
Allows TSQLUnit to run unit tests as a database job (SQL Agent)

I finally released code publicly (yikes!). I doubt anyone will ever see it, let alone use it. However, it still frightens me beyond belief!

Go check it out now on github:
https://github.com/jjerome00/TSQLUnit_Job

It's essentially a wrapper for TSQLUnit that allows unit tests to be run as a database job (SQL Agent), with the results sent as an email (with html formatting). Read more about it on Github.

I wrote it years ago for a situation where we needed to look for common error conditions in a legacy system. We didn't have the resources to fix everything, so I would write a test to look for certain situations. The job would notify me if something came up. It was very useful for planning - if an issue only occurred once every six months, we might apply a different fix instead of re-writing the entire module.

It requires TSQLUnit, and SQL Server (duh).

TSQLUnit_Job

Tuesday, October 30, 2012

NULLIF (post, blank)


I revisited a neat little function in SQL Sever called NULLIF, and I thought it might be worth mentioning.  It came in very handy today when I used it to build a query on some summary data, it handled any divide-by-zero errors without much fuss.

NULLIF( param1, param2)

The function will return NULL if the comparsion of the two parameters is equal, otherwise it will just use the first parameter. 

select isnull( revenue / NULLIF(volume,0), 0 ) as [price] from sales_summary


A quick breakdown:

  1. NULLIF(volume, 0)  -- if volume is 0 then this function will return NULL, otherwise it will return the volume
  2. revenue / NULLIF(volume,0)  -- this is just my calculation (revenue/volume = price), if volume is zero the entire thing will return NULL (I like to think of it as exploding out as NULL)
  3. ISNULL( revenue / NULLIF(volume,0), 0 ) -- The entire thing is wrapped by a ISNULL; if NULL is returned by NULLIF, it returns NULL for the entire calculation, which is handled by the ISNULL
An example:

begin tran t1

-- build some test summary data
declare @sales_summary table (
period int,
revenue money,
volume numeric(18,4)
)
;

insert into @sales_summary
select 1, 100.00, 50
union all
select 2, 0, 0
union all
select 3, 5000, 2500
union all
select 4, 30000, 20
;

select * from @sales_summary;

select
period,
revenue,
volume,
--(revenue / volume) as price --This will cause Divide-By-Zero for period 2
( revenue / NULLIF(volume,0) ) as [ex], --example showing the NULLIF returning NULL for period 2
isnull( revenue / NULLIF(volume,0), 0 ) as [price] -- Using the ISNULL to handle NULL and show 0
from @sales_summary
;

rollback tran t1

Tuesday, October 2, 2012

SQL Date table (or Calendar table)

I came across a request to show data out of sql for every day in a month.  I know that I can't assume that my data will have results for every day, so I have to show days where there was no activity.  An easy way to achieve this is in SQL is with a Date table; sometimes they are also called Calendar tables. 

Man am I ever glad I paid attention when someone told me about these things!  It makes building queries against dates much faster. 


Here is an example end result.  (I included the build scripts below)

SELECT
dt.date,
o.order_date,
o.order_number

FROM
Date.DateTable dt
left join dbo.Orders o on dt.date = o.order_date

WHERE
dt.date between '9/1/2012' and '9/5/2012'
;


This will show every day 9/1 - 9/5, and if there is an order that particular day, it will show those as well.  Otherwise the query will show NULL for o.order_date and o.order_number.  I did that just to show what was going on in the left join.






There are a ton of examples out there on how to build Date tables, and this is just my version.  The idea is that you build this table once.  Make sure you cover all the dates and date-parts you expect to be asked for. For example, sometimes have to deal with Julian dates, so I incorporated them into the date table to handle those instances.

This script is a copy of one I used a few years ago, and contains a few user-defined functions, schemas, etc.  So I wouldn't just copy-paste if I were you!

The build script:

--BEGIN TRAN T1;
--drop table Date.DateTable

-- Create the table (I use a "DATE" schema for date-related objects)
-- Some of these fields are just things I know the client asks for a lot.
CREATE TABLE Date.DateTable
(
date_id INT PRIMARY KEY,
[date] datetime,
[month] tinyint,
[day] tinyint,
[year] char(4),
[quarter] tinyint,
[week] tinyint,
[year_day] smallint,
[week_day] tinyint,
[is_first_day_in_month] tinyint,
[is_last_day_in_month] tinyint,
string_date varchar(10),
formatted_date varchar(30),
day_name varchar(10),
month_name varchar(12),
julian_date int,
date_endOfDay datetime,
firstDayOfMonth datetime,
lastDayOfMonth datetime
)
;

-- Setup the date range I want to insert into the table
DECLARE @Date DATETIME;
declare @end_date datetime;

SET @Date = '1/1/1970';
set @end_date = '12/31/2100';

-- Just a count of the number of rows I should expect
select datediff(dd, @Date, @end_date) -- 47846

-- I use a while statement over other options (cursor) because
-- I feel they are fairly easy to read and you only have to run this script once anyway.
WHILE @Date <= @end_date
BEGIN

-- My insert statement uses a few user-defined functions,
-- but you can figure out what they do fairly easily.
INSERT INTO Date.DateTable (
date_id,
[date],
[month],
[day],
[year],
[quarter],
[week],
[year_day],
[week_day],
[is_first_day_in_month],
[is_last_day_in_month],
string_date,
formatted_date,
day_name,
month_name,
julian_date,
date_endOfDay,
firstDayOfMonth,
lastDayOfMonth
)
select
CONVERT(VARCHAR(8), @Date, 112) as [date_id],
@Date as [date],
DATEPART(mm, @Date) as [month],
DATEPART(dd, @Date) as [day],
year(@Date) as [year],
DATEPART(qq, @Date) as [Quarter],
DATEPART(ww, @Date) as [week],
DATEPART(dy, @Date) as [year_day],
DATEPART(dw, @Date) as [week_day],
case when (datediff(dd, @Date, DATE.FirstDayOfMonth(@Date)) = 0) then 1 else 0 end as [is_first_day_in_month],
case when (datediff(dd, @Date, DATE.LastDayOfMonth(@Date)) = 0) then 1 else 0 end as [is_last_day_in_month],
CONVERT(VARCHAR(10), @date, 101) as [string_date],
DATENAME(MM, @date) + RIGHT(CONVERT(VARCHAR(12), @date, 107), 9) as [formatted_date],
DATENAME(dw, @Date) as [day_name],
DATENAME(mm, @Date) as [month_name],
DATE.Convert_to_Julian(@Date) as [julian_date], -- an int representation of a date, used by client
DATE.EndOfDay(@Date) as [date_endOfDay], -- the Last possible (milli) second of the day
DATE.FirstDayOfMonth(@Date) as [firstDayOfMonth],
DATE.EndOfDay(DATE.LastDayOfMonth(@Date)) as [lastDayOfMonth]
;

SET @Date = DATEADD(dd, 1, @Date);
END

-- Hopefully this will equal 47846
select count(*) from Date.DateTable

--ROLLBACK TRAN T1;



Just in case you're interested, here is the build script for the example (i.e. the Orders table):

CREATE TABLE [dbo].[Orders](
[order_id] [int] IDENTITY(1,1) NOT NULL,
[order_date] [datetime] NOT NULL,
[order_number] [nvarchar](50) NOT NULL
) ON [PRIMARY]
;

insert into dbo.Orders (order_date, order_number)
select '9/1/2012', 'Order 1'
union all
select '9/5/2012', 'Order 2'
;

Thursday, February 16, 2012

SQL Server and Excel: Include column headers

If you want to build a quick report from your sql results in Excel, I'd highly recommend turning an option in Management Studio (SSMS) called "Include column headers when copying or saving the results".  Dare I say it's one of those helpful options you only know about if you happened to stumble across it?

You'll find it under:
  1. [System Menu] > Tools > Options...
  2. Query Results + SQL Server + Results to Grid
  3. Check the box that says "Include column headers when copying or saving the results"

Options dialog:



Example of use:

Select from Results Grid
Select All (or Ctrl+A & Ctrl+C)

Pasting into Excel
Headers included!

Friday, February 3, 2012

SQL 2008 Installation Follies


I installed SQL Server 2008 (R2) on my machine alongside SQL Server 2005, which was already on there as the default instance; I named my 2008 instance “SQL2008”. 

If you’re like me, you have lived the past few years without the need to think about default instances.   So remember that stuff like “(local)” will connect to your 2005 instance, and “(local)\SQL2008” will connect to your 2008 instance.

SSMS 2008 will gladly connect to your (default) 2005 instance, and then you will be left scratching your head trying to figure out why you can’t create databases with a compatibility level of 100, or continue to get frustrating errors trying to restore 2008 backups.

That’s my errant move of the day, I hope this finds you before you do anything drastic.

Wednesday, February 1, 2012

Show/Hide Results Pane – SQL Server 2008

A few years back I wrote an article about doing this same thing in SQL Server 2005.  All the images from the previous post were somehow lost, and since the instructions are basically the same for SQL 2008 I thought it would be a nice refresher.

One of the simplest and most useful features when debugging a sql script is to be able to run your query to see some results, then hide the results pane to continue tweaking your sql code. This feature was a button on the toolbar in Query Analyzer in SQL Server 2000, but for some reason it has been missing in SSMS (SQL Server Management Studio) since 2005. Well, the button may not exist by default, but it's easy to add it in.

This is what we are striving for, a button on the SQL Editor toolbar that says either "Show Results Pane" or "Hide Results Pane":


To add a button to hide the results pane:

1. Click on the little down-arrow at the end of a toolbar and choose "Add or Remove Buttons", then choose "Customize...":
Add button to Toolbar


2. The Customize dialog window will open with the "Commands" tab selected. Now we only need to find the command we are looking for. Under "Categories:", choose "Window". Then under "Commands:", find the command "Show Results Pane":

Choosing the "Show Results Pane" command from the menus


 3. Once you have located the "Show Results Pane" command, you have to click and drag it to where you want it to be located on the toolbar:

Starting the drag


Placing on toolbar



End result after placement

Originally I had my button located on the Standard toolbar, but eventually I figured out it made more sense to add it to the SQL Editor toolbar. It's pretty easy to find the SQL Editor toolbar - it only displays when you are working in a query window.


Here is the end result again:

Notice the button says "Hide Results Pane" when the results pane is showing:
"Hide Results Pane" Button


Also notice that the button says "Show Results Pane" when the results pane is hidden:
"Show Results Pane" Button




Wednesday, June 9, 2010

End Of Day(s) Function

Some of our reports were not showing items marked with a date and time when using the BETWEEN operator. This was because BETWEEN only considers values that are equal to one of the values, or falls in-between the range defined by them. When you use dates with with no time, the operator assumes the time is 0.

i.e.: BETWEEN '1/1/2010' and '1/2/2010' would mean '1/2/2010' would be treated as '1/2/2010 00:00:00.000'.

I created a date function to return a given day as the last second of the day. So '1/2/2010' will be returned as '1/2/2010 23:59:59.997. The milliseconds weren't really needed in my case, but I felt like doing it anyway.


Here is the function I created. I use a [DATE] schema for my date-related functions on our server.


/*
[DATE].[EndOfDay]
6/3/2010 JMJ

Returns a datetime set to the Last possible (milli) second of the day.
i.e. [DATE].[EndOfDay]('12/8/2008') would return '12/31/2008 23:59:59.997'
*/
CREATE FUNCTION [DATE].[EndOfDay] (
@InputDate DATETIME
)

RETURNS DATETIME
BEGIN
--Get Next day (with no time)
declare @dtNextDay datetime;
set @dtNextDay = DATEADD(d, 1, DATEADD(d, DATEDIFF(d, 0, @InputDate), 0));

-- remove -2 milliseconds (that's as close as you can get)
-- which puts you into the last second of the original given day
return DATEADD(ms, -2, @dtNextDay);
END

----
Here is some example code using the function:

declare @dates table (
entrydate datetime
);

insert into @dates
select '1/1/2010 00:00:00.000'
union all
select '1/1/2010 10:00:00.000'
union all
select '1/2/2010 00:00:00.000'
union all
select '1/2/2010 12:00:00.000'
union all
select '1/2/2010 23:00:00.000'
;


select * from @dates
where entrydate between '1/1/2010' and '1/2/2010'
;


-- returns:
-- 2010-01-01 00:00:00.000
-- 2010-01-01 10:00:00.000
-- 2010-01-02 00:00:00.000


select * from @dates
where entrydate between '1/1/2010' and DATE.EndOfDay('1/2/2010')
;

-- returns:
-- 2010-01-01 00:00:00.000
-- 2010-01-01 10:00:00.000
-- 2010-01-02 00:00:00.000
-- 2010-01-02 12:00:00.000
-- 2010-01-02 23:00:00.000

Thursday, July 3, 2008

Number of Decimal places and the SubString() function

I wanted a function that could give me the number of decimal places used in a given number. I really didn’t find any elaborate solutions on the web, so I decided to write my own by converting the number into a varchar and parsing it appropriately. While building the function, I discovered a neat little feature of the SubString() function.


Normally, SQL seems to work with an index of 1. So I if asked it the position of the “J” in “Jason”, it would return 1 (one). However, I was surprised to learn that the SubString() function accepts a 0 (zero) as a parameter for where to start the sub string.


This is an effective way to trim-off the last character of a string using SubString(@var, 0, len(@var)). For example, if I want to trim off the last character of “0040”, I can use the SubString() command to trim off the last “0”.

Here is an explanation with code:

--SubString(string, start, length)

Declare @var varchar(10);

Set @var = '0040';

Select SubString(@var, 1, len(@var)) --will return ‘.0040’

Select SubString(@var, 0, len(@var)) --will return ‘.004’


Using 0 for the start point with the length of the string has the effect of trimming the last character from the string. I wasn’t expecting this behavior, but it certainly is nice!


Decimal Count Function

Here is the entire function in case you are looking for something similar. I built it for use with SQL Server 2005.


Here is my usual disclaimer:

I’m not sure if this was the best way to go about solving the problem, but it worked. I thought my code and comments might be useful for someone else who has a similar problem. This code is free and comes with no guarantees.


One more thing - apparently I lost my formatting (tabs/spaces/etc) when I pasted this code, and I'm too lazy to fix it. Should only take a couple of minutes to make it look the way you're used to seeing it.



/*

DecimalCount

7/3/2008 JMJ

Count of the number of decimal places used after decimal point.

Params:

@numString varchar(MAX) varchar representing a number, assumes 1 (one) decimal point

Returns:

int Number of places used after decimal point, 0 (zero) if no decimal point

is found in the number. Will not consider zeros on the end of the number

(i.e. .0040 will consider only .004, which will return 3).

*/

CREATE function [dbo].[DecimalCount] (

@numString varchar(MAX)

) returns int

as

begin

declare @decimalPlace bigint;

declare @subStr varchar(max);

set @decimalPlace = 0;

--find index of decimal place

set @decimalPlace = Charindex('.',@numString,0);

if (@decimalPlace > 0)

begin

-- @subStr = string without decimal (or anything left of decimal)

set @subStr = @numString;

set @subStr = Right(@numString, Len(@numString) - @decimalPlace);

-- remove any trailing zeros

while(charindex('0',@subStr,len(@subStr))) = len(@subStr)

begin

-- catch conditions for numbers like '100.' or '100.0'

if ( charindex('0',@subStr,len(@subStr)) = 0

and len(@subStr) = 0 )

begin

break;

end

--using 0 for beginning will trim the last char from string

set @subStr = SubString(@subStr, 0, len(@subStr));

end

if (len(@subStr) is null)

begin

return 0;

end

else

begin

return len(@subStr);

end

end

else

begin

--no decimal place

return 0;

end

return 0;

end