I have two date columns and I would like to calculate their differences in Months/Days and exclude years.
Assuming the first date is 10/30/2017, comparing it to the current date, its difference should one. If the current date is 10/30/2018, the difference should also be one.
A few examples:
Schedule Date:10/30/2017 Current Date 10/29/2017 Diff 1
Schedule Date:10/30/2017 Current Date 11/30/2017 Diff 30
Schedule Date:10/30/2017 Current Date 10/29/2018 Diff 1
Schedule Date:10/30/2017 Current Date 11/30/2018 Diff 30
Schedule Date:10/30/2017 Current Date 10/29/2019 Diff 1
Schedule Date:10/30/2017 Current Date 11/30/2019 Diff 30
Try This
SELECT ABS(365 * DATEDIFF(year, '10/30/2017', '11/30/2018')
- DATEDIFF(day, '10/30/2017', '11/30/2018')) AS DateDiff;
Note that the difference between 10/30/ and 11/30/ can't be 30 days as you have shown.It is 31 days.
You can use Common Table Expression to get your results like below :
DECLARE #Schedule_Date datetime = '10/30/2017'
DECLARE #Current_Date datetime = '11/30/2019'
;WITH usingCTE AS
(
SELECT CAST(STUFF(CONVERT(varchar, #Schedule_Date, 102), 1, 4, CAST(YEAR(#Current_Date) AS varchar)) AS datetime) AS Schedule_Date
)
SELECT abs(DATEDIFF(day, #Current_Date, Schedule_Date)) FROM usingCTE
Another approach of the query :
DECLARE #Schedule_Date datetime = '10/30/2017'
DECLARE #Current_Date datetime = '11/30/2017'
SELECT ABS(DATEDIFF(day,
REPLACE(#Schedule_Date, DATEPART(year, #Schedule_Date),
DATEPART(year, #Current_Date)), -- replace the year with current year
#Current_Date))
This might work for you, first adjust the date to be in the same year, then calculate the number of days different between the two dates:
DECLARE #ScheduleDate DATE = '2017-10-30';
DECLARE #Dates TABLE (CurrentDate DATE);
INSERT INTO #Dates VALUES ('2017-10-29'),('2017-11-30'),('2018-10-29'),('2018-11-30'),('2019-10-29'),('2019-11-30');
SELECT #ScheduleDate ScheduleDate, *
FROM #Dates a
CROSS APPLY (SELECT AdjustedDate=DATEADD(YEAR, YEAR(#ScheduleDate) - YEAR(a.CurrentDate), a.CurrentDate)) b
CROSS APPLY (SELECT Diff=ABS(DATEDIFF(DAY, #ScheduleDate, b.AdjustedDate))) c
I need to create a patient census report that shows average number patients present per hour and per day of a week over a given time period. This would allow me to show, for example, over the last 6 months there was an average of 4 people in the ER on Mondays. I have a table valued function that will show the following for patients:
VisitID, FromDateTime, ThruDateTime, LocationID.
I was able to show the number of patients in, for example, the ER for a given day using the code below. But it is limited to one day only. (Adapted from http://www.sqlservercentral.com/Forums/Topic939818-338-1.aspx).
--Census Count by Date Range--
DECLARE #BeginDateParameter DateTime, #EndDateParameter DateTime
SET #BeginDateParameter = '20160201'
SET #EndDateParameter = '2016-02-01 23:59:59.000'
----------------------------------------------------
-- Create a temp table to hold the necessary values
-- plus an extra "x" field to track processing
----------------------------------------------------
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL DROP TABLE #Temp
CREATE TABLE #Temp (ID INT Identity NOT NULL, VisitID VarChar(100), SourceID VarChar(100),
FromDateTime DateTime, ThruDateTime DateTime, x INT)
----------------------------------------------------
-- Populate the temp table with values from the
-- the actual table in the database
----------------------------------------------------
INSERT INTO #Temp
SELECT VisitID, FromDateTime, ThruDateTime
FROM PatientFlowTable(BeginDateParameter,#EndDateParameter)
WHERE (FromDateTime BETWEEN #BeginDateParameter AND #EndDateParameter +1
OR ThruDateTime BETWEEN #BeginDateParameter AND #EndDateParameter +1)
AND LocationID = 'ER'
-- Given Period is taken as inclusive of given hours in the input (eg. 15:25:30 will be taken as 15:00:00)
-- frist make sure that the minutes, seconds and milliseconds are removed from input range for clarity
set #BeginDateParameter = dateadd(hh, datepart(hh,#BeginDateParameter), convert(varchar(12),#BeginDateParameter,112))
set #EndDateParameter = dateadd(hh, datepart(hh,#EndDateParameter), convert(varchar(12),#EndDateParameter,112))
-- you may create this CTE by other ways (eg. from permanent Tally table)...
;with dh
as
(
select top 24
DATEADD(hour,ROW_NUMBER() OVER (ORDER BY [Object_id])-1,convert(varchar(12),#BeginDateParameter,112)) as HoDstart
,DATEADD(hour,ROW_NUMBER() OVER (ORDER BY [Object_id]),convert(varchar(12),#BeginDateParameter,112)) as HoDend
,ROW_NUMBER() OVER (ORDER BY Object_id)-1 as DayHour
from sys.columns -- or any other (not very big) table which have more than 24 raws, just remamber to change
-- [Object_id] in OVER (ORDER BY [Object_id]... to some existing column
)
select d.DayHour, count(w.VisitID) as PatientCount
from dh d
left join #Temp w
on w.[FromDateTime] < d.HoDend
and w.[ThruDateTime] >= d.HoDstart
where d.HoDstart between #BeginDateParameter and #EndDateParameter
group by d.DayHour
order by d.DayHour
SELECT VisitID, FromDateTime, ThruDateTime
FROM PatientFlowTable(BeginDateParameter,#EndDateParameter)
WHERE (FromDateTime BETWEEN #BeginDateParameter AND #EndDateParameter +1
OR ThruDateTime BETWEEN #BeginDateParameter AND #EndDateParameter +1)
AND LocationID = 'ER'
Output example for the first three hours show patients that were present in the ER by taking into account their departure time.
Hour PatientCount
0 2
1 3
2 3
For querying short time periods, I would create a table-valued function that generates the hour entries. The results table can be joined into your query.
CREATE FUNCTION [dbo].[f_hours] (#startDateTime DATETIME,
#endDateTime DATETIME)
RETURNS #result TABLE (
[dateTime] DATETIME PRIMARY KEY
)
AS
BEGIN
DECLARE
#dateTime DATETIME = #startDateTime,
#hours INT = DATEDIFF(hour, #startDateTime, #endDateTime)
WHILE (#dateTime <= #endDateTime)
BEGIN
INSERT
INTO #result
VALUES (#dateTime)
SET #dateTime = DATEADD(hour, 1, #dateTime)
END
RETURN
END
GO
The time required by the function can be output with SET STATISTICS TIME ON. For the generation of over 6000 records needs my computer 53 ms.
SET STATISTICS TIME ON
SELECT *
FROM [dbo].[f_hours]('2016-02-01', '2016-02-10 16:00')
SET STATISTICS TIME OFF
I have data of the following format:
Date Value
08/28 100
09/01 1
09/01 5
09/10 2
I would like my output to be:
Date Value
08/28 100
08/29 100
08/30 100
08/31 100
09/01 106
09/02 106
.
.
.
09/10 108
I'm just getting started with SQL, so any help would be appreciated. What I have right now is below, but that's not really close to what I seek:
SELECT Date, COUNT(DISTINCT(Service)) AS Value
FROM [Directory]
WHERE Date <= #myDate
GROUP BY Date ORDER BY Date
First, you can use a sub query to get the aggregate values
SELECT Date, (SELECT SUM(Value) FROM Directory d WHERE d.Date <= Directory.Date)
FROM [Directory]
WHERE Date <= #myDate
ORDER BY Date
Which would give you something that looks like this:
Date Value
08/28 100
09/01 101
09/01 106
09/10 108
Then you can add a Date table as sgeddes suggested. This article explains if fairly well: http://michaelmorley.name/how-to/create-date-dimension-table-in-sql-server
Then you can modify your query like so
SELECT DateTable.Date, (SELECT SUM(Value) FROM Directory d WHERE d.Date <= Directory.Date)
FROM [Directory] LEFT OUTER JOIN DateTable on Directory.Date = DateTable.Date
WHERE DateTable.Date <= #myDate
ORDER BY DateTable.Date
To get the data format you're looking for.
Based on sgeddes suggestion:
SELECT a.Date, COUNT(DISTINCT(d.Service)) AS Value
FROM [Directory] d
LEFT OUTER JOIN [Date Table] a on d.Date = a.Date
WHERE Date <= #myDate
GROUP BY Date
ORDER BY Date
Use following script in sqlserver :
BEGIN
--If exist then drop temp tables
DROP TABLE #YOURTABLE;
DROP TABLE #TEST1;
DECLARE #MINDATE DATETIME;
DECLARE #MAXDATE DATETIME;
CREATE TABLE #YOURTABLE(
CDATE DATE,
VALUE INT
);
INSERT INTO #YOURTABLE VALUES ('08/28/2014',100),('09/01/2014',1),('09/01/2014',5),('09/10/2014',100);
--select start date and end date from your table
SELECT #MINDATE=MIN(CDATE),#MAXDATE=MAX(CDATE) FROM #YOURTABLE;
CREATE TABLE #TEST1(
CDATE DATE,
VALUE INT
);
;WITH CALENDAR
AS (
SELECT #MINDATE CDATE
UNION ALL
SELECT CDATE + 1
FROM CALENDAR
WHERE CDATE + 1 <= #MAXDATE
)
-- insert all dates with 0 value in temp table
INSERT INTO #TEST1 SELECT CDATE,0 FROM CALENDAR;
--delete dates which are already there in your table
DELETE FROM #TEST1 WHERE CDATE IN (SELECT CDATE FROM #YOURTABLE)
-- insert all dates with values from your table to temporary table which holds dates which are not in your table
INSERT INTO #TEST1 SELECT * FROM #YOURTABLE;
SELECT T1.CDATE,(SELECT SUM(VALUE) FROM #TEST1 T2 WHERE T2.CDATE<=T1.CDATE) FROM #TEST1 T1
END
I'm having one holiday table based on that I need to return last working date.
Table_holiday
Id Date Text
1 2013-03-29 Good Friday
2 2013-05-01 Maharashtra day
3 2013-05-02 Holiday
When I execute my date_recursive function it should have to check and return me last_business date
For example
If I execute it on #date datetime = '2013-03-29' it should return me last working date '2013-03-28'as this is the last working date
any help m newbie in sql.
Please try:
DECLARE #Table_holiday as TABLE(id int, [date] datetime, [Text] nvarchar(50))
insert into #Table_holiday values(1, '2013-03-29', 'Good Friday')
insert into #Table_holiday values(2, '2013-05-01', 'Maharashtra day')
insert into #Table_holiday values(3, '2013-05-02', 'Holiday')
declare #date datetime
set #date ='2013-03-29'
;with T(dt) as
(
select #date union all
select T1.[date] from T inner join #Table_holiday T1 on T1.[date]=T.dt-1
)select min(dt)-1 from T
Assuming ANY date not in your holiday table is a working date, you can search backwards until you find a non-holiday.
;with cte(adate) as (
select #date
from table_holiday
where #date = Date
union all
select h.Date
from cte
join table_holiday h on dateadd(d,-1,cte.adate) = h.Date
)
select isnull((select dateadd(d,-1,min(adate)) from cte), #date);
select (case when DATEDIFF(day,GETDATE(),'2008-08-05')<2 Then "last
working date" Else "" END) from Table_holiday
Hope its helpful.
I need to make a temporary table that holds of range of dates, as well as a couple of columns that hold placeholder values (0) for future use. The dates I need are the first day of each month between $startDate and $endDate where these variables can be several years apart.
My original sql statement looked like this:
select dbo.FirstOfMonth(InsertDate) as Month, 0 as Trials, 0 as Sales
into #dates
from customer
group by dbo.FirstOfMonth(InsertDate)
"FirstOfMonth" is a user-defined function I made that pretty much does what it says, returning the first day of the month for the provided date with the time at exactly midnight.
This produced almost exactly what I needed until I discovered there were occasionally gaps in my dates where I had a few months were there were no records insert dates. Since my result must still have the missing months I need a different approach.
I have added the following declarations to the stored procedure anticipating their need for the range of the dates I need ...
declare $startDate set $startDate = select min(InsertDate) from customer
declare $endDate set $endDate = select max(InsertDate) from customer
... but I have no idea what to do from here.
I know this question is similar to this question but, quite frankly, that answer is over my head (I don't often work with SQL and when I do it tends to be on older versions of SQL Server) and there are a few minor differences that are throwing me off.
I needed something similar, but all DAYS instead of all MONTHS.
Using the code from MatBailie as a starting point, here's the SQL for creating a permanent table with all dates from 2000-01-01 to 2099-12-31:
CREATE TABLE _Dates (
d DATE,
PRIMARY KEY (d)
)
DECLARE #dIncr DATE = '2000-01-01'
DECLARE #dEnd DATE = '2100-01-01'
WHILE ( #dIncr < #dEnd )
BEGIN
INSERT INTO _Dates (d) VALUES( #dIncr )
SELECT #dIncr = DATEADD(DAY, 1, #dIncr )
END
This will quickly populate a table with 170 years worth of dates.
CREATE TABLE CalendarMonths (
date DATETIME,
PRIMARY KEY (date)
)
DECLARE
#basedate DATETIME,
#offset INT
SELECT
#basedate = '01 Jan 2000',
#offset = 1
WHILE (#offset < 2048)
BEGIN
INSERT INTO CalendarMonths SELECT DATEADD(MONTH, #offset, date) FROM CalendarMonths
SELECT #offset = #offset + #offset
END
You can then use it by LEFT joining on to that table, for the range of dates you require.
I would probably use a Calendar table. Create a permanent table in your database and fill it with all of the dates. Even if you covered a 100 year range, the table would still only have ~36,525 rows in it.
CREATE TABLE dbo.Calendar (
calendar_date DATETIME NOT NULL,
is_weekend BIT NOT NULL,
is_holiday BIT NOT NULL,
CONSTRAINT PK_Calendar PRIMARY KEY CLUSTERED (calendar_date)
)
Once the table is created, just populate it once in a loop, so that it's always out there and available to you.
Your query then could be something like this:
SELECT
C.calendar_date,
0 AS trials,
0 AS sales
FROM
dbo.Calendar C
WHERE
C.calendar_date BETWEEN #start_date AND #end_date AND
DAY(C.calendar_date) = 1
You can join in the Customers table however you need to, outer joining on FirstOfMonth(InsertDate) = C.calendar_date if that's what you want.
You can also include a column for day_of_month if you want which would avoid the overhead of calling the DAY() function, but that's fairly trivial, so it probably doesn't matter one way or another.
This of course will not work in SQL-Server 2000 but in a modern database where you don't want to create a permanent table. You can use a table variable instead creating a table so you can left join the data try this. Change the DAY to HOUR etc to change the increment type.
declare #CalendarMonths table (date DATETIME, PRIMARY KEY (date)
)
DECLARE
#basedate DATETIME,
#offset INT
SELECT
#basedate = '01 Jan 2014',
#offset = 1
INSERT INTO #CalendarMonths SELECT #basedate
WHILE ( DATEADD(DAY, #offset, #basedate) < CURRENT_TIMESTAMP)
BEGIN
INSERT INTO #CalendarMonths SELECT DATEADD(HOUR, #offset, date) FROM #CalendarMonths where DATEADD(DAY, #offset, date) < CURRENT_TIMESTAMP
SELECT #offset = #offset + #offset
END
A starting point of a useful kludge to specify a range or specific list of dates:
SELECT *
FROM
(SELECT CONVERT(DateTime,'2017-1-1')+number AS [Date]
FROM master..spt_values WHERE type='P' AND number<370) AS DatesList
WHERE DatesList.Date IN ('2017-1-1','2017-4-14','2017-4-17','2017-12-25','2017-12-26')
You can get 0 to 2047 out of master..spt_values WHERE type='P', so that's five and a half year's worth of dates if you need it!
Tested below and it works, though it's a bit convoluted.
I assigned arbitrary values to the dates for the test.
DECLARE #SD smalldatetime,
#ED smalldatetime,
#FD smalldatetime,
#LD smalldatetime,
#Mct int,
#currct int = 0
SET #SD = '1/15/2011'
SET #ED = '2/02/2012'
SET #FD = (DATEADD(dd, -1*(Datepart(dd, #SD)-1), #sd))
SET #LD = (DATEADD(dd, -1*(Datepart(dd, #ED)-1), #ED))
SET #Mct = DATEDIFF(mm, #FD, #LD)
CREATE TABLE #MyTempTable (FoM smalldatetime, Trials int, Sales money)
WHILE #currct <= #Mct
BEGIN
INSERT INTO #MyTempTable (FoM, Trials, Sales)
VALUES
(DATEADD(MM, #currct, #FD), 0, 0)
SET #currct = #currct + 1
END
SELECT * FROM #MyTempTable
DROP TABLE #MyTempTable
For SQL Server 2000, this stackoverflow post looks promising for a way to temporarily generate dates calculated off of a start and end date. It's not exactly the same but quite similar. This post has a very in-depth answer on truncating dates, if needed.
In case anyone stumbles on this question and is working in PostgreSQL instead of SQL Server 2000, here is how you might do it there...
PostgreSQL has a nifty series generating function. For your example, you could use this series of all days instead of generating an entire calendar table, and then do groupings and matchups from there.
SELECT current_date + s.a AS dates FROM generate_series(0,14,7) AS s(a);
dates
------------
2004-02-05
2004-02-12
2004-02-19
(3 rows)
SELECT * FROM generate_series('2008-03-01 00:00'::timestamp,
'2008-03-04 12:00', '10 hours');
generate_series
---------------------
2008-03-01 00:00:00
2008-03-01 10:00:00
2008-03-01 20:00:00
2008-03-02 06:00:00
2008-03-02 16:00:00
2008-03-03 02:00:00
2008-03-03 12:00:00
2008-03-03 22:00:00
2008-03-04 08:00:00
(9 rows)
I would also look into date_trunc from PostgreSQL using 'month' for the truncator field to maybe refactor your original query to easily match with a date_trunc version of the calendar series.
select top (datediff(D,#start,#end)) dateadd(D,id-1,#start)
from BIG_TABLE_WITH_NO_JUMPS_IN_ID
declare #start datetime
set #start = '2016-09-01'
declare #end datetime
set #end = '2016-09-30'
create table #Date
(
table_id int identity(1,1) NOT NULL,
counterDate datetime NULL
);
insert into #Date select top (datediff(D,#start,#end)) NULL from SOME_TABLE
update #Date set counterDate = dateadd(D,table_id - 1, #start)
The code above should populate the table with all the dates between the start and end. You would then just join on this table to get all of the dates needed. If you only needed a certain day of each month, you could dateadd a month instead.
SELECT P.Id
, DATEADD ( DD, -P.Id, P.Date ) AS Date
FROM (SELECT TOP 1000 ROW_NUMBER () OVER (ORDER BY (SELECT NULL)) AS Id, CAST(GETDATE () AS DATE) AS Date FROM master.dbo.spt_values) AS P
This query returns a table calendar for the last 1000 days or so. It can be put in a temporary or other table.
Create a table variable containing a date for each month in a year:
declare #months table (reportMonth date, PRIMARY KEY (reportMonth));
declare #start date = '2018', #month int = 0; -- base 0 month
while (#month < 12)
begin
insert into #months select dateAdd(month, #month, #start);
select #month = #month + 1;
end
--verify
select * from #months;
This is by far the quickest method I have found (much quicker than inserting rows 1 by 1 in a WHILE loop):
DECLARE #startDate DATE = '1900-01-01'
DECLARE #endDate DATE = '2050-01-01'
SELECT DATEADD(DAY, sequenceNumber, #startDate) AS TheDate
INTO #TheDates
FROM (
SELECT ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n + 10000*tenthousands.n AS sequenceNumber
FROM
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) hundreds(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) thousands(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tenthousands(n)
WHERE ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n + 10000*tenthousands.n <= DATEDIFF(day, #startDate, #endDate)
) theNumbers
SELECT *
FROM #TheDates
ORDER BY TheDate
The recursive answer:
DECLARE #startDate AS date = '20220315';
DECLARE #endDate AS date = '20230316'; -- inclusive
WITH cte_minutes(dt)
AS (
SELECT
DATEFROMPARTS(YEAR(#startDate), MONTH(#startDate), 1)
UNION ALL
SELECT
DATEADD(month, 1, dt)
FROM
cte_minutes
WHERE DATEADD(month, 1, dt) < #endDate
)
SELECT
dt
into #dates
FROM
cte_minutes
WHERE
dt >= #startDate
AND
dt <= #endDate
OPTION (MAXRECURSION 2000);
DROP TABLE dbo.#dates