Multi Day Values - sql

I am having some troubles with my query. I am trying to get the total import rate for the current day, while also matching up the previous day with the correlating hour.
Example: 1AM (Current Day) = 20
1AM(Yesterday) = 25.
As of right now the columns Current and Yesterday are showing identical values.
SELECT
z.[import Hour],
COUNT(z.orderno) as CurrentDate,
COUNT(od.orderno) as Yesterday
FROM (
(select datepart(hh, recvtime) as [import Hour],
orderno
from mck_hvs.orderheader with (nolock)
where convert(date, recvtime) = convert(date, getdate())
and orderno not like '%ST'
UNION
select datepart(hh, recvtime) as [import Hour],
orderno from mck_hvs.oldorderheader with (nolock)
where convert(date, recvtime) = convert(date, getdate())
and orderno not like '%ST' ) as z
Join
(
select datepart(hh, od.recvtime) as [import Hour],
od.orderno from mck_hvs.oldorderheader od with (nolock)
where convert(date, od.recvtime) = convert(date, getdate()-1)
and od.orderno not like '%ST' ) as OD
ON z.[import Hour] = od.[import Hour] )
group by z.[import Hour]

If you use following data:
DECLARE #Orders AS TABLE(OrderNo INT,OrderTaken datetime)
INSERT INTO #Orders VALUES(123,'2017-07-24 12:20:24')
INSERT INTO #Orders VALUES(124,'2017-07-24 12:30:24')
INSERT INTO #Orders VALUES(125,'2017-07-24 13:40:24')
INSERT INTO #Orders VALUES(126,'2017-07-24 13:50:24')
INSERT INTO #Orders VALUES(227,'2017-07-25 12:20:24')
INSERT INTO #Orders VALUES(228,'2017-07-25 12:30:24')
INSERT INTO #Orders VALUES(229,'2017-07-25 13:40:24')
INSERT INTO #Orders VALUES(220,'2017-07-25 13:50:24')
with output:
The following SQL:
DECLARE #Date DATETIME='2017-07-25'
;WITH today AS(
SELECT Cast(OrderTaken As Date) oDate,
CASE WHEN DATEPART(hh,OrderTaken) > 12 THEN CONVERT(VARCHAR(2),DATEPART(hh,OrderTaken)-12) + ' PM' WHEN DATEPART(hh,OrderTaken) = 12 THEN CONVERT(VARCHAR(2),DATEPART(hh,OrderTaken)) + ' PM' WHEN DATEPART(hh,OrderTaken) < 12 THEN CONVERT(VARCHAR(2),DATEPART(hh,OrderTaken)) + ' AM' END As oHour,
Count(OrderNo) OrderCountToday FROM #Orders
WHERE CAST(OrderTaken AS DATE)=CAST(#Date AS DATE)
GROUP BY Cast(OrderTaken As Date),DATEPart(Hour,OrderTaken)
)
,yesterday AS(
SELECT Cast(OrderTaken As Date) oDate,
CASE WHEN DATEPART(hh,OrderTaken) > 12 THEN CONVERT(VARCHAR(2),DATEPART(hh,OrderTaken)-12) + ' PM' WHEN DATEPART(hh,OrderTaken) = 12 THEN CONVERT(VARCHAR(2),DATEPART(hh,OrderTaken)) + ' PM' WHEN DATEPART(hh,OrderTaken) < 12 THEN CONVERT(VARCHAR(2),DATEPART(hh,OrderTaken)) + ' AM' END As oHour,
Count(OrderNo) OrderCountYesterday
FROM #Orders
WHERE CAST(OrderTaken AS DATE)=CAST(#Date-1 AS DATE)
GROUP BY Cast(OrderTaken As Date),DATEPart(Hour,OrderTaken)
)
SELECT t.oDate Today,t.oHour Hour,t.OrderCountToday,y.OrderCountYesterday FROM today t join yesterday y ON t.oHour=y.oHour and t.oDate=dateadd(day,1,y.oDate)
will result as:
p.s.: You can summarize this SQL a lot if you don't want to use AM/PM thing in the hour. Even the logic for AM/PM can be summarized further but that will show 12 PM as 0 PM.
Hope this help.

I've included a bit of code up front just to generate some test data. You probably won't need it, but others might.
USE sandbox
GO
--------------------------------------------------------------------------------------------
--Recreate the OP's environment in my sandpit
DROP TABLE IF EXISTS mck_hvs.orderheader --2016 syntax
create table mck_hvs.orderheader(
recvtime datetime NOT NULL
,orderno varchar(20) NOT NULL
)
;
ALTER TABLE mck_hvs.orderheader
ADD CONSTRAINT PK_mck_hvs_orderheader_orderno PRIMARY KEY CLUSTERED (orderno)
;
GO
--------------------------------------------------------------------------------------------
DROP TABLE IF EXISTS mck_hvs.oldorderheader
;
create table mck_hvs.oldorderheader(
recvtime datetime NOT NULL
,orderno varchar(20) NOT NULL
)
;
ALTER TABLE mck_hvs.oldorderheader
ADD CONSTRAINT PK_mck_hvs_oldorderheader_orderno PRIMARY KEY CLUSTERED (orderno)
;
GO
--------------------------------------------------------------------------------------------
--Generate some test data (about two years worth working back from today)
--First the old data (not today's)
INSERT mck_hvs.oldorderheader
SELECT top 100000 dateadd(mi,abs(checksum(newid())) %1440,dateadd(dd,-abs(checksum(newid())) %720,getdate()-1)) as recvtime
,right('0000000' + cast(row_number() OVER(ORDER BY (SELECT NULL)) as varchar(20)),7)
+ char(82 + abs(checksum(newid()))%4) + char(82 + abs(checksum(newid()))%4) as orderno--add two random chars from R,S,T,U to the end
FROM sys.columns col1
cross join sys.columns col2
;
--Now today's (assume 500 orders came in)
INSERT mck_hvs.orderheader
SELECT top 500 dateadd(mi,abs(checksum(newid())) %1440,dateadd(dd,datediff(dd,0,getdate()),0)) --add a random number of minutes to midnight last night
,right('0000000' + cast(row_number() OVER(ORDER BY (SELECT NULL)) +100000 as varchar(20)),7)
+ char(82 + abs(checksum(newid()))%4) + char(82 + abs(checksum(newid()))%4) --add two random chars from R,S,T,U to the end
FROM sys.columns col1
cross join sys.columns col2
;
--------------------------------------------------------------------------------------------
WITH yesterday /*all my problems seemed so far away*/as
(
SELECT datepart(hour,old.recvtime) as received_hour
,count(*) as orders_received
FROM mck_hvs.oldorderheader as old
WHERE old.recvtime < dateadd(dd,datediff(dd,0,getdate()),0) --Yesterday
AND old.recvtime >= dateadd(dd,datediff(dd,0,getdate())-1,0)
AND old.orderno not like '%ST' --Note: This is not a SARGable search.
GROUP BY datepart(hour,recvtime)
)
, today as
(
SELECT datepart(hour,ordr.recvtime) as received_hour
,count(*) as orders_received
FROM mck_hvs.orderheader as ordr
WHERE ordr.orderno not like '%ST' --Google "SARG". Trust me.
GROUP BY datepart(hour,ordr.recvtime)
)
SELECT isnull(yesterday.received_hour,today.received_hour) as received_hour
,isnull(yesterday.orders_received,0) as orders_received_yesterday
,isnull(today.orders_received,0) as orders_received_today
FROM yesterday
--FULL JOIN in case there are hours of activity in one table that don't exist in the other table.
FULL JOIN today ON yesterday.received_hour = today.received_hour
;

Related

How to calculate MTD given daily account balance in SQL Server?

I have a table with columns [accountid], [DateEnding], and [AccountBalance].
I need to calculate MTD using the balance of the current month and subtracting the account balance from the last day of the previous month for each accountid.
So far I have this:
SELECT [accountid]
,[DateEnding]
,[AccountBalance]
,[AccountBalance MTD Last] = AccountBalance - FIRST_VALUE(AccountBalance) OVER (PARTITION BY accountid, YEAR(DATEADD(mm,-1,[DateEnding])), MONTH(DATEADD(mm,-1,[DateEnding])) ORDER BY [DateEnding] DESC)
FROM [test]
ORDER BY accountid, DateEnding;
Here, for each distinct account, we find the latest record available according to DateEnding
we then find the last day of the last month by taking a number of days away equal to the current day number. e.g 23rd April 2019 we subtract 23 days to get 1st March 2019
we can then find the balance on that day.
Then put the calculation together in the SELECT
SELECT Q1.accountid,
Q2.DateEnding ,
Q3.EOMbalance,
Q2.LatestBalance,
Q2.LatestBalance - Q3.EOMbalance EOM
FROM (
SELECT Distinct t1.accountid FROM test t1
) Q1
CROSS APPLY (
SELECT TOP 1 t2.AccountBalance LatestBalance, t2.[DateEnding]
FROM test t2
WHERE t2.[accountid] = Q1.accountid
ORDER BY t2.[DateEnding] DESC
) Q2
CROSS APPLY (
SELECT Top 1 t3.AccountBalance EOMbalance
FROM test t3
WHERE t3.[accountid] = Q1.accountid
AND t3.[DateEnding]
= dateadd(day,0 - DAY(q2.dateending), q2.dateending)
ORDER BY t3.[DateEnding] DESC
) Q3
The first answer seems a little complicated for this problem (Cross Apply isn't necessary here).
The following may be easier for you:
I first look at the current day's account balances in subquery 'a'.
Then I look at the account balances from the last day of last month's data, in subquery 'b'.
Then it's just a matter of subtracting the two to show the MTD delta:
select a.accountid,
a.DateEnding,
a.AccountBalance as [Current AccountBalance],
b.AccountBalance as [EOM prior AccountBalance], --added for clarity
a.AccountBalance-b.AccountBalance as [AccountBalance MTD Last]
from
(select accountid, DateEnding, AccountBalance
from #test
where DateEnding = cast(getdate() as date)
/* getdate() returns today's date, so this query will also be with respect to today */
) a
left join
(select *
from #test
where DateEnding = DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-1, -1)
/*this returns the last day of last month, always*/
) b
on a.accountid = b.accountid
Here is the SQL that makes this sample data and #test table. Simply execute it to have your own '#test' table to run against:
/*drop table #test
drop table #dates */
create table #test ([accountid] varchar(255),[DateEnding] date, [AccountBalance] decimal(16,2))
create table #dates (rnk int,dt date)
insert into #dates (dt)
values (cast('20180101' as date))
DECLARE
#basedate DATE,
#d INT
SELECT
#basedate = '20180101',
#d = 1
WHILE #d < (select datediff(day,cast('20180101' as date),getdate())+2) --select datediff(day,getdate(),cast('20180101' as datetime))
BEGIN
INSERT INTO #dates (dt)
values (DATEADD(day, 1, (select max(dt) from #dates)))
set #d = #d+1
END
update a
set a.rnk = b.rnk
from #dates a
left join (select rank() over (order by dt) rnk,dt from #dates) b on a.dt = b.dt
declare #a int
set #a = 1
declare #i int
set #i = 1
while #a <20
begin
while #i < (select max(rnk) from #dates)
begin
insert into #test
values (#a,(select dt from #dates where rnk = #i),cast(rand()*1000.0+#i as decimal(16,2)))
set #i=#i+1
end
set #a=#a+1
set #i = 1
end

How to list all months in specific period of time which doesn't have any order at that month

I want to know how to list all months in specific period of time which doesn't have any order. If you can help me.
I have Order Table has OrderDate column
I just make this:
select distinct month(Order.OrderDate) from Order where year(Order.OrderDate) = 1997
the result will show me the months that have order in specific year only
what should i do to complete this query
You need to retrieve the months in which no orders are placed for that we can use below query
;WITH months(MonthNumber) AS
(
SELECT 1
UNION ALL
SELECT MonthNumber+1
FROM months
WHERE MonthNumber < 12
)
SELECT DATENAME( month , DATEADD( month ,MonthNumber , 0 ) )
FROM months
EXCEPT
SELECT DISTINCT month([Order].OrderDate)
FROM [Order]
WHERE YEAR([Order].OrderDate) = 1997
You can try using left join like below
DEMO
select * from
(
VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)
) AS M(val)
left join t1 on month(OrderDate)=val
and year(orderdate)=1997
where month(OrderDate) is null
The problem here is that if we we have a date range, then we may go beyond single year, for example 01-Jul-2017 to 30-Jun-2018 has 2 years, therefore creating a month range may NOT work in this scenario. The possible solution is to have a list of all the months in range along with the year, so that when we search an order, we'll search by the month and year both.
-- this is test order table, just to test the output
declare #order table(OrderDate date);
insert into #order(OrderDate) values('2018-01-01')
declare #dateRange table(d datetime not null primary key);
-- date range input parameter
declare #startDate date = '2017-06-01';
declare #endDate date = '2018-06-30';
-- modifying date range so that we go from start
-- of the month to the end of the month in the range
set #startDate = cast(year(#startDate) as varchar(100)) + '-' + cast(month(#startDate) as varchar(100)) + '-1';
set #endDate = dateadd(day, -1, dateadd(month, 1, cast(year(#endDate) as varchar(100)) + '-' + cast(month(#endDate) as varchar(100)) + '-1'));
-- creating dates for every month
declare #d date = #startDate;
while(#d <= #endDate)
begin
insert into #dateRange(d) values(#d);
set #d = dateadd(month, 1, #d);
end
-- selecting all the months in the range where
-- order does not exists
select cast(year(t.d) as varchar(100)) + '-' + DATENAME(month, t.d) as [Month]
from #dateRange as t
where not exists(
select 1
from #order as x
where month(x.OrderDate) = month(t.d) and year(x.OrderDate) = year(t.d)
)
order by t.d
Output: (notice that 2018-January is missing from result because it has an order)
Month
------------------
2017-June
2017-July
2017-August
2017-September
2017-October
2017-November
2017-December
2018-February
2018-March
2018-April
2018-May
2018-June
You are looking for a LEFT JOIN
CREATE TABLE Orders
(
OrderDate DATE
);
INSERT INTO Orders VALUES
('2018-01-01'),
('2018-03-01'),
('2018-05-15');
DECLARE #MND DATE = (SELECT MIN(OrderDate) FROM Orders);
DECLARE #MXD DATE = (SELECT MAX(OrderDate) FROM Orders);
WITH CTE AS
(
SELECT #MND OrderDate
UNION ALL
SELECT DATEADD(Month, 1, CTE.OrderDate)
FROM CTE
WHERE CTE.OrderDate <= DATEADD(Month, -1, #MXD)
)
SELECT MONTH(CTE.OrderDate) [Months]
FROM CTE LEFT JOIN Orders O ON MONTH(CTE.OrderDate) = MONTH(O.OrderDate)
AND
YEAR(CTE.OrderDate) = YEAR(O.OrderDate)
WHERE O.OrderDate IS NULL;
-- Add extra conditions here to filter the period needed
Returns:
+--------+
| Months |
+--------+
| 2 |
| 4 |
+--------+
Demo

Conditional Count On Row_Number

I have a query that calculates the number working days within a month based on a table which stores all our public holidays.
The current output would show all working days, excluding public holidays and Saturday and Sunday, I would like to show each day of the month, but don't increment on a public holiday or Saturday or Sunday.
Is there a way to conditionally increment the row number?
Query is below:
DECLARE #startnum INT=0
DECLARE #endnum INT=365;
WITH gen AS
(
SELECT #startnum AS num
UNION ALL
SELECT num + 1
FROM gen
WHERE num + 1 <= #endnum
)
, holidays AS
(
SELECT CONVERT(DATE, transdate) AS HolidayDate
FROM WORKCALENDER w
WHERE w.CALENDARID = 'PubHoliday'
)
, allDays AS
(
SELECT DATEADD( d, num, CONVERT( DATE, '1 Jan 2016' ) ) AS DateOfYear
, DATENAME( dw, DATEADD( d, num, CONVERT( DATE, '1 Jan 2016' ))) AS [dayOfWeek]
FROM gen
)
select number = ROW_NUMBER() OVER ( ORDER BY DateOfYear )
, *
from allDays
LEFT OUTER JOIN holidays
ON allDays.DateOfYear = holidays.HolidayDate
WHERE holidays.HolidayDate IS NULL
AND allDays.dayOfWeek NOT IN ( 'Saturday', 'Sunday')
AND DateOfYear >= CONVERT( DATE, '1 ' + DATENAME( MONTH, GETDATE() ) + ' 2016' )
AND DateOfYear < CONVERT( DATE, '1 ' + DATENAME( MONTH, DATEADD( month, 1, GETDATE()) ) + ' 2016' )
option (maxrecursion 10000)
kind of pseudo code
select date, row_number() over (order by date) as num
from ( select date
from allDates
where month = x and weekday
exept
select date
from holidays
where month is x
) as t
union all
select date, null
from holidays
where month is x
order by date
You could use a windowed sum, see how the output of WorkdaySequenceInMonth is composed.
DECLARE #startDate DATE = '20160101'
, #numDays INT = 365
, #num INT = 0;
DECLARE #Holidays TABLE (Holiday DATE);
INSERT INTO #Holidays(Holiday)
VALUES ('20160101')
, ('20160115')
, ('20160714');
WITH nums AS
(
SELECT row_number() OVER (ORDER BY object_id) - 1 as num
FROM sys.columns
),
dateRange as
(
SELECT
DATEADD(DAY, num, #startDate) AS Dt
, num
FROM nums
WHERE num < #numDays
),
Parts AS
(
SELECT
R.Dt as [Date]
, Year(R.Dt) as [Year]
, Month(R.Dt) as [Month]
, Day(R.Dt) as [Day]
, Datename(weekday, R.Dt) as [Weekday]
, CASE WHEN H.Holiday IS NOT NULL
OR Datename(weekday, R.Dt) IN ('Saturday', 'Sunday')
THEN 0
ELSE 1
END AS IsWorkday
FROM dateRange R
LEFT JOIN #Holidays H ON R.Dt = H.Holiday
)
--
select
*
, sum(IsWorkday) over (PARTITION BY [Year],[month]
ORDER BY [Day]
ROWS UNBOUNDED PRECEDING) as WorkdaySequenceInMonth
from Parts
order by [Year], [Month]
Hi You can try this query, the initial part is the data generation, maybe you won't need it.
Then I generate a temp table with all the dates for the time period set in #StartYear, #EndYear
Then just simple queries to return the data
-- generate holidays table
select holiday
into #tempHolidays
from
(
select '20160101' as holiday
union all
select '20160201' as holiday
union all
select '20160205' as holiday
union all
select '20160301' as holiday
union all
select '20160309' as holiday
union all
select '20160315' as holiday
) as t
create table #tempCalendar (Date_temp date)
select * from
#tempHolidays
declare #startYear int , #endYear int, #i int, #dateStart datetime , #dateEnd datetime, #date datetime, #i = 0
Select #startYear = '2016'
,#endYear = '2016'
,#dateStart = (Select cast( (cast(#startYear as varchar(4)) +'0101') as datetime))
,#dateEnd = (Select cast( (cast(#startYear as varchar(4)) +'1231') as datetime))
,#date = #dateStart
--Insert dates of the period of time
while (#date <> #dateEnd)
begin
insert into #tempCalendar
Select #date
set #date = (select DATEADD(dd,1,#date))
end
-- Retrive Date list
Select Date_temp
from #tempCalendar
where Date_temp not in (Select holiday from #tempHolidays)
and datename(weekday,Date_temp) not in ('Saturday','Sunday')
--REtrieve sum of working days per month
select DATEPART(year,Date_temp) as year
,DATEPART(month,Date_temp) as Month
,Count(*) as CountOfWorkingDays
from #tempCalendar
where Date_temp not in (Select holiday from #tempHolidays)
and datename(weekday,Date_temp) not in ('Saturday','Sunday')
Group by DATEPART(year,Date_temp)
,DATEPART(month,Date_temp)
You should change #tempHolidays for your Holidays table, and use #StarYear and #EndYear as your time period.
Here's a simple demo that shows the use of the partition by clause to keep contiguity in your sequencing for non-holidays
IF OBJECT_ID('tempdb.dbo.#dates') IS NOT null
DROP TABLE #dates;
CREATE TABLE #dates (d DATE);
IF OBJECT_ID('tempdb.dbo.#holidays') IS NOT null
DROP TABLE #holidays;
CREATE TABLE #holidays (d DATE);
INSERT INTO [#holidays]
( [d] )
VALUES
('2016-12-25'),
('2017-12-25'),
('2018-12-25');
INSERT INTO [#dates]
( [d] )
SELECT TOP 1000 DATEADD(DAY, n, '2015-12-31')
FROM [Util].dbo.[Numbers] AS [n];
WITH holidays AS (
SELECT d.*, CASE WHEN h.d IS NULL THEN 0 ELSE 1 END AS [IsHoliday]
FROM [#dates] AS [d]
LEFT JOIN [#holidays] AS [h]
ON [d].[d] = [h].[d]
)
SELECT d, ROW_NUMBER() OVER (PARTITION BY [holidays].[IsHoliday] ORDER BY d)
FROM [holidays]
ORDER BY d;
And please forgive my marking only Christmas as a holiday!

What is the best way to find next n week days

I got the following code from the following question I asked:
Passing in Week Day name to get nearest date in SQL
I need to find next 4 Weekdays based on today's date for corresponding Day-Of-Week in my table ie, if today is 2015-01-24 the result should be 1/24, 1/31, 2/7, 2/14 for Saturdays.
TABLE
SAMPLE QUERY
create table #t
(
jobId int,
personId int,
frequencyVal varchar(10)
);
insert into #t values (1,100,'Mondays'),(2,101,'Saturdays');
WITH cte(n) AS
(
SELECT 0
UNION ALL
SELECT n+1 FROM cte WHERE n < 3
)
select #t.jobId, #t.personId, #t.frequencyVal, STUFF(a.d, 1, 1, '') AS FutureDates
from #t
cross apply (SELECT CASE #t.frequencyVal
WHEN 'SUNDAYS' THEN 1
WHEN 'MONDAYS' THEN 2
WHEN 'TUESDAYS' THEN 3
WHEN 'WEDNESDAYS' THEN 4
WHEN 'THURSDAYS' THEN 5
WHEN 'FRIDAYS' THEN 6
WHEN 'SATURDAYS' THEN 7
END)tranlationWeekdays(n)
cross apply (select ',' + CONVERT(varchar(10), CONVERT(date,dateadd(WEEK, cte.n,CONVERT(DATE, DATEADD(DAY, (DATEPART(WEEKDAY, GETDATE()) + tranlationWeekdays.n) % 7, GETDATE()))))) from cte FOR XML PATH('')) a(d);
drop table #t;
EXPECTED RESULT
Gets the first day of current month.
DECLARE #FIRSTDAY DATE=DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0)
Create the table and insert values
create table #t
(
jobId int,
personId int,
frequencyVal varchar(10)
);
insert into #t values (1,100,'Mondays'),(2,101,'Saturdays');
You can use either of the below queries for your situation.
QUERY 1 : Select the first 4 week of days in current month for particular week day
-- Gets the first day of current month
DECLARE #FIRSTDAY DATE=DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0)
;WITH CTE as
(
-- Will find all dates in current month
SELECT #FIRSTDAY as DATES
UNION ALL
SELECT DATEADD(DAY,1,DATES)
FROM CTE
WHERE DATES < DATEADD(MONTH,1,#FIRSTDAY)
)
,CTE2 AS
(
-- Join the #t table with CTE on the datename+'s'
SELECT jobId,personId,frequencyVal, DATES,
ROW_NUMBER() OVER(PARTITION BY DATENAME(WEEKDAY,CTE.DATES) ORDER BY CTE.DATES) DATECNT
FROM CTE
JOIN #t ON DATENAME(WEEKDAY,CTE.DATES)+'s' = #t.frequencyVal
WHERE MONTH(DATES)= MONTH(GETDATE())
)
-- Converts to CSV and make sure that only 4 days are generated for month
SELECT DISTINCT C2.jobId,C2.personId,frequencyVal,
SUBSTRING(
(SELECT ', ' + CAST(DATEPART(MONTH,DATES) AS VARCHAR(2)) + '/' +
CAST(DATEPART(DAY,DATES) AS VARCHAR(2))
FROM CTE2
WHERE C2.jobId=jobId AND C2.personId=personId AND DATECNT<5
ORDER BY CTE2.DATES
FOR XML PATH('')),2,200000) futureDates
FROM CTE2 C2
SQL FIDDLE
For example, in Query1 the nearest date(here we take example as Saturday) of
2015-Jan-10 will be 01/03,01/10,01/17,01/24
2015-Jan-24 will be 01/03,01/10,01/17,01/24
QUERY 2 : Select nearest 4 week of days in current month for particular week day
-- Gets the first day in current month
DECLARE #FIRSTDAY DATE=DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0)
;WITH CTE as
(
-- Will find all dates in current
SELECT CAST(#FIRSTDAY AS DATE) as DATES
UNION ALL
SELECT DATEADD(DAY,1,DATES)
FROM CTE
WHERE DATES < DATEADD(MONTH,1,#FIRSTDAY)
)
,CTE2 AS
(
-- Join the #t table with CTE on the datename+'s'
SELECT jobId,personId,frequencyVal,DATES,
-- Get week difference for each weekday
DATEDIFF(WEEK,DATES,GETDATE()) WEEKDIFF,
-- Count the number of weekdays in a month
COUNT(DATES) OVER(PARTITION BY DATENAME(WEEKDAY,CTE.DATES)) WEEKCOUNT
FROM CTE
JOIN #t ON DATENAME(WEEKDAY,CTE.DATES)+'s' = #t.frequencyVal
WHERE MONTH(DATES)= MONTH(GETDATE())
)
-- Converts to CSV and make sure that only nearest 4 week of days are generated for month
SELECT DISTINCT C2.jobId,C2.personId,frequencyVal,
SUBSTRING(
(SELECT ', ' + CAST(DATEPART(MONTH,DATES) AS VARCHAR(2)) + '/' +
CAST(DATEPART(DAY,DATES) AS VARCHAR(2))
FROM CTE2
WHERE C2.jobId=jobId AND C2.personId=personId AND C2.frequencyVal=frequencyVal AND
((WEEKDIFF<3 AND WEEKDIFF>-3 AND WEEKCOUNT = 5) OR WEEKCOUNT <= 4)
ORDER BY CTE2.DATES
FOR XML PATH('')),2,200000) futureDates
FROM CTE2 C2
SQL FIDDLE
For example, in Query2 the nearest date(here we take example as Saturday) of
2015-Jan-10 will be 01/03,01/10,01/17,01/24
2015-Jan-24 will be 01/10,01/17,01/24,01/31
QUERY 3 : Select next 4 week's dates for particular week day irrelevant of month
;WITH CTE as
(
-- Will find all dates in current month
SELECT CAST(GETDATE() AS DATE) as DATES
UNION ALL
SELECT DATEADD(DAY,1,DATES)
FROM CTE
WHERE DATES < DATEADD(DAY,28,GETDATE())
)
,CTE2 AS
(
-- Join the #t table with CTE on the datename+'s'
SELECT jobId,personId,frequencyVal, DATES,
ROW_NUMBER() OVER(PARTITION BY DATENAME(WEEKDAY,CTE.DATES) ORDER BY CTE.DATES) DATECNT
FROM CTE
JOIN #t ON DATENAME(WEEKDAY,CTE.DATES)+'s' = #t.frequencyVal
)
-- Converts to CSV and make sure that only 4 days are generated for month
SELECT DISTINCT C2.jobId,C2.personId,frequencyVal,
SUBSTRING(
(SELECT ', ' + CAST(DATEPART(MONTH,DATES) AS VARCHAR(2)) + '/' +
CAST(DATEPART(DAY,DATES) AS VARCHAR(2))
FROM CTE2
WHERE C2.jobId=jobId AND C2.personId=personId AND C2.frequencyVal=frequencyVal
AND DATECNT < 5
ORDER BY CTE2.DATES
FOR XML PATH('')),2,200000) futureDates
FROM CTE2 C2
SQL FIDDLE
The following would be the output if the GETDATE() (if its Saturday) is
2015-01-05 - 1/10, 1/17, 1/24, 1/31
2015-01-24 - 1/24, 1/31, 2/7, 2/14
This is a simpler way I think, and I think it fits your requirements
Note that I have changed your frequency_val column to an integer that represents the day of the week from SQL servers perspective and added a calculated column to illustrate how you can easily derive the day name from that.
/*************************************************/
--Set up our sample table
/*************************************************/
declare #t table
(
jobId int,
personId int,
--frequencyVal varchar(10) -- why store a string when a tiny int will do.
frequency_val tinyint,
frequency_day as datename(weekday,frequency_val -1) + 's'
)
insert into #t
values
(1,100,1),--'Mondays'),
(2,101,6),--'Saturdays');
(3,101,7),--'Sundays');
(4,100,2)--'Tuesdays'),
--select * from #t
/*************************************************/
--Declare & initialise variables
/*************************************************/
declare #num_occurances int = 4
declare #from_date date = dateadd(dd,3,getdate()) -- this will allow you to play with the date simply by changing the increment value
/*************************************************/
-- To get a row for each occurance
/*************************************************/
;with r_cte (days_ahead, occurance_date)
as (select 0, convert(date,#from_date,121)
union all
select r_cte.days_ahead +1, convert(date,dateadd(DD, r_cte.days_ahead+1, #from_date),121)
from r_cte
where r_cte.days_ahead < (7 * #num_occurances) -1
)
select t.*, r_cte.occurance_date
from
#t t
inner join r_cte
on DATEPART(WEEKDAY, dateadd(dd,##DATEFIRST - 1 ,r_cte.occurance_date)) = t.frequency_val
/*************************************************/
--To get a single row with a CSV of every occurance
/*************************************************/
;with r_cte (days_ahead, occurance_date)
as (select 0, convert(date,#from_date,121)
union all
select r_cte.days_ahead +1, convert(date,dateadd(DD, r_cte.days_ahead+1, #from_date),121)
from r_cte
where r_cte.days_ahead < (7 * #num_occurances) -1
)
select
t.*,
STUFF( (select ', '
+ convert(varchar(2),datepart(month,occurance_date),0) + '/'
+ convert(varchar(2),datepart(day,occurance_date),0) as occurance
from r_cte
where DATEPART(WEEKDAY, dateadd(dd,##DATEFIRST - 1 ,r_cte.occurance_date)) = t.frequency_val
FOR XML PATH (''),TYPE).value('.','varchar(30)')
,1,2,'') occurance_date -- rest of STUFF() function
from
#t t

include weekends on sql query

I have a database that stores the log-in and log-out of the employees but we don't have work on weekends. My supervisor want the DTR report format(I'm using RDLC report) include the weekends. (see attached image)
The image above is the expected output format for DTR. I just want to know how to include Weekends though my data are on weekdays only. Is it possible to do this using SQL Query? If yes, should I use looping in sql here?
SQL Code:
select user_id,log_date,login_time,logout_time
from table_DTR
where user_id = 'USER1'
AND log_date BETWEEN '11/21/2014' AND '12/09/2014'
Use common table expression and generate date range with from and to date and than use CTE as left join to actual table. I haven't used user_id filter in left join so apply it to your query:
DECLARE #TMEP TABLE
(
[Date] DATE,
[IN] VARCHAR(10),
[OUT] VARCHAR(10)
)
INSERT INTO #TMEP VALUES ('2014-11-11','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-12','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-13','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-14','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-15','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-18','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-19','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-20','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-21','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-22','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-25','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-26','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-27','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-28','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-11-29','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-12-1','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-12-2','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-12-3','7:30','5:30')
INSERT INTO #TMEP VALUES ('2014-12-4','7:30','5:30')
DECLARE #FromDate DATE
SET #FromDate = '2014-11-11 06:00:00.000'
DECLARE #ToDate DATE
SET #ToDate = '2014-12-11 06:00:00.000'
;WITH CTE_TableDate ([CTEDate]) as
(
SELECT #FromDate
UNION ALL
SELECT DATEADD(DAY,1,CTEDate) FROM CTE_TableDate WHERE [CTEDate] < #ToDate
)
SELECT
CTE_TableDate.CTEDate,
CASE WHEN DATEPART(DW, CTE_TableDate.CTEDate) = 7 THEN 'SATURDAY'
WHEN DATEPART(DW, CTE_TableDate.CTEDate) = 1 THEN 'SUNDAY'
ELSE TEMP.[In] END AS [IN],
CASE WHEN DATEPART(DW, CTE_TableDate.CTEDate) = 7 THEN 'SATURDAY'
WHEN DATEPART(DW, CTE_TableDate.CTEDate) = 1 THEN 'SUNDAY'
ELSE TEMP.[OUT] END AS [OUT]
FROM CTE_TableDate
LEFT JOIN
(
select
[Date],
[IN],
[OUT]
from
#TMEP) TEMP
ON
CTE_TableDate.CTEDate = TEMP.[Date]
try below solution :
DECLARE #startdate DATE = '11/21/2014' -- your start date
DECLARE #enddate DATE = '12/09/2014' -- your start date
-- create list of all dates between min(log_date) and MAX(log_date)
;WITH cte
AS (SELECT #startdate AS log_date
UNION ALL
SELECT Dateadd(dd, 1, log_date) log_date
FROM cte
WHERE log_date < #enddate)
-- select the data using left outer join so that it will return missing dates too.
SELECT t1.user_id,
c.log_date,
t2.login_time,
t2.logout_time
FROM cte c
CROSS JOIN (SELECT DISTINCT user_id
FROM mytable) t1
LEFT OUTER JOIN mytable t2
ON t2.user_id = t1.user_id
AND t2.log_date = c.log_date
ORDER BY t1.user_id,c.log_date
OPTION(maxrecursion 1000)
It will return null in time columns for weekends.
Note : if you are getting error : The statement terminated. The maximum recursion 100 has been exhausted before statement completion. then try using OPTION(maxrecursion 3000) or greater.
You can create a Calendar table as below:
CREATE TABLE dbo.Calendar
(
dt DATE PRIMARY KEY, -- use SMALLDATETIME if < SQL Server 2008
IsWorkDay BIT
);
DECLARE #s DATE, #e DATE;
SELECT #s = '2000-01-01' , #e = '2029-12-31';
INSERT dbo.Calendar(dt, IsWorkDay)
SELECT DATEADD(DAY, n-1, '2000-01-01'), 1
FROM
(
SELECT TOP (DATEDIFF(DAY, #s, #e)+1) ROW_NUMBER()
OVER (ORDER BY s1.[object_id])
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
) AS x(n);
SET DATEFIRST 1;
-- weekends
UPDATE dbo.Calendar SET IsWorkDay = 0
WHERE DATEPART(WEEKDAY, dt) IN (6,7);
-- Christmas
UPDATE dbo.Calendar SET IsWorkDay = 0
WHERE MONTH(dt) = 12
AND DAY(dt) = 25
AND IsWorkDay = 1;
and then use the same as
DECLARE #table_DTR TABLE
( USER_ID VARCHAR(10),
log_date DATE,
login_time TIME,
logout_time TIME)
INSERT INTO #table_DTR VALUES ('USER1','11/21/2014','7:55:00','5:00:00')
select CASE d.IsWorkDay WHEN 0 THEN datename(dw,d.dt) else DTR.user_id END AS user_id,
d.dt AS log_date,
DTR.login_time,
DTR.logout_time
from dbo.Calendar d
LEFT JOIN #table_DTR DTR ON d.dt = DTR.log_date AND DTR.user_id = 'USER1'
WHERE d.dt BETWEEN '11/21/2014' AND '11/26/2014'
For detailed explanation on pros of Calendar table you can refer here..