Build datetime in SQL Server 2012 - sql-server

I need to build a datetime in a select statement based on another 2 columns (datetime).
I cannot seem to get the conversion correct. Can you spot what I am doing wrong?
It seems to me that DatePart it omits the "0" part of the day
Below script should create all the data necessary
IF EXISTS (SELECT * FROM sys.databases WHERE name='TestDB')
BEGIN
ALTER DATABASE TestDB
SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE TestDB
END
CREATE DATABASE TestDB
GO
IF OBJECT_ID(N'[dbo].[TestTable]','U') IS NOT NULL
DROP TABLE [dbo].[TestTable]
GO
CREATE TABLE [dbo].[TestTable]
(
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[DateSample1] datetime NOT NULL,
[DateSample2] datetime NOT NULL
)
GO
INSERT dbo.TestTable (DateSample1, DateSample2)
VALUES('2006-10-06 00:00:00.000', '2007-01-17 00:00:00.000')
/*
In your select statement you should return another column "DateSample3"
and this should be year from DateSample1 and month and day from dateSample2
*/
--my try1
SELECT
tt.DateSample1, tt.DateSample2,
DateSample3 = CAST(DATEPART(YYYY, tt.DateSample1) AS CHAR(4))
+ CAST(DATEPART(MM, tt.DateSample2) AS CHAR(2))
+ CAST(DATEPART(dd, tt.DateSample2) AS CHAR(2))
,WantedResultForDateSample3='2006-01-17 00:00:00.000'
FROM
dbo.TestTable tt
--mytry2 THROWS AN ERROR
--Conversion failed when converting date and/or time from character string
/*
SELECT
tt.DateSample1, tt.DateSample2,
DateSample3 = CONVERT(DATETIME,CAST(DATEPART(YYYY, tt.DateSample1) AS CHAR(4))
+ CAST(DATEPART(MM, tt.DateSample2) AS CHAR(2))
+ CAST(DATEPART(dd, tt.DateSample2) AS CHAR(2)),120),
WantedResult='2006-01-17 00:00:00.000'
FROM
dbo.TestTable tt
*/

You can use DATETIMEFROMPARTS
DATETIMEFROMPARTS(YEAR(tt.DateSample1),
MONTH(tt.DateSample2),
DAY(tt.DateSample2),
0,0,0,0)
Which is a lot cleaner than constructing a string IMO.
Whichever method you use you might have to deal with impossible dates with this requirement. One approach is below
SELECT CASE WHEN month=2
AND day = 29
AND (yr % 4 != 0 OR (yr % 100 = 0 AND yr % 400 != 0))
THEN NULL
ELSE
DATETIMEFROMPARTS(yr,
month,
day,
0,0,0,0)
END
FROM [dbo].[TestTable] tt
CROSS APPLY (VALUES (YEAR(tt.DateSample1),
MONTH(tt.DateSample2),
DAY(tt.DateSample2))) V(yr, month, day)
SQL Fiddle

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.

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'

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.

SQL Server CTE For Each Date

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

How to compare datetime in SQL Server in where clause

I have CreatedDate as datetime column in my database table. I want to fetch the rows where CreatedDate and current time difference is more than 1 hour
Select * from TableName where (DateDiff(hh,CreatedDate,GetDate())>1
Answer by #Amit Singh works if you only care about the hour value itself, versus any 60 minute period.
The problem with using DATEDIFF(hh) that way is that times of 13:01 and 14:59 are only one "hour" apart.
Like:
select datediff(hh,'1/1/2001 13:59','1/1/2001 14:01')
I think doing this would address that issue:
declare #cd datetime='9/12/2013 03:10';
declare #t table(id int,CreatedDate datetime);
insert #t select 1,'9/12/2013 02:50';
insert #t select 2,'9/12/2013 02:05';
select * from #t where #cd>(DateAdd(hh,1,CreatedDate))
Dan Bellandi raises a valid point, but if it really matters if the dates should be 60 minutes apart, then just check if they are 60 minutes apart:
SELECT * FROM TableName WHERE DATEDIFF(MINUTE, DateColumnName, GETDATE()) >= 60
If you don't expect any rows created in the future...
where CreatedDate < dateadd(hour, -1, getdate())
CREATE TABLE trialforDate
(
id INT NULL,
NAME VARCHAR(20) NULL,
addeddate DATETIME NULL
)
INSERT INTO trialforDate VALUES (1,'xxxx',GETDATE())
INSERT INTO trialforDate VALUES (2,'yyyy',GETDATE())
INSERT INTO trialforDate VALUES (1,'zzzz','2013-09-12 11:20:40.533')
SELECT *
FROM trialforDate
WHERE GETDATE() > DATEADD(HOUR, 1, addeddate)
C# Code
DateTime param1= System.DateTime.Now;
DateTime param2= System.DateTime.Now.AddHours(1);
SQL Query:
SELECT * FROM TableName WHERE CreatedDate = param1 AND CreatedDate =param2;

Resources