Please start any new threads on our new site at https://forums.sqlteam.com. We've got lots of great SQL Server experts to answer whatever question you can come up with.

 All Forums
 SQL Server 2005 Forums
 Transact-SQL (2005)
 Help filling in 'missing' dates

Author  Topic 

R
Constraint Violating Yak Guru

328 Posts

Posted - 2011-05-11 : 05:20:17
I have a stored procedure that searches for login dates for a client. These are then put into a table variable. Finally, the results are selected and grouped by month-year. So this statement:


SELECT
COUNT(*) AS totals,
CAST(year([loginDate]) as nvarchar) + '-' + CAST(month([loginDate]) as nvarchar) as colName
FROM
@tmpTable
GROUP BY
datename(month, [loginDate])+' '+CAST(Year(loginDate) AS nvarchar), month([loginDate]), year([loginDate])
ORDER BY
year(loginDate),
month(loginDate)


produces this result:


totals colName
-----------------
64 2006-12
139 2007-1
52 2007-3
121 2007-4


You can see that because there were no logins during 2007-2 that this month is missing from the results. Is there a quick way to join the table variable to a statement that finds the min/max dates and simply 'fills in' the missing months, so I can get...


totals colName
-----------------
64 2006-12
139 2007-1
0 2007-2 <<-- 'filled in'
52 2007-3
121 2007-4


Here's hoping...

webfred
Master Smack Fu Yak Hacker

8781 Posts

Posted - 2011-05-11 : 05:51:39
Here is an approach:
http://www.kodyaz.com/articles/sql-server-missing-dates-table-using-tsql-cte-calendar-table.aspx


No, you're never too old to Yak'n'Roll if you're too young to die.
Go to Top of Page

khtan
In (Som, Ni, Yak)

17689 Posts

Posted - 2011-05-11 : 05:55:48
you will need to generate the lists of possible dates and then LEFT JOIN to your table.

If you have a number / tally table

declare @start_date datetime,
@end_date datetime

select @start_date = '2006-01-01',
@end_date = '2007-12-31'

; with
dates as
(
select date_start = dateadd(month, n.number, @start_date),
date_end = dateadd(month, n.number + 1, @start_date)
from numbers n
where n.number between 0 and datediff(month, @start_date, @end_date)
)
select isnull(count(t.loginDate), 0) as totals,
convert(varchar(7), d.date_start, 121)
from dates d
left join @tmpTable t on d.date_start <= t.loginDate
and d.date_end > t.loginDate
group by convert(varchar(7), d.date_start, 121)


if you do not have one, you can use F_TABLE_NUMBER_RANGE or F_TABLE_DATE




KH
[spoiler]Time is always against us[/spoiler]

Go to Top of Page

webfred
Master Smack Fu Yak Hacker

8781 Posts

Posted - 2011-05-11 : 06:05:40
also look here: http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=96787


No, you're never too old to Yak'n'Roll if you're too young to die.
Go to Top of Page

R
Constraint Violating Yak Guru

328 Posts

Posted - 2011-05-11 : 06:29:16
Okay thanks for your posts. I had a go at trying to solve this in a different way...

I thought if I get the MIN login date, and the MAX login date, I could loop through these (adding a month each time) and put those values into a secondary table variable. Then, simply join the second table in my original statement.

The problem however is that my WHILE loop is failing (it never actually starts to loop, and just skips). Can anyone please verify that the syntax is correct please, or can anyone see what I've done wrong...?!?


DECLARE @monthsYears TABLE (monthYear nvarchar(10))
DECLARE @mMin INT
DECLARE @mMax INT
DECLARE @yMin INT
DECLARE @yMax INT
DECLARE @minDate datetime
DECLARE @maxDate datetime
SET @minDate = (SELECT MIN([loginDate]) FROM @tmpTable)
SET @maxDate = (SELECT MAX([loginDate]) FROM @tmpTable)
SET @mMin = (SELECT MIN(MONTH(@minDate)))
SET @yMin = (SELECT MIN(YEAR(@minDate)))
SET @mMax = (SELECT MAX(MONTH(@maxDate)))
SET @yMax = (SELECT MAX(YEAR(@maxDate)))
PRINT @mMin -- '6'
PRINT @yMin -- '2006'
PRINT @mMax -- '5'
PRINT @yMax -- '2011'
WHILE (@mMin <= @mMax AND @yMin <= @yMax)
BEGIN
-- ...............This never starts...............!

INSERT INTO @monthsYears
(
monthYear
)
VALUES
(
cast(@mMin AS nvarchar) + '-' + cast(@yMin AS nvarchar)
)
SET @mMin = @mMin + 1
IF @mMin > 12
BEGIN
SET @yMin = @yMin + 1
SET @mMin = 1
END
END

SELECT * FROM @monthsYears -- No rows!
Go to Top of Page

khtan
In (Som, Ni, Yak)

17689 Posts

Posted - 2011-05-11 : 08:21:34
because you are getting the min & max of the month separately. So if you have logindate like 2010-12-02 and 2011-02-03 you will not get any result at all.

You should get the minimum date and maximum date and then loop on that


select @minDate = MIN([loginDate]),
@maxDate = MAX([loginDate])
FROM @tmpTable

while @minDate <= @maxDate
begin
INSERT INTO @monthsYears
select right(convert(varchar(10), @minDate, 105), 7)
select @minDate = dateadd(month, 1, @minDate)
end
SELECT * FROM @monthsYears



KH
[spoiler]Time is always against us[/spoiler]

Go to Top of Page

R
Constraint Violating Yak Guru

328 Posts

Posted - 2011-05-11 : 08:32:39
Hi khtan you just beat me there...

I had a play around and did this instead, although I'm not sure how effecient it is...


DECLARE @monthsYears TABLE (monthYear nvarchar(10))
DECLARE @mMin INT
DECLARE @mMax INT
DECLARE @yMin INT
DECLARE @yMax INT
DECLARE @minDate datetime
DECLARE @maxDate datetime
SET @minDate = (SELECT MIN([loginDate]) FROM @tmpTable)
SET @maxDate = (SELECT MAX([loginDate]) FROM @tmpTable)
SET @mMin = (SELECT MIN(MONTH(@minDate)))
SET @yMin = (SELECT MIN(YEAR(@minDate)))
SET @mMax = (SELECT MAX(MONTH(@maxDate)))
SET @yMax = (SELECT MAX(YEAR(@maxDate)))
PRINT @mMin -- '6'
PRINT @yMin -- '2006'
PRINT @mMax -- '5'
PRINT @yMax -- '2011'
DECLARE @dateReached bit
SET @dateReached = 0

WHILE @dateReached = 0
BEGIN
IF @yMin >= @yMax AND @mMin >= @mMax
BEGIN
SET @dateReached = 1
END
INSERT INTO @monthsYears
(
monthYear
)
VALUES
(
cast(@yMin AS nvarchar) + '-' + cast(@mMin AS nvarchar)
)
SET @mMin = @mMin + 1
IF @mMin > 12
BEGIN
SET @yMin = @yMin + 1
SET @mMin = 1
END
END

SELECT * FROM @monthsYears
Go to Top of Page

R
Constraint Violating Yak Guru

328 Posts

Posted - 2011-05-11 : 08:39:53
quote:
Originally posted by khtan

because you are getting the min & max of the month separately. So if you have logindate like 2010-12-02 and 2011-02-03 you will not get any result at all.



I tried your suggestion out but there was a problem. If the DAY integer of the end date is less than the DAY integer of the start date, it misses off the last month.


DECLARE @monthsYears TABLE (monthYear nvarchar(10))
DECLARE @minDate datetime
DECLARE @maxDate datetime
SET @minDate = (SELECT MIN([loginDate]) FROM @tmpTable)
SET @maxDate = (SELECT MAX([loginDate]) FROM @tmpTable)
WHILE @minDate <= @maxDate
BEGIN
INSERT INTO @monthsYears
(
monthYear
)
VALUES
(
cast(YEAR(@minDate) AS nvarchar) + '-' + cast(MONTH(@minDate) AS nvarchar)
)
SET @minDate = DATEADD(Month, 1, @minDate)
END
SELECT * FROM @monthsYears


Is there a way to modify the start date to make it the first day of that respective month, and modify the end date to make it the last day of that month? I think this would get around that problem...
Go to Top of Page

khtan
In (Som, Ni, Yak)

17689 Posts

Posted - 2011-05-11 : 08:56:45
quote:
I tried your suggestion out but there was a problem. If the DAY integer of the end date is less than the DAY integer of the start date, it misses off the last month.

true. i missed that.

this will convert the day to 1st of the month and over come that.

select @minDate = dateadd(day, datediff(day, 0, MIN([loginDate])), 0),
@maxDate = dateadd(day, datediff(day, 0, MAX([loginDate])), 0)
FROM @tmpTable



KH
[spoiler]Time is always against us[/spoiler]

Go to Top of Page

R
Constraint Violating Yak Guru

328 Posts

Posted - 2011-05-11 : 09:06:55
quote:
Originally posted by khtan
this will convert the day to 1st of the month and over come that.

select @minDate = dateadd(day, datediff(day, 0, MIN([loginDate])), 0),
@maxDate = dateadd(day, datediff(day, 0, MAX([loginDate])), 0)
FROM @tmpTable




Hmmmm. Not quite sure I understand what you've done there, however, it is still missing off the last month!
Go to Top of Page

khtan
In (Som, Ni, Yak)

17689 Posts

Posted - 2011-05-11 : 09:14:37
very sorry, should by month instead of day


select @minDate = dateadd(month, datediff(month, 0, MIN([loginDate])), 0),
@maxDate = dateadd(month, datediff(month, 0, MAX([loginDate])), 0)
FROM @tmpTable



KH
[spoiler]Time is always against us[/spoiler]

Go to Top of Page

R
Constraint Violating Yak Guru

328 Posts

Posted - 2011-05-11 : 09:22:27
LOL not a problem. Works like a charm.

I don't suppose I could ask you to explain the logic of the statement please?

Thanks!
Go to Top of Page

khtan
In (Som, Ni, Yak)

17689 Posts

Posted - 2011-05-11 : 10:21:51
sure

1. this "datediff(month, 0, <date>)" = <1> will give you the different in terms of month between the <date> and 0 which is 1900-01-01.
2. dateadd(month, < 1 >, 0) adding the number of month to 1900-01-01 will give you back the date with day = 1
3. The end result is you will always get 1st of the month of the input <date>.


KH
[spoiler]Time is always against us[/spoiler]

Go to Top of Page

khtan
In (Som, Ni, Yak)

17689 Posts

Posted - 2011-05-11 : 10:23:19
similarly,

if you have a date with time (eg getdate()), you can use similar technique

try

select dateadd(day, datediff(day, 0, getdate()), 0),
dateadd(month, datediff(month, 0, getdate()), 0),
dateadd(year, datediff(year, 0, getdate()), 0)



KH
[spoiler]Time is always against us[/spoiler]

Go to Top of Page

R
Constraint Violating Yak Guru

328 Posts

Posted - 2011-05-11 : 10:58:39
That's great. Thank you very much!
Go to Top of Page
   

- Advertisement -