Dated Between 2 date variable - sql-server

I have a table with Customers IDs,Name Room Number,Start Date and End date. I want to create a list of dates (daily breakdown) between the start date and end date but want it to look like the tbale i have mocked up in excel in the link below:
i have used
=IF(TEXT(D2,"DD/MM/YYYY")=TEXT(E2,"DD/MM/YYYY"),"Same",IF(F2=DATE(YEAR(D2),MONTH(D2),DAY(D2)),"Start",IF(F2=DATE(YEAR(E2),MONTH(E2),DAY(E2)),"End","Between"))) formula in the Logic ColumN
I have tried to intregrate the following bit of SQL into my query but cant seem to get it to do what i want:
declare #fromdate dateTIME
set #fromdate= '1/1/2017'(Wanted to used Column Startdate)
declare #todate dateTIME = getdate()-1(Wanted used Column Enddate)
;with CTE AS
(SELECT #fromdate AS RESULT
UNION ALL
SELECT RESULT + 1 FROM CTE WHERE RESULT<=#todate
)
SELECT Result,1 as Count FROM CTE OPTION(MAXRECURSION 0)
Any help would be gratefully appreciated.

It is not completely clear what isn't working in your query. If it entails getting the rows per day, the easiest way to go about this is to actually have a date-table ready in your database. You can refer to this date-table in your view. See below an example which I have used in multiple projects.
CREATE PROCEDURE [dbo].[FillDateTable](#StartDate DATE, #EndDate DATE) AS
BEGIN
TRUNCATE TABLE dbo.DateTable;
WITH CTERecursive AS (
SELECT #StartDate AS 'CalendarDate'
UNION ALL
SELECT DATEADD(DAY,1,CalendarDate) AS 'CalendarDate'
FROM CTERecursive
WHERE DATEADD(DAY,1,CalendarDate)<=#EndDate
), TotalCalendar AS (
SELECT CalendarDate
, DAY(CalendarDate) AS 'Day'
, MONTH(CalendarDate) AS 'Month'
, YEAR(CalendarDate) AS 'Year'
, DATEADD(DAY,-DATEPART(WEEKDAY,CalendarDate)+1,CalendarDate) AS 'FirstDayOfWeek'
, DATEADD(DAY,6,DATEADD(DAY,-DATEPART(WEEKDAY,CalendarDate)+1,CalendarDate)) AS 'LastDayOfWeek'
FROM CTERecursive
)
INSERT INTO dbo.DateTable ([CalendarDate], [Day], [Month], [Year], [FirstDayOfWeek], [LastDayOfWeek], [NewWeek])
SELECT *
, NewWeek=CASE WHEN CalendarDate=FirstDayOfWeek THEN 1 ELSE 0 END
FROM TotalCalendar
OPTION (MAXRECURSION 0)
END
Furthermore, it'd be wise to use DATEADD instead of +1 in your CTE.
DECLARE #fromdate DATE
SET #fromdate=CONVERT(DATE,'20170101',112)
DECLARE #todate DATE=GETDATE()-1
;
WITH CTE AS
(SELECT #fromdate AS RESULT
UNION ALL
SELECT DATEADD(DAY,1,RESULT) FROM CTE WHERE RESULT<=#todate
)
SELECT [DateInclusive]=RESULT
,1 as Count FROM CTE OPTION(MAXRECURSION 0)
UPDATE: I'll presume you have a table with the Id, Room, Name, Start- and Enddate readily available. Your query could be structured as follows, yielding an overview similar to your Excel mockup:
DECLARE #Bookings TABLE ([Id] VARCHAR(30), [Room] INT, [Name] VARCHAR(255), [Startdate] DATE, [Enddate] DATE)
INSERT INTO #Bookings VALUES ('V123789',8,'H. Brown','20170201','20170315'),('V555589',1,'J. Frost','20170205','20170420');
WITH CTE AS
(SELECT Id, [Startdate] AS InclusiveDate FROM #Bookings
UNION ALL
SELECT Id, DATEADD(DAY,1,InclusiveDate) FROM CTE WHERE InclusiveDate < (SELECT Enddate FROM #Bookings WHERE Id=CTE.Id)
)
SELECT B.[Id], B.[Room], B.[Name], B.[Startdate], B.[Enddate], CTE.[InclusiveDate]
, CASE WHEN CTE.[InclusiveDate]=B.[Startdate] THEN 'Start'
WHEN CTE.[InclusiveDate]=B.[Enddate] THEN 'End'
WHEN CTE.[InclusiveDate]>B.[Startdate] AND CTE.[InclusiveDate]<B.[Enddate] THEN 'Between' END AS Logic
FROM CTE
JOIN #Bookings B on B.Id=CTE.[Id]
ORDER BY B.[Id], CTE.[InclusiveDate] ASC
OPTION(MAXRECURSION 0)

Related

Can I "left join" days between 2 dates in sql server?

There is a table in SQL Server where data is entered day by day. In this table, data is not filled in some days.
Therefore, there are no records in the table.
Sample: dataTable
I need to generate a report like the one below from this table.
Create a table with all the days of the year. I know that I can output a report by "joining" the "dataTable" table.
But this solution seems a bit strange to me.
Is there another way?
the code i use for temp date table
CREATE TABLE tempDate (
calendarDate date,
PRIMARY KEY (calendarDate)
)
DECLARE
#start DATE= '2021-01-01',
#dateCount INT= 730,
#rowNumber INT=1
WHILE (#rowNumber < #dateCount)
BEGIN
INSERT INTO tempDate values (DATEADD(DAY, #rowNumber, #start))
set #rowNumber=#rowNumber+1
END
GO
select * from tempDate
This is how I join using this table
SELECT
*
FROM
tempDate td WITH (NOLOCK)
LEFT JOIN dataTable dt WITH (NOLOCK) ON dt.reportDate = td.calendarDate
WHERE
td.calendarDate BETWEEN '2021-09-05' AND '2021-09-15'
Create a table with all the days of the year. I know that I can output a report by "joining" the "dataTable" table.
This is the way. You can generate that "table" on the fly if you really want to, but normally the best way is to simply have a calendar table.
You can use common expression tables for dates. The code you need:
IF(OBJECT_ID('tempdb..#t') IS NOT NULL)
BEGIN
DROP TABLE #t
END
CREATE TABLE #t
(
id int,
dt date,
dsc varchar(100),
)
INSERT INTO #t
VALUES
(1, '2021.09.08', 'a'),
(1, '2021.09.09', 'b'),
(1, '2021.09.12', 'c')
DECLARE #minDate AS DATE
SET #minDate = (SELECT MIN(dt) FROM #t)
DECLARE #maxDate AS DATE
SET #maxDate = (SELECT MAX(dt) FROM #t)
;WITH cte
AS
(
SELECT #minDate AS [dt]
UNION ALL
SELECT DATEADD(DAY, 1, [dt])
FROM cte
WHERE DATEADD(DAY, 1, [dt])<=#maxDate
)
SELECT
ISNULL(CAST(t.id AS VARCHAR(10)), '') AS [id],
cte.dt AS [dt],
ISNULL(t.dsc, 'No record has been entered in the table.') AS [dsc]
FROM
cte
LEFT JOIN #t t on t.dt=cte.dt
The fastest method is to use a numbers table, you can get a date list between 2 dates with that:
DECLARE #Date1 DATE, #Date2 DATE
SET #Date1 = '20200528'
SET #Date2 = '20200625'
SELECT DATEADD(DAY,number+1,#Date1) [Date]
FROM master..spt_values
WHERE type = 'P'
AND DATEADD(DAY,number+1,#Date1) < #Date2
If you go go in LEFT JOIN this select, whit your table, you have the result that you want.
SELECT *
FROM (SELECT DATEADD(DAY,number+1,#Date1) [Date]
FROM master..spt_values WITH (NOLOCK)
WHERE type = 'P'
AND DATEADD(DAY,number+1,#Date1) < #Date2 ) as a
LEFT JOIN yourTable dt WITH (NOLOCK) ON a.date = dt.reportDate
WHERE td.[Date] BETWEEN '2021-09-05' AND '2021-09-15'

Get data from the last day of the month without the use of loops or variables

I wrote a query that should select the last record of each month in a year. I'd like to create a View based on this select, that I could run later in my project, but unfortunately I can't use any while loops or variables in a view command. Is there a way to select all these records - last days of a month in a View that I can use later?
My desired effect of the view:
The query that I'm trying to implement in a view:
DECLARE #var_day01 DATETIME;
DECLARE #month int;
SET #month = 1;
DROP TABLE IF EXISTS #TempTable2;
CREATE TABLE #TempTable2 (ID int, date datetime, INP2D float, INP3D float, ID_device varchar(max));
WHILE #month < 13
BEGIN
SELECT #var_day01 = CONVERT(nvarchar, date) FROM (SELECT TOP 1 * FROM data
WHERE DATEPART(MINUTE, CONVERT(nvarchar, date)) = '59'
AND
MONTH(CONVERT(nvarchar, date)) = (CONVERT(nvarchar, #month))
ORDER BY date DESC
) results
ORDER BY date DESC;
INSERT INTO #TempTable2 (ID, date, INP2D,INP3D,ID_device)
SELECT * FROM data
WHERE DATEPART(MINUTE, CONVERT(nvarchar, date)) = '59'
AND
MONTH(CONVERT(nvarchar, date)) = (CONVERT(nvarchar, #month))
AND
DAY(CONVERT(nvarchar, date)) = CONVERT(datetime, DATEPART(DAY, #var_day01))
ORDER BY date DESC
PRINT #var_day01
SET #month = #month +1;
END
SELECT * FROM #TempTable2;
If you are actually just after the single most recent row for each month, there is no need for a while loop to achieve this. You just need to identify the max date value for each month and then filter your source data for those for those rows.
One way to achieve this is via a row_number window function:
declare #t table(id int,dt datetime2);
insert into #t values(1,getdate()-40),(2,getdate()-35),(3,getdate()-25),(4,getdate()-10),(5,getdate());
select id
,id_device
,dt
from(select id
,id_device
,dt
,row_number() over (partition by id_device, year(dt), month(dt) order by dt desc) as rn
from #t
) as d
where rn = 1;
You can add a simple where to your select statement, in where clause you will add one day to the date field and then select the day from the resultant date. If the result date is 1 then only you will select that record
the where clause for your query will be : Where Day(DATEADD(d,1,[date])) = 1

Creating a monthly report in T-SQL

I have a table with the following columns
CaseID
DateLogged
CompletionDate
I am trying to create a monthly stock report.
I need to identify monthly which cases are New, current and Completed each month
for instance, All cases logged only in August are new cases while all cases completed in august would show completed and all cases logged that are not completed will be current.
DROP TABLE IF EXISTS #temptable
-- Create date variables
SET dateformat ymd
DECLARE #datefrom datetime = CONVERT(DATETIME, '2019-04-01 00:00:00', 121)
DECLARE #dateto datetime = (SELECT CAST(DATEADD(DAY, -DAY(GETDATE()), GETDATE()) AS date))
-- Recursive date table creation
;WITH monthserial AS
(
SELECT #datefrom AS monthdate
UNION ALL
SELET DATEADD(MONTH, 1, monthdate)
FROM monthserial
WHERE monthdate < #dateto
)
SELECT MN.*
INTO #temptable
FROM monthserial MN
SELECT * FROM MainTable VW
CROSS JOIN #temptable TBL
WHERE DateLogged <= monthdate) test
You will need to use case-when for your situation:
select CaseID,
case
when not (CompletionDate is null) then 'Completed'
when DateLogged >= #input then 'New'
else 'Current'
end
from yourtable
order by DateLogged;
EDIT
Since we are interested about the last entry, we can add a where clause, like this:
select CaseID,
case
when not (CompletionDate is null) then 'Completed'
when DateLogged >= #input then 'New'
else 'Current'
end
from yourtable
where not exists (
select 1
from yourtable inner
where inner.CaseID = yourtable.CaseID and inner.CompletionDate < yourtable.CompletionDate
)
order by DateLogged;

Pivot Activity Code Days Months

I am trying to get the activity codes for specific days to show the 31 days in every month of the year for a specific staff member.
If the staff member was present, sick, holiday leave, etc... I want those activity codes to display based on the output below for a year act_date range.
Thanks!
Pivot Activity Code Days Months
This can be achieved with pivoting. Here you can enter the staff id in the query to fetch the results for that particular staff.
--create table
create table staff_info
(
staffId int,
actDate datetime,
activityCode int
)
--insert values
insert into staff_info values
(2699, '01/02/2017', 101),
(2699, '05/14/2017', 303),
(2699, '08/06/2017', 101),
(1927, '10/25/2017', 105)
--actual solution
select * from
(
select staffId, day(actDate) as act_day,month(actDate) as actual_month,
activityCode
from staff_info
where staffId=2699 ----- enter the staff id here
) src
pivot
(
sum(activityCode)
for act_day in ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],
[14],[15],
[16],[17],[18],[19],[20],[21],[22],[23],[24],[25],[26],[27],[28],[29],[30],
[31]
)
) p
Result:
Firstly, create a function which would give the date values for a specific range
CREATE FUNCTION [dbo].[GetAllDatesBetweenRange]
(
#FromDate DATE
,#ToDate DATE
)
RETURNS #Dates TABLE
(
DateVal DATE
)
AS
BEGIN
;WITH CTE
AS
(
SELECT #FromDate AS FromDate
UNION ALL
SELECT DATEADD(DD,1,FromDate)
FROM CTE
WHERE FromDate < #ToDate
)
INSERT INTO #Dates
SELECT FromDate FROM CTE
OPTION (MAXRECURSION 0)
RETURN;
END
GO
Use the below dynamic query to pivot for the specific date range
DECLARE #Sql NVARCHAR(MAX);
DECLARE #DateVal NVARCHAR(MAX);
SELECT #DateVal = STUFF((SELECT ',['+CAST(DateVal AS NVARCHAR(50))+']'
FROM [dbo].[GetAllDatesBetweenRange]('2017-01-01','2017-12-31')
FOR XML PATH('')),1,1,'')
SET #Sql = '
;WITH CTE
AS
(
SELECT Res1.STAFF_ID
,Res2.DateVal
,Res1.ACTIVITY_CODE
FROM [dbo].[GetAllDatesBetweenRange](''''2017-01-01'''',''''2017-12-31'''') Res1
LEFT JOIN TableA A ON A.ACT_DATE = Res1.DateVal
)
SELECT STAFF_ID
,*
FROM CTE
PIVOT
(
MAX(ACTIVITY_CODE)
FOR DateVal IN ('+#DateVal+')'+'
)'
EXEC SP_EXECUTESQL #Sql

Group data by interval of 15 minutes and use cross tab

I am trying to write a query based on datetime and weekday in sql server where my output should be like :
My table descriptions are:
**Branch**(DateKey integer,
BranchName varchar2(20),
TransactionDate datetime,
OrderCount integer)
**Date**(DateKey integer PrimaryKey,
DayNameofWeek varchar2(15))
This is the raw data I have
So, this is quite a long shot but I solved it the following way:
I created a table valued function, which would take a date as a parameter and find all 15-minute intervals during that day.
For each day it would go from 00:00, to 00:15, 00:30 up to 23:30, 23:45, and 23:59. It also returns each interval start time and end time, since we will need to use this for every row in your branch table to check if they fall into that time slot and if so, count it in.
This is the function:
create function dbo.getDate15MinIntervals(#date date)
returns #intervals table (
[interval] int not null,
[dayname] varchar(20) not null,
interval_start_time datetime not null,
interval_end_time datetime not null
)
as
begin
declare #starttime time = '00:00';
declare #endtime time = '23:59';
declare #date_start datetime;
declare #date_end datetime;
declare #min datetime;
select #date_start = cast(#date as datetime) + cast(#starttime as datetime), #date_end = cast(#date as datetime) + cast(#endtime as datetime);
declare #minutes table ([date] datetime)
insert into #minutes values (#date_start), (#date_end) -- begin, end of the day
select #min = DATEADD(mi, 0, #date_start)
while #min < #date_end
begin
select #min = DATEADD(mi, 1, #min)
insert into #minutes values (#min)
end
insert into #intervals
select ([row]-1)/15+1 intervalId, [dayname], min(interval_time) interval_start_time
> -- **NOTE: This line is the only thing you need to change:**
, DATEADD(ms, 59998, max(interval_time)) interval_end_time
from
(
select row_number() over(order by [date]) as [row], [date], datename(weekday, [date]) [dayname], [date] interval_time
from #minutes
) t
group by ([row]-1)/15+1, [dayname]
order by ([row]-1)/15+1
return
end
--example of calling it:
select * from dbo.getDate15MinIntervals('2017-07-14')
Then, I am querying your branch table (you don't really need the Date table, the weekday now you have it in the function but even if not, there's a DATENAME function in SQL Server, starting with 2008 that you can use.
I would query your table like this:
select branchname, [dayname], ISNULL([11:30], 0) as [11:30], ISNULL([11:45], 0) as [11:45], ISNULL([12:00], 0) as [12:00], ISNULL([12:45], 0) as [12:45]
from
(
select intervals.[dayname]
, b.branchname
, convert(varchar(5), intervals.interval_start_time, 108) interval_start_time -- for hh:mm format
, sum(b.ordercount) ordercount
from branch b cross apply dbo.getDate15MinIntervals(CAST(b.TransactionDate as date)) as intervals
where b.transactiondate between interval_start_time and interval_end_time
group by intervals.[dayname], b.branchname, intervals.interval_start_time, intervals.interval_end_time
) t
pivot ( sum(ordercount) for interval_start_time in ( [11:30], [11:45] , [12:00], [12:45] )) as p
Please note I have used in the PIVOT function only the intervals I can see in the image you posted, but of course you could write all 15-minute intervals of the day manually - you would just need to write them once in the pivot and once in the select statement - or optionally, generate this statement dynamically.

Resources