SQL Server CTE For Each Date - sql-server

I am stuck at this point where I need to get a report for a screen in my project.
The user input is start date and end date...
I need the "closed task count" for each day between those values. If there are no tasks on some dates, the count should return "0". Here I am so far, but I really don't understand what I am doing wrong. Please help!
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
ALTER PROCEDURE APP.GET_TASK_ENTRY_ACTIVE_GRAPH
(
#START_DATE DATETIME,
#END_DATE DATETIME
)
AS
BEGIN
SET NOCOUNT ON
CREATE TABLE #TMP_TASK_VALS
(
DATE_VALUE DATETIME,
VAL INT
)
INSERT INTO #TMP_TASK_VALS
(
DATE_VALUE, VAL
)
SELECT CONVERT(DATETIME, TASK_CLOSING_DATE), COUNT(1) FROM APP.TASK_ENTRIES (NOLOCK)
WHERE TASK_CLOSING_DATE BETWEEN #START_DATE AND #END_DATE
GROUP BY TASK_CLOSING_DATE
--ORDER BY TASK_CLOSING_DATE DESC
--SELECT * FROM #TMP_TASK_VALS
;WITH CTE_DAILY(DAY) AS
(
SELECT #START_DATE AS DAY
UNION ALL
SELECT DAY + 1 FROM CTE_DAILY
WHERE DAY < #END_DATE
)
SELECT CTE_DAILY.DAY, COUNT(VAL) FROM CTE_DAILY WITH (NOLOCK) LEFT JOIN #TMP_TASK_VALS WITH (NOLOCK) ON #TMP_TASK_VALS.DATE_VALUE = CTE_DAILY.DAY
GROUP BY CTE_DAILY.DAY
DROP TABLE #TMP_TASK_VALS
END
GO
/*
exec APP.GET_TASK_ENTRY_ACTIVE_GRAPH '2015-08-10', '2015-08-16'
*/
The result is like, I have all the dates continuosly, but the value (count) is all zero.
Cheers.

I observed that you are converting TASK_CLOSING_DATE to datetime in first query, but not while using BETWEEN.I see some datatype mismatch. Please try to convert while joining with CTE too.
EDIT: As per OP's feedback the issues is with conversion of date and datetime fields.
OP's Comment : Converting my DATETIME to DATE totally solved this

Related

How to optimize below my SQL query shown here

This query is written for those users who did not log-in to the system between 1st July to 31 July.
However when we run the query in query analyzer then it's taking more than 2 minutes. But in application side giving error as 'Execution Timeout Expired. The timeout period elapsed prior to completion of the operation or the server is not responding'.
Below query takes start date as 1st July 2022 and get all the users and add those users into temp table called '#TABLE_TEMP' and increases to next date.
Again while loop runs and fetch users for 2nd July and so on until it reaches to 31st July.
Can anyone help on this to optimize the query using CTE or any other mechanism?
H
ow can we avoid While loop for better performance?
DECLARE #TABLE_TEMP TABLE
(
Row int IDENTITY(1,1),
[UserId] int,
[UserName] nvarchar(100),
[StartDate] nvarchar(20),
[FirstLogin] nvarchar(20),
[LastLogout] nvarchar(20)
)
DECLARE #START_DATE datetime = '2022-07-01';
DECLARE #END_DATE datetime = '2022-07-31';
DECLARE #USER_ID nvarchar(max) = '1,2,3,4,5,6,7,8,9';
DECLARE #QUERY nvarchar(max) = '';
WHILE(#START_DATE < #END_DATE OR #START_DATE = #END_DATE)
BEGIN
SET #QUERY = 'SELECT
s.userid AS [UserId],
s.username AS [UserName],
''' + CAST(#START_DATE as nvarchar) + ''' AS [StartDate],
MAX(h.START_TIME) as [FirstLogin],
MAX(ISNULL(h.END_TIME, s.LAST_SEEN_TIME)) as [LastLogout]
FROM USER s
LEFT JOIN USER_LOGIN_HISTORY h ON h.userid = s.userid
LEFT JOIN TEMP_USER_INACTIVATION TUI ON TUI.userid = s.userid AND ('''+ CAST(#START_DATE as nvarchar) +''' BETWEEN ACTIVATED_DATE AND DEACTIVATD_DATE)
WHERE s.userid IN (' + #USER_ID + ')
AND h.userid NOT IN (SELECT userid FROM USER_LOGIN_HISTORY WHERE CAST(START_TIME AS DATE) = '''+ CONVERT(nvarchar,(CAST(#START_DATE AS DATE))) +''') AND ACTIVATED_DATE IS NOT NULL
GROUP BY s.userid, h.userid, s.username, s.last_seen_time
HAVING CAST(MAX(ISNULL(h.END_TIME, s.LAST_SEEN_TIME)) AS DATE) <> '''+ CONVERT(nvarchar,(CAST(#START_DATE AS DATE))) + '''
ORDER BY [User Name]'
INSERT INTO #TABLE_TEMP
EXEC(#QUERY)
SET #START_DATE = DATEADD(DD, 1, #START_DATE)
END
Without the query plan, it's hard to say for sure.
But there are some clear efficiencies to be had.
Firstly, there is no need for a WHILE loop. Create a Dates table which has every single date in it. Then you can simply join it.
Furthermore, do not inject the #USER_ID values. Instead, pass them thorugh as a Table Valued Parameter. At the least, split what you have now into a temp table or table variable.
Do not cast values you want to join on. For example, to check if START_TIME falls on a certain date, you can do WHERE START_TIME >= BeginningOfDate AND START_TIME < BeginningOfNextDate.
The LEFT JOINs are suspicious, especially given you are filtering on those tables in the WHERE.
Use NOT EXISTS instead of NOT IN or you could get incorrect results
DECLARE #START_DATE date = '2022-07-01';
DECLARE #END_DATE date = '2022-07-31';
DECLARE #USER_ID nvarchar(max) = '1,2,3,4,5,6,7,8,9';
DECLARE #userIds TABLE (userId int PRIMARY KEY);
INSERT #userIds (userId)
SELECT CAST(value AS int)
FROM STRING_SPLIT(#USER_ID, ',');
SELECT
s.userid as [UserId],
s.username as [UserName],
d.Date as [StartDate],
MAX(h.START_TIME) as [FirstLogin],
MAX(ISNULL(h.END_TIME, s.LAST_SEEN_TIME)) as [LastLogout]
FROM Dates d
JOIN USER s
LEFT JOIN USER_LOGIN_HISTORY h ON h.userid = s.userid
LEFT JOIN TEMP_USER_INACTIVATION TUI
ON TUI.userid = s.userid
ON d.Date BETWEEN ACTIVATED_DATE AND DEACTIVATD_DATE -- specify table alias (don't know which?)
WHERE s.userid in (SELECT u.userId FROM #userIds u)
AND NOT EXISTS (SELECT 1
FROM USER_LOGIN_HISTORY ulh
WHERE ulh.START_TIME >= CAST(d.date AS datetime)
AND ulh.START_TIME < CAST(DATEADD(day, 1, d.date) AS datetime)
AND ulh.userid = h.userid
)
AND ACTIVATED_DATE IS NOT NULL
AND d.Date BETWEEN #START_DATE AND #END_DATE
GROUP BY
d.Date,
s.userid,
s.username,
s.last_seen_time
HAVING CAST(MAX(ISNULL(h.END_TIME, s.LAST_SEEN_TIME)) AS DATE) <> d.date
ORDER BY -- do you need this? remove if possible.
s.username;
Better to collect dates in a table rather than running query in a loop. Use following query to collect dates between given date range:
DECLARE #day INT= 1
DECLARE #dates TABLE(datDate DATE)
--creates dates table first and then create dates for the given month.
WHILE ISDATE('2022-8-' + CAST(#day AS VARCHAR)) = 1
BEGIN
INSERT INTO #dates
VALUES (DATEFROMPARTS(2022, 8, #day))
SET #day = #day + 1
END
Then to get all dates where user did not login, you have to use Cartesian join and left join as illustrated below
SELECT allDates.userID,
allDates.userName,
allDates.datDate notLoggedOn
FROM
(
--This will reutrun all users for all dates in a month i.e. 31 rows for august for every user
SELECT *
FROM Users,
#dates
) allDates
LEFT JOIN
(
--now get last login date for every user between given date range
SELECT userID,
MAX(login_date) last_Login_date
FROM USER_LOGIN_HISTORY
WHERE login_date BETWEEN '2022-08-01' AND '2022-08-31'
GROUP BY userID
) loggedDates ON loggedDates.last_Login_date = allDates.datDate
WHERE loggedDates.last_Login_date IS NULL --filter out only those users who have not logged in
ORDER BY allDates.userID,
allDates.datDate
From this query you will get every day of month when a user did not logged in.
If there is no need to list every single date when user did not log in, then Cartesian join can be omitted. This will further improve the performance.
I hope this will help.

Conversion failed when converting the varchar value '2018-01-01' to data type int

ALTER procedure [dbo].[USP_xyz]
AS
declare
#date varchar(100),
#conditiondate varchar(100);
SET #date = (SELECT top 1 column1 from mytable ORDER BY column1 DESC);
set #conditiondate = SUBSTRING(#date,1,10) ;
SELECT CONVERT(INT, #conditiondate) ;
print #conditiondate
return #conditiondate;
GO
This is the statement that's throwing the error
SELECT CONVERT(INT, #conditiondate)
If you are trying to get any parts of the Date, Like Month, Day or Year, You have built-in Functions for this in SQL Server
SELECT
DAY(GETDATE()),--To Get the Date
MONTH(GETDATE()),--To get The Month
YEAR(GETDATE())--To Get the Year
or if you are trying to Format based on some specific format, try like this
SELECT FORMAT(GETDATE(),'MMMDDYYYY')

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

SQL Server stored procedure to add days to a date

I need to write a stored procedure that given a date and a number of working days, adds those given days to that date, and returns the new date, without counting the non-working days and the weekends. the non-working day are stored in another table.
It's my second stored procedure so I'm not quite familiar with the lexic, so, sorry in advance if you find obvious mistakes.
So far I've gotten to this:
CREATE PROCEDURE DateAdd
(#GivenDate DATE, #DaysToAdd int)
DECLARE #ReturnDate DATE,
DECLARE #Counter int,
DECLARE #NextDate DATE
AS
SET #Counter = 0
SET #ReturnDate = #GivenDate
SET #NextDate = #GivenDate
GO
WHILE (#Counter < #DaysToAdd)
#Counter + 1
IF(datepart(weekday, #FechaVariable) !=6 &&
datepart(weekday, #FechaVariable) != 7)
IF(#TODO-- call the query and check it with #NextDate)
#FechaRetorno = DateAdd(dd, 1, #FechaRetorno)
ELSE IF #NextDate = DateAdd(dd, 1, #NextDate)
EN IF
END WHILE
-- I don't know where to put this query, or how to call ir from the IF
SELECT Date
FROM non_working_days
WHERE Date = $Variable
RETURN #FechaRetorno
A Tally/Calendar table would to the trick as well, but you can to do this with an ad-hoc tally table. Also, this approach would be faster than an recursive cte.
One additonal option is that you can exclude HOLIDAYS by adding the following to the WHERE clause.
and D not in ('2017-12-25','2018-01-01')
The SQL
Declare #Date date = '2017-04-01'
Declare #Days int = 5
Select D
From (
Select D,RN=Row_Number() over (Order by D)
From (Select Top ((#Days*2)+10) D=DateAdd(DAY,-1+Row_Number() Over (Order By Number),#Date) From master..spt_values ) A
Where DateName(WEEKDAY,D) not in ('Saturday','Sunday')
) A
Where RN=#Days
Returns
2017-04-07
Here. Assuming the other table is called OtherTable and the column of dates to avoid is called DatesToAvoid, this will add one day at a time to your date, and if that date is not a weekend or in the DatesToAvoid, it will decrement #DaysToAdd. Once #DaysToAdd reaches 0, it stops.
CREATE PROCEDURE DateAddsp(#GivenDate DATE, #DaysToAdd int)
AS
BEGIN
WHILE #DaystoAdd > 0
BEGIN
SET #GivenDate = DATEADD(DAY,1,#GivenDate)
SET #DaysToAdd = CASE
WHEN #GivenDate IN (SELECT DatesToAvoid FROM OtherTable) OR DATEPART(DW,#GivenDate) IN (1,7) /* Saturday or Sunday*/
THEN #DaysToAdd
ELSE #DaysToAdd + 1
END
END
RETURN DATEADD(DW, #DaysToAdd, #GivenDate);
END
You're are overcomplicating things. The function which you need to use is dateadd: https://learn.microsoft.com/en-us/sql/t-sql/functions/dateadd-transact-sql
Try something along the lines of the following:
CREATE PROCEDURE DateAddsp(#GivenDate DATE, #DaysToAdd int)
AS
BEGIN
RETURN DATEADD(DW, #DaysToAdd, #GivenDate);
END
You cannot use dateadd as the name for the stored proc, as it is a reserved word in SQL Server - it is the name of the function which I just utilized above.
Instead of a procedure, this is an in-line table-valued function to get the result of adding working days to a date.
create function dbo.udf_add_working_days (#Date date, #Days int)
returns table with schemabinding as return (
with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, days as (
select top (1000)
[Date]=convert(date,dateadd(day,row_number() over(order by (select 1))-1,#date))
from n as deka cross join n as hecto cross join n as kilo
order by [Date]
)
, working_days as (
select top (#Days)
[Date]
from days
where datename(weekday,[date]) not in ('Saturday','Sunday')
/* -- put your non working days table info here and uncomment this clause
and not exists (
select 1
from dbo.HolidayTable h
where days.[Date] = h.HolidayDate
)
--*/
order by [Date]
)
select top 1 [date]
from working_days
order by [Date] desc
);
go
and you would call it like so:
select [date]
from dbo.udf_add_working_days('20170401',10)
rextester demo: http://rextester.com/ITXB6884
returns:
+------------+
| date |
+------------+
| 2017-04-14 |
+------------+
Or you can call it using dates from a query using cross apply()
select ...
, x.Date as NewWorkDate
from t
dbo.udf_add_working_days (t.[Date], t.[NumberOfDays]) as x
Reference on inline table valued functions
When is a SQL function not a function? "If it’s not inline, it’s rubbish." - Rob Farley
Inline Scalar Functions - Itzik Ben-Gan
Scalar functions, inlining, and performance: An entertaining title for a boring post - Adam Machanic
TSQL User-Defined Functions: Ten Questions You Were Too Shy To Ask - Robert Sheldon

SQL Query for showing dates not stored in the table

I have a table in which i am storing dates and other information.
I wanna display the records for the dates which are not stored in the table.
Eg.. i have dates 01/01/2012[dd/mm/yyyy] , 03/01/2012 , 06/01/2012.
I wanna show the output for the dates 02/01/2012 ,04/05/2012 , 05/01/2012.
Query for this please in SQLServer2008
You can use the DATEADD function. For example, to add 1 day to current date:
SELECT DATEADD(dd, 1, GETDATE())
You can find more information on MSDN.
;WITH MYCTE AS
(
SELECT CAST('1900-01-01' AS DATETIME) DateValue
UNION ALL
SELECT DateValue + 1
FROM MYCTE
WHERE DateValue + 1 < '3550-12-31'
)
SELECT DateValue, B.SomeColumn
FROM MYCTE A
LEFT JOIN MyTable B ON A.DateValue = B.DateValue
OPTION (MAXRECURSION 0)
I got the date range cte from here (and it takes 4 seconds to generate a table of 250000 rows)
This code displays all dates from 2011 year missing in your table:
create table #dates (d datetime);
declare #start_period datetime;
set #start_period='01.01.2011';
declare #end_period datetime;
set #end_period='01.01.2012';
declare #d datetime;
set #d=#start_period
while(#d<#end_period)
begin
insert into #dates (d) values (#d)
SET #d=#d+1
end
select d from #dates where d not in (select <date> from <your_table>)

Resources