tsql intesect between 2 datetime recordsets - sql-server

I have an appointments table with appointments for a number of 'resources'
what i need to do is query that and return (for a particular resource) all free appointment slots across a date range.
i had thought the best way to approach this would be to generate a temp table of possible appointment times (as the length of appointment may be 30/60/90 minutes - the appointment length would be specified for the query.) and then select the intersect of those two recordsets. i.e. all of those - across the date range - where there are NOT appointments in the appointments table. thus returning all possible appointments for that resource.
or maybe just - again - generate the records of possible appointment datetimes, and then except the actual appointments already booked..?
unless of course someone can suggest an easier option.?
also not entirely sure how to generate the table of possibles ie a table with records for 2010-12-08 09:00, 2010-12-08 10:00, and so on (for 1 hr appointments)...
any ideas?
edit: have a vague idea on the possibles...
DECLARE #startDate DateTime
DECLARE #EndDate DateTime
set #startDate = '2010-12-08 09:00'
set #endDate = '2010-12-11 09:00';
with mycte as
(
select cast(#startDate as datetime) DateValue
union all
select dateadd(mi,30,DateValue)
from mycte
where DateValue <= #endDate
and datepart(hh, dateadd(mi,30,DateValue)) Between 9 AND 16
)
select DateValue
from mycte

This is a classic gaps and islands problem. It essentially a common problem where you need to identify missing values (gaps) in a sequence. Fortunately there is a free sample chapter on this very topic from the Manning book, SQL Server MVP Deep Dives. Hopefully it will provide inspiration as it gives guidance on a number of possible approaches.
http://www.manning.com/nielsen/SampleChapter5.pdf
Here is Itzik Ben-Gan's description of the problem, quoted from the above chapter.
Gaps and islands problems involve
missing values in a sequence
... The sequences involved can also
be temporal, such as order dates, some
of which are missing due to inactive
periods (weekends, holidays). Finding
periods of inactivity is an example of
the gaps problem, and finding periods
of activity is an example of the
islands problem.

Related

Workaround on Sliding Window Function in Snowflake

I've stumbled upon a problem that is giving me huge headaches, which is the following:
I have a table Deals, that contains information about this entity from our Sales CRM. I also have a table Company, that contains information about the companies pegged to those deals.
I was asked to compute a metric called Pipeline Conversion Rate, which is calculated as:
Won deals / Created Deals
Until here, everything is quite clear. Nevertheless, when computing this metric I was asked to do so in a sliding-window-function-fashion, which means to compute the metric only looking at the prior 90 days. Thing is that to look at the last 90 days of the numerator, we need to use one Date (created date); while when looking at the prior 90 days of the denominator, we should take into account the closed date (both dimensions are part of the Deals table).
There wouldn't be any problem if we could do this kind of window functions in Snowflake, as the following (I know syntax may not be exactly this one, but you get the idea):
count(deal_id) over (
partition by is_inbound, sales_agent, sales_tier, country
order by created_date range between 90 days preceding and current row
) as created_deals_last_90_days,
count(case when is_deal_won then deal_id end) over (
partition by is_inbound, sales_agent, sales_tier, country
order by created_date range between 90 days preceding and current row
) as won_deals_last_90_days
But we can't as far as I know. So my current workaround is the following (taken from this post):
select
calendar_date,
is_inbound,
sales_tier,
sales_agent,
country,
(
select count(deal_id)
from deals
where d.is_inbound = is_inbound
and d.sales_tier = sales_tier
and d.sales_agent = sales_agent
and d.country = country
and created_date between cal.calendar_date - 90 and cal.calendar_date
) as created_deals_last_90_days,
(
select count(case when is_deal_won then deal_id end)
from deals
where d.is_inbound = is_inbound
and d.sales_tier = sales_tier
and d.sales_agent = sales_agent
and d.country = country
and closed_date between cal.calendar_date - 90 and cal.calendar_date
) as won_deals_last_90_days
from calendar as cal
left join deals as d on cal.calendar_date between d.created_date and d.closed_date
*Note that I am using a calendar table here as base table, in order to have visibility on all calendar dates since without it I might say I'd be missing on those dates where there are no new deals (could happen on weekends).
Problem is that I am not getting correct figures when I cross check the raw data and the output of this query, and I have no idea how to make this (ugly) workaround, well... work.
Any ideas are more than welcome!
Well, it turns out it was way easier than I expected. After some trial-and-error, I figured out the only thing that could be failing was the JOIN condition in the outer query:
on cal.calendar_date between d.created_date and d.closed_date
This was assuming that both dates needed to be in the range, while this assumption is wrong. By tweaking the above mentioned part of the outer query to:
on cal.calendar_date >= d.created_date
It captures all those Deals that were created on or before the calendar_date, and therefore all of them since it is a mandatory field.
Maintaining the rest of the query as is, and assuming that there will be no nulls in any of the partitions, the results are the ones I expected.

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.

How to Get rows that are added 'x' minutes before in sqlserver?

I want to get all rows that have being added 'x' minutes before.
SELECT [PromoCodeID]
,[CustomerID]
,[DiscountAmount]
,[AddedBy]
,[AddedDate]
,[ModifiedBy]
,[ModifiedDate]
FROM [tbl_PromoCodesNewCustomer]
Where....
Eg: Records added 30min before from todays date time
NOTE: Record Added date is added in the field 'AddedDate' which has DATETIME datatype.
You can use this:
SELECT [PromoCodeID]
,[CustomerID]
,[DiscountAmount]
,[AddedBy]
,[AddedDate] AS added
,[ModifiedBy]
,[ModifiedDate]
FROM [tbl_PromoCodesNewCustomer]
WHERE DATEADD(minute,x,added) > GETDATE()
Where "x" in DATEADD is the number of minutes you want.
i suggest a light variation on FirstHorizon answer:
SELECT [PromoCodeID]
,[CustomerID]
,[DiscountAmount]
,[AddedBy]
,[AddedDate] AS added
,[ModifiedBy]
,[ModifiedDate]
FROM [tbl_PromoCodesNewCustomer]
WHERE AddedDate < DATEADD(minute * -1,x,getdate())
the change may look minor but depending on the number of involved rows, indexes and some other factor evaluated by the query optimizer this query may perform way better because there is no calc to make on the data.
here is an article that explain the reason (look at the second paragraph, 'Using Functions in Comparisons within the ON or WHERE Clause').
EDIT:
I'd advise you to populate the addedDate field with the UTC date and time, otherwise you could have problems with data managed among different servers \ time zones or on time change days.
So, if we consider this, the where clause will be:
WHERE DATEDIFF(mi,addedDate,GETUTCDATE)<30

SQL Server: calculate working time

How I can calculate the working time in SQL Server between two datetime variables, excluding the holidays?
Any ideas?
Holidays aren't universal - they depends very much on your location. Not even the fact which days of the week are "working" days is the same - it depends on your location.
Because of that, a general, universal answer will not be possible, and for that reason, there's also no system-provided function in T-SQL for doing this. How would SQL Server know what holidays you have in your corner of the world??.
You need to have a table of your holidays somewhere in your system and handle it yourself.
Some posts that might be of some help to you:
Calculate Number of Working Days in SQL Server: this just basically removes any Saturdays and Sundays - but doesn't include other holidays
How do I count the number of business days between two dates? : shows the same main approach, with the addition of a table that contains other holidays like Easter, 4th of July (US National Holiday) and so on
Like marc_s says, you currently need a custom solution. I really hope Microsoft adds some standard functionality: it's tough to get right, and holidays are pretty much standardized by location.
Here's an example:
declare #start_date datetime
declare #end_date datetime
set #start_date = '2010-12-20'
set #end_date = '2010-12-26'
-- A table with all non-working days. This just adds Christmass, but you
-- probably should add weekends as well.
declare #non_working_days table (dt datetime)
insert #non_working_days values ('2010-12-25'), ('2010-12-26')
-- Remove the time part
set #start_date = DATEADD(D, 0, DATEDIFF(D, 0, #start_date))
set #end_date = DATEADD(D, 0, DATEDIFF(D, 0, #end_date))
-- Find the number of non-working-days
declare #nwd_count int
select #nwd_count = count(*)
from #non_working_days
where dt >= #start_date and dt < #end_date
-- Print result
select datediff(DAY, #start_date, #end_date) - #nwd_count
This prints 5, because the 25th is not a working day.
Have a table which has a row for every date you're interested in, and, say, a "working hours" column, or just a "working day" indicator if you want to do it at day granularity. (I find this approach makes the final SQL simpler, plus enables all sorts of other useful queries, but then I'm into data warehousing, rather than operational databases, so you may find the "just list the holidays" approach better, depending...)
You will, of course, have to create that table yourself, working from some feed of holiday dates for the region you're interested in.
Typically you can project these forward at least a year, as most public holidays are agreed a long way in advance (though there are some that pop up at the "last minute" -- in the UK, for example, 29 April will be an extra public holiday in 2010, as there's a royal wedding taking place, and we got less than a year's notice of that.
Then you just
SELECT
SUM(working_hours)
FROM
all_dates
WHERE
the_date BETWEEN #start_date AND #end_date
If you want to do this internationally, it gets incredibly difficult to get your data; there's no sensible source that I know of for international holiday dates, and different regions in a "country" might have different dates -- e.g. you may know that someone's in the United Kingdom, but unless you know if they're in Scotland or not, you won't know if the first two days of the year are a public holiday, or just the first...

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