sql server while date not weekend or a specific date - sql

I'm trying to write an sql while loop to increment a date until it doesn't mate a date in two other tables and is not a Saturday or a Sunday.
Something like this
DECLARE #DueDate datetime
SELECT #DueDate = datetime FROM tbl_status WHERE (parent_id = #ComplaintId)
WHILE((SELECT COUNT(date) FROM tbl1 WHERE(date = #DueDate)) > 0 AND (SELECT COUNT(date) FROM tbl2 WHERE(date = #DueDate)) > 0 AND DATEPART(d,#DueDate) = 'Saturday' AND DATEPART(d,#DueDate) = 'Sunday')
BEGIN
#DueDate = DATEADD(d,1,#DueDate)
END
Can anyone help
thanks

As I mentioned in my comment, you are going about this in a very inefficient manner with your while loop.
If you don't have a table of dates to use in a lookup, you can create one with a derived table, otherwise known as a Common Table Expression:
-- Set up the test data:
declare #t1 table (d date);
declare #t2 table (d date);
insert into #t1 values('20161230'),('20170111'),('20170110');
insert into #t2 values('20161225'),('20170105'),('20170106');
-- Declare your DueDate:
declare #DueDate date = '20170105';
-- Use a CTE to build a table of dates. You will want to set the Start and End dates automatically with SELECT statements:
declare #DatesStart date = '20161201';
declare #DatesEnd date = '20170225';
with Tally0 as
(
select x from (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) as x(x)
)
,Tally1 as
(
select row_number() over (order by (select null))-1 as rn
from Tally0 t1 -- 10 rows -- Add more CROSS APPLY joins
cross apply Tally0 t2 -- 100 rows -- to get enough rows to cover
cross apply Tally0 t3 -- 1000 rows -- your date range.
)
,Dates as
(
select dateadd(d,t.rn,#DatesStart) as DateValue
from Tally1 t
where t.rn <= datediff(d,#DatesStart,#DatesEnd)
)
select min(d.DateValue) as NextDate -- SELECT the next available Date.
from Dates d
left join #t1 t1
on(d.DateValue = t1.d)
left join #t2 t2
on(d.DateValue = t2.d)
where t1.d is null -- That isn't in either table
and t2.d is null -- and isn't on a Saturday or Sunday.
and datename(weekday,d.DateValue) not in('Saturday','Sunday')
and d.DateValue > #DueDate

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

Add an additional flag value to the SQL query based on consecutive day logic

I have a table with the following structure
Date Holiday Flag
12/23/2016 -1
12/24/2016 -1
12/25/2016 1
12/26/2016 1
12/27/2016 -1
I want to add an additional flag based derived from the two columns mentioned above as such
Date Holiday Flag Previous Flag
12/23/2016 -1 -1
12/24/2016 -1 -1
12/25/2016 1 -1
12/26/2016 1 1
12/27/2016 -1 -1
Basically, in the event that there's a holiday on two consecutive days (12/25/2016 and 12/26/2016), I want 'Previous Flag' to reflect that on the second day (12/26/2016) as 1
I'm using SQL Server 2008 to form the query but cant seem to figure out the logic.
What is the best way to approach this situation? Thank you in advance for your help, I'm new to programming. Any help will be appreciated.
With the help of a CTE and Row_Number()
Declare #YourTable table (Date date, [Holiday Flag] int)
Insert Into #YourTable values
('12/23/2016',-1),
('12/24/2016',-1),
('12/25/2016', 1),
('12/26/2016', 1),
('12/27/2016',-1)
;with cte as (
Select *
,RN = Row_Number() over (Order By Date)
From #YourTable
)
Select A.Date
,A.[Holiday Flag]
,[Previous Flag] = IsNull(B.[Holiday Flag],A.[Holiday Flag])
From cte A
Left Join cte B on (B.RN=A.RN-1)
Order By A.Date
Returns
Not sure I agree with the desired results. I show 12/27 previous flag as 1
This might work.
DECLARE #T TABLE (Date DATETIME,HolidayFlag INT)
INSERT INTO #T SELECT '12/23/2016',-1
INSERT INTO #T SELECT '12/24/2016',-1
INSERT INTO #T SELECT '12/25/2016',1
INSERT INTO #T SELECT '12/26/2016',1
INSERT INTO #T SELECT '12/27/2016',-1
SELECT
This.Date,
This.HolidayFlag,
LastHolidayFlag=CASE WHEN Last.Date IS NULL THEN -1 ELSE Last.HolidayFlag END
FROM
(
SELECT Date,HolidayFlag,RowNumber=ROW_NUMBER() OVER (ORDER BY Date) FROM #T
)AS This
LEFT OUTER JOIN
(
SELECT Date,HolidayFlag, RowNumber=ROW_NUMBER() OVER (ORDER BY Date) FROM #T
)AS Last ON Last.RowNumber=This.RowNumber-1
Not too complicated. Try this.
Declare #Table table (Date date, [Holiday Flag] int)
Insert Into #Table values
('12/23/2016',-1),
('12/24/2016',-1),
('12/25/2016',1),
('12/26/2016',1),
('12/27/2016',-1)
Select A.Date
,A.[Holiday Flag]
,[Previous Flag] = IsNull(B.[Holiday Flag],A.[Holiday Flag])
From #Table A
Left Join #Table B on (DateAdd(day,-1 , A.Date)=B.Date)
Order By A.Date
Since you have sequential dates, a ROW_NUMBER() is superfluous. Try this:
Declare #MyTbl table (
Dt date PRIMARY KEY,
HolidayFlag int
)
Insert Into #MyTbl
values
('12/23/2016',-1),
('12/24/2016',-1),
('12/25/2016', 1),
('12/26/2016', 1),
('12/27/2016',-1)
SELECT t1.Dt as [Date],
t1.HolidayFlag as [Holiday Flag],
CASE
WHEN t1.HolidayFlag = 1
THEN IsNull(t2.HolidayFlag, -1)
ELSE -1
END as [Previous Flag]
FROM #MyTbl t1
LEFT JOIN #MyTbl t2
ON t2.dt = dateadd(day, -1, t1.dt)

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..

Query to aggregate totals between dates

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

T-SQL to select every nth date from column of dates

I have a column of dates. They are all workdays. I would like to generate a list of dates that are 'n' days apart. For example, starting with the most recent date, I want to find the date n days before it, 2n days before it, 3n days before it, etc. I could use a while loop but I wanted to know if I could use SQL set operations instead. Can it be done?
Find the difference between the most_recent_date and dates in your table, then use the modulo function, where n is the interval.
SELECT date
FROM my_table
WHERE mod(most_recent_date - date, n) = 0
This is the perfect case for a CTE:
DECLARE #LastDate datetime;
DECLARE #N int;
DECLARE #NCoefficientMax;
SELECT #N = 1, #NCoefficientMax = 10;
SELECT #LastDate = MyDate
FROM MyTable
ORDER BY MyDate DESC
WITH mycte
AS
(
SELECT DATEADD(dd, #N, #LastDate) AS NextDate, #N AS NCoefficient
UNION ALL
SELECT DATEADD(dd, #N, NextDate), #N + NCoefficient AS NCoefficient
FROM mycte WHERE NCoefficient < #NCoefficientMax
)
SELECT NextDate FROM mycte
Where #NCoefficientMax is the max coefficient for N.
You can use the dateadd funcion
and make select with join to self table.
What that you need to do it -
Insert the result to temporary table,
with additional column then contain the row_number then order like the result
simple example:
declare #t1 table (d datetime, row int)
insert #t1
select d, row_number()over(order by d)
from T1
order by d
select T1A.*, datediff(day,T1A.d,T1B.d) as dif
from #t1 as T1A
left join #t1 as T1B on T1A.row = T1B.row-1
DECLARE #mostRecent datetime2
SELECT #mostRecent = MAX(dateColumn)
FROM table
SELECT columns
FROM table
WHERE (DATEDIFF(day, dateColumn, #mostRecent) % n) = 0