Getting Birthdays worldwide with time difference and DST - sql-server

I have a user table with a birthday field and I am trying to get all the user ids where their birthday is today. The hard part is to account for time difference between countries and DST.
This is what I have so far:
SELECT *
FROM Users WITH(NOLOCK)
WHERE MONTH(Birthday) = MONTH(GETDATE())
AND DAY(Birthday) BETWEEN DAY(GETDATE()) - 2 AND DAY(GETDATE()) + 2
I am including the -/+ 2 days to make sure I have birthdays for people who might be several hours ahead of or behind our server time. I'm not sure where to go from here.
How can I make sure I get the right users?
EDIT:
I am using SQL Server 2005 and know the location of a user based on their bill country.

you may need to work it out using datetiemoffset:
http://msdn.microsoft.com/en-us/library/bb630289.aspx

The example below creates a test table in [tempdb] that has 48 hours of dates.
If my server is running on GMT time, I can convert all the dates stored in the table to GMT by using the offsetting to zero the constant.
-- Just playing around
use tempdb;
go
-- A simple table with dates
create table my_birthday
(
-- simple id
my_id int identity(1,1) primary key,
-- store date with offset
my_date datetimeoffset
);
-- Data with GMT offset
declare #cnt int = -48;
while #cnt < 49
begin
insert into my_birthday (my_date) values ( DATEADD(HH, -#cnt, SYSDATETIMEOFFSET()) );
set #cnt += 1;
end;
-- Pick todays data
select * from my_birthday
where datediff(d, my_date, switchoffset(CONVERT(datetimeoffset, '2013-12-12'), '+00:00') ) = 0
Looking at the output, we get 5 extra days on 12-11 since I am in the EST time zone and loose 5 days at the end of day. But we still only get 24 hours which is correct.
In short, you need the datetimeoffset data type and the switchoffset() function for this solution.

Related

T-SQL create label “week #1: 1/1/18 - 1/7/18”

Is there a way to create a table with the following?
Label
“week #1: 1/1/18 - 1/7/18”
“week #2: 1/8/18 - 1/15/18”
And so forth?
Basically, I’m looking for the week number and the date range that week includes.
I think what you want as a starting point, is a "date dimension" or "calendar table". Here's one of many examples for creating them (creating them is not really the issue though, it's how you use them that's more important).
In your example, it looks like you want to pivot the data (create a crosstab). As a rule of thumb, you're generally better off pivoting on the client application, than you are persisting that denormalised anti-pattern in a relational database.
Here's a fictitious example:
DECLARE #start_date as datetime = '20180301';
DECLARE #end_date as datetime = dateadd(dd,datediff(dd,0,GETDATE()),0);--midnight last night
SELECT cal.week_starting --The date of the start of the week eg 15 April 2018.
,dateadd(d,6,cal.week_starting) as week_ending -- The date of the last day of the week eg 21 April 2018. You can cast as varchar, format and concatenate to the previous field to suit yourself.
,my_events.my_category
,count(*) as recs
FROM my.CALENDAR cal
JOIN dbo.big_list_of_events my_events ON cal.census_dttm = my_events.event_date
WHERE my_events.event_date >= #start_date
and my_events.event_date < #end_date
GROUP BY cal.week_starting
,my_events.my_category
ORDER BY cal.week_starting
,my_events.my_category
;
Once you get to this point you're ready to query it with your client application (eg Pivot Tables in Excel) and slice and dice to your heart's content. Again, you probably don't want data stored in your db as a crosstab.

SQL update based on query results with two parameters

I've created a virtual table in SQL Server that has 28 days from the current date and each date has rows for time that range from 12-10 pm incremented by 15 min and another value to indicate that it's turned on/off for availability, so it would be something like this:
date time onoff
-------------------------------------------------
2015-04-08 12:00 1
2015-04-08 12:15 1
....continue until 22:00 then start next day
2015-04-09 12:00 1
..... continue for 28 days
I'd like to update the availability based on a query from another table which would return the date, start and end time...
So far I came up with this
update table1
set onoff = 0
where tbl1date in (select tbl2date from table2 where userid = 1)
The problem I'm having is adding in the between certain hours part of the equation and I'm not sure how to do something like this in SQL or how to even search for the answer based on not being able to word it properly...
Can someone help or point me in the right direction?
use a DATETIME, don't use separate DATE and TIME fields.
I think you should take a look at DATEDIFF (https://technet.microsoft.com/pl-pl/library/ms189794(v=sql.110).aspx) function.
Your where query could look like this:
update table1 set onoff = 0
where
DATEDIFF(minute, <MIN_RANGE>, tbl1date) >= 0 and
DATEDIFF(minute, tbl1date, <MAX_RANGE>) >= 0
How you calculate MIN_RANGE and MAX_RANGE depends on your table2 structure.
As suggested, if you have control over the structure, use datetime fields as they are easier to do the comparisons on. I'm going to assume you don't have control over the structure.
In order to compare the datetimes you need to create them from your separate date and times. You can either parse the time field for the hours and minutes and use DATEADD to add the appropriate offsets to the date, or you can use CONVERT to interpret a date time string as a date. Something like
CONVERT(datetime, SUBSTRING(CONVERT(varchar, tbl1date, 121), 1, 10) + ' ' + tbl1time, 121)
What this does is to convert the date to odbc cannonical format and throwaway the time part as it takes only the first 10 characters. Then it appends the time and interprets the whole string as a odbc cannonical datetime string. That format is yyyy-mm-dd hh:mi:ss.mmm. The hours are based on 24 hours. So if your times are in AM/PM format you're going to have to convert them.
If your other table has separate date and times you'd use a similar expression to combine them.
Once you have the datetimes you can do something like this
UPDATE table1
SET onoff = 0
WHERE <expression above> BETWEEN (SELECT min_value FROM table2) AND (SELECT max_value FROM table2)

Query to create records between two dates

I have a table with the following fields (among others)
TagID
TagType
EventDate
EventType
EventType can be populated with "Passed Inspection", "Failed Inspection" or "Repaired" (there are actually many others, but simplifies to this for my issue)
Tags can go many months between a failed inspection and the ultimate repair... in this state they are deemed to be "awaiting repair". Tags are still inspected each month even after they have been identified as having failed. (and just to be clear, a “failed inspection” doesn’t mean the item being inspected doesn’t work at all… it still works, just not at 100% capacity…which is why we still do inspections on it).
I need to create a query that counts, by TagType, Month and Year the number of Tags that are awaiting repair. The end result table would look like this, for example
TagType EventMonth EventYear CountofTagID
xyz 1 2011 3
abc 1 2011 2
xyz 2 2011 2>>>>>>>>>>>>indicating a repair had been made since 1/2011
abc 2 2011 2
and so on
The "awaiting repair" status should be assessed on the last day of the month
This is totally baffling me...
One thought that I had was to develop a query that returned:
TagID,
TagType,
FailedInspectionDate, and
NextRepairDate,
then try and do something that stepped thru the months in between the two dates, but that seems wildly inefficient.
Any help would be much appreciated.
Update
A little more research, and a break from the problem to think about it differently gave me the following approach. I'm sure its not efficient or elegant, but it works. Comments to improve would be appreciated.
declare #counter int
declare #FirstRepair date
declare #CountMonths as int
set #FirstRepair = (<Select statement to find first repair across all records>)
set #CountMonths = (<select statement to find the number of months between the first repair across all records and today>)
--clear out the scratch table
delete from dbo.tblMonthEndDate
set #counter=0
while #counter <=#CountMonths --fill the scratch table with the date of the last day of every month from the #FirstRepair till today
begin
insert into dbo.tblMonthEndDate(monthenddate) select dbo.lastofmonth(dateadd(m,#counter, #FirstRepair))
set #counter = #counter+1
end
--set up a CTE to get a cross join between the scratch table and the view that has the associated first Failed Inspection and Repair
;with Drepairs_CTE (FacilityID, TagNumber, CompType, EventDate)
AS
(
SELECT dbo.vwDelayedRepairWithRepair.FacilityID, dbo.vwDelayedRepairWithRepair.TagNumber, dbo.vwDelayedRepairWithRepair.CompType,
dbo.tblMonthEndDate.MonthEndDate
FROM dbo.vwDelayedRepairWithRepair INNER JOIN
dbo.tblMonthEndDate ON dbo.vwDelayedRepairWithRepair.EventDate <= dbo.tblMonthEndDate.MonthEndDate AND
dbo.vwDelayedRepairWithRepair.RepairDate >= dbo.tblMonthEndDate.MonthEndDate
)
--use the CTE to build the final table I want
Select FacilityID, CompType, Count(TagNumber), MONTH(EventDate), YEAR(EventDate), 'zzz' as EventLabel
FROM Drepairs_CTE
GROUP BY FacilityID, CompType, MONTH(EventDate), YEAR(EventDate)`
Result set ultimately looks like this:
FacilityID CompType Count Month Year Label
1 xyz 2 1 2010 zzz
1 xyz 1 2 2010 zzz
1 xyz 1 7 2009 zzz
Here is a recursive CTE which generates table of last dates of months in interval starting with minimum date in repair table and ending with maximum date.
;with tableOfDates as (
-- First generation returns last day of month of first date in repair database
-- and maximum date
select dateadd (m, datediff (m, 0, min(eventDate)) + 1, 0) - 1 startDate,
max(eventDate) endDate
from vwDelayedRepairWithRepair
union all
-- Last day of next month
select dateadd (m, datediff (m, 0, startDate) + 2, 0) - 1,
endDate
from tableOfDates
where startDate <= endDate
)
select *
from tableOfDates
-- If you change the CTE,
-- Set this to reasonable number of months
-- to prevent recursion problems. 0 means no limit.
option (maxrecursion 0)
EndDate column from tableOfDates is to be ignored, as it serves as upper bound only. If you create UDF which returns all the dates in an interval, omit endDate in select list or remove it from CTE and replace with a parameter.
Sql Fiddle playground is here.

SQL Server Retrieving Recurring Appointments By Date

I'm working on a system to store appointments and recurring appointments. My schema looks like this
Appointment
-----------
ID
Start
End
Title
RecurringType
RecurringEnd
RecurringTypes
---------------
Id
Name
I've keeped the Recurring Types simple and only support
Week Days,
Weekly,
4 Weekly,
52 Weekly
If RecurringType is null then that appointment does not recur, RecurringEnd is also nullable and if its null but RecurringType is a value then it will recur indefinatly. I'm trying to write a stored procedure to return all appointments and their dates for a given date range.
I've got the stored procedure working for non recurring meetings but am struggling to work out the best way to return the recurrences this is what I have so far
ALTER PROCEDURE GetAppointments
(
#StartDate DATETIME,
#EndDate DATETIME
)
AS
SELECT
appointment.id,
appointment.title,
appointment.recurringType,
appointment.recurringEnd,
appointment.start,
appointment.[end]
FROM
mrm_booking
WHERE
(
Start >= #StartDate AND
[End] <= #EndDate
)
I now need to add in the where clauses to also pick up the recurrences and alter what is returned in the select to return the Start and End Dates for normal meetings and the calculated start/end dates for the recurrences.
Any pointers on the best way to handle this would be great. I'm using SQL Server 2005
you need to store the recurring dates as each individual row in the schedule. that is, you need to expand the recurring dates on the initial save. Without doing this it is impossible to (or extremely difficult) to expand them on the fly when you need to see them, check for conflicts, etc. this will make all appointments work the same, since they will all actually have a row in the table to load, etc. I would suggest that when a user specifies their recurring date, you make them pick an actual number of recurring occurrences. When you go to save that recurring appointment, expand them all out as individual rows in the table. You could use a FK to a parent appointment row and link them like a linked list:
Appointment
-----------
ID
Start
End
Title
RecurringParentID FK to ID
sample data:
ID .... RecurringParentID
1 .... null
2 .... 1
3 .... 2
4 .... 3
5 .... 4
if in the middle of the recurring appointments schedule run, say ID=3, they decide to cancel them, you can follow the chain and delete the remaining ID=3,4,5.
as for expanding the dates, you could use a CTE, numbers table, while loop, etc. if you need help doing that, just ask. the key is to save them as regular rows in the table so you don't need to expand them on the fly every time you need to display or evaluate them.
I ended up doing this by creating a temp table of everyday between the start and end date along with their respective day of the week. I limited the recurrence intervals to weekdays and a set amount of weeks and added where clauses like this
--Check Week Days Reoccurrence
(
mrm_booking.repeat_type_id = 1 AND
#ValidWeeklyDayOfWeeks.dow IN (1,2,3,4,5)
) OR
--Check Weekly Reoccurrence
(
mrm_booking.repeat_type_id = 2 AND
DATEPART(WEEKDAY, mrm_booking.start_date) = #ValidWeeklyDayOfWeeks.dow
) OR
--Check 4 Weekly Reoccurences
(
mrm_booking.repeat_type_id = 3 AND
DATEDIFF(d,#ValidWeeklyDayOfWeeks.[Date],mrm_booking.start_date) % (7*4) = 0
) OR
--Check 52 Weekly Reoccurences
(
mrm_booking.repeat_type_id = 4 AND
DATEDIFF(d,#ValidWeeklyDayOfWeeks.[Date],mrm_booking.start_date) % (7*52) = 0
)
In case your interested I built up a table of the days between the start and end date using this
INSERT INTO #ValidWeeklyDayOfWeeks
--Get Valid Reoccurence Dates For Week Day Reoccurences
SELECT
DATEADD(d, offset - 1, #StartDate) AS [Date],
DATEPART(WEEKDAY,DATEADD(d, offset - 1, #StartDate)) AS Dow
FROM
(
SELECT ROW_NUMBER() OVER(ORDER BY s1.id) AS offset
FROM syscolumns s1, syscolumns s2
) a WHERE offset <= DATEDIFF(d, #StartDate, DATEADD(d,1,#EndDate))
Its not very elegant and probably very specific to my needs but it does the job I needed it to do.

TSQL Query - return all seconds between two dates

I need a TSQL query which returns all seconds since a given begin date. I am going to use this to left outer join against another table to see how many bytes were downloaded each second. I am using Sql Server 2008.
I'm shooting from the hip here, but here's a start:
DECLARE #STARTDATE DATETIME
DECLARE #ENDDATE DATETIME
SET #STARTDATE = '05/01/2010'
SET #ENDDATE = GETDATE()
-- Find the seconds between these two dates
SELECT DATEADD(SECOND, Number, #StartDate) AS N
FROM Numbers
WHERE Number < DATEDIFF(SECOND, #STARTDATE, #ENDDATE)
This assumes a table called Numbers, with a column named Number containing values from 1 up. The be able to get results for an entire month, you're going to need to have values up to around 2.5 million. I'd keep the query down to perhaps a day, meaning the Numbers table can get away with values less than 100,000.
Here's a great article on numbers tables: http://www.sqlservercentral.com/articles/Advanced+Querying/2547/
Registration is required, but it's free. If you do any serious SQL Server programming this site is quite useful.
You will likely need an Auxiliary numbers table for this. Do you need all seconds represented or can you just round to the nearest second and group by that?
Also how many seconds are we talking about here and what format do you currently have them stored in. Are they already rounded?
If not then maybe to avoid the overhead of rounding them or doing a BETWEEN type query every time (as well as the repeated DATEADDs) maybe you could use Marc's DATEDIFF answer at insert/update time to store the seconds from some base date then just join onto the numbers table using the calculated numeric column.
Code to create Numbers table from here http://web.archive.org/web/20150411042510/http://sqlserver2000.databases.aspfaq.com/why-should-i-consider-using-an-auxiliary-numbers-table.html Just to add to Brad's answer.
CREATE TABLE dbo.Numbers
(
Number INT IDENTITY(1,1) PRIMARY KEY CLUSTERED
)
WHILE COALESCE(SCOPE_IDENTITY(), 0) <= 1000000
BEGIN
INSERT dbo.Numbers DEFAULT VALUES
END
DATEDIFF ( s , #FirstDate , #SecondDate )

Resources