How do I format a column but keep it as datetime? - sql-server

This may be the dumbest question, but I have a column called [Start Time] which is (datetime, not null) and outputs data in the form '2016-05-25 00:12:01.977'
What I want is to snip everything except the Month and Year and group data into months. What I used is
select format([Start Time],'MMM/yyyy') as [Month]
Which worked fine except that it seems now no longer to be a datetime field and when I add
order by format([Start Time],'MMM/yyyy')
It puts the months in alphabetical order rather than Jan Feb etc. I tried using
convert(date,format([Start Time],'MMM/yyyy'))
but that brought back the error
Msg 241, Level 16, State 1, Line 1 Conversion failed when converting
date and/or time from character string.
Can anyone tell me if it is possible to maintain the date data type when formatting and grouping by month and if so how?
Thanks

Handling dates in SQL Server require a little bit work, but you can get around your problem by changing the dates first to the 1st of the month, grouping with that, and then finally format it, something like this:
select
format(Month2,'MMM/yyyy') as Month,
amount
from
(
select
DATEADD(month, DATEDIFF(month, 0, [Start Time]), 0) as Month2,
sum(amount) as amount
from
yourtable
group by
DATEADD(month, DATEDIFF(month, 0, [Start Time]), 0)
) X
order by
Month2
Format is also quite heavy operation, you should consider using convert or datepart if you have a lot of rows.

select format([Start Time],'MMM/yyyy') as [Month]
order by month([Start Time]), year([Start Time])

Can you check this :
Declare #t table(dtvalue datetime)
insert into #t values ('2016-05-01 00:12:01.977'), ('2016-05-06 00:12:01.977'), ('2016-05-10 00:12:01.977'), ('2016-05-15 00:12:01.977'),
('2016-05-25 00:12:01.977'), ('2016-06-25 00:12:01.977'), ('2016-06-28 00:12:01.977')
--select * from #t
select dtvalue,
format(dtvalue,'MMM/yyyy')
from #t
group by dtvalue
insert into #t values ('2016-05-02 00:12:01.977')
select dtvalue,
format(dtvalue,'MMM/yyyy')
from #t
group by dtvalue
/*Count the record in a month via grouping*/
select count (format(dtvalue,'MMM/yyyy')),
format(dtvalue,'MMM/yyyy')
from #t
group by format(dtvalue,'MMM/yyyy')

Related

Evaluating datetime into timewindows

Im trying to establish for any given datetime a tag that is purely dependent on the time part.
However because the time part is cyclic I cant make it work with simple greater lower than conditions.
I tried a lot of casting and shift one time to 24hour mark to kinda break the cycle However it just gets more and more complicated and still doesnt work.
Im using SQL-Server, here is the situation:
DECLARE #tagtable TABLE (tag varchar(10),[start] time,[end] time);
DECLARE #datetimestable TABLE ([timestamp] datetime)
Insert Into #tagtable (tag, [start], [end])
values ('tag1','04:00:00.0000000','11:59:59.9999999'),
('tag2','12:00:00.0000000','19:59:59.9999999'),
('tag3','20:00:00.0000000','03:59:59.9999999');
Insert Into #datetimestable ([timestamp])
values ('2022-07-24T23:05:23.120'),
('2022-07-27T13:24:40.650'),
('2022-07-26T09:00:00.000');
tagtable:
tag
start
end
tag1
04:00:00.0000000
11:59:59.9999999
tag2
12:00:00.0000000
19:59:59.9999999
tag3
20:00:00.0000000
03:59:59.9999999
for given datetimes e.g. 2022-07-24 23:05:23.120, 2022-07-27 13:24:40.650, 2022-07-26 09:00:00.000
the desired result would be:
date
tag
2022-07-25
tag3
2022-07-27
tag2
2022-07-26
tag1
As I wrote i tried to twist this with casts and adding and datediffs
SELECT
If(Datepart(Hour, a.[datetime]) > 19,
Cast(Dateadd(Day,1,a.[datetime]) as Date),
Cast(a.[datetime] as Date)
) as [date],
b.[tag]
FROM #datetimestable a
INNER JOIN #tagtable b
ON SomethingWith(a.[datetime])
between SomethingWith(b.[start]) and SomethingWith(b.[end])
The only tricky bit here is that your tag time ranges can go over midnight, so you need to check that your time is either between start and end, or if it spans midnight its between start and 23:59:59 or between 00:00:00 and end.
The only other piece is splitting your timestamp column into date and time using a CTE, to save having to repeat the cast.
;WITH splitTimes AS
(
SELECT CAST(timestamp AS DATE) as D,
CAST(timestamp AS TIME) AS T
FROM #datetimestable
)
SELECT
DATEADD(
day,
CASE WHEN b.[end]<b.start THEN 1 ELSE 0 END,
a.D) as timestamp,
b.[tag]
FROM [splitTimes] a
INNER JOIN #tagtable b
ON a.T between b.[start] and b.[end]
OR (b.[end]<b.start AND (a.T BETWEEN b.[start] AND '23:59:59.99999'
OR a.T BETWEEN '00:00:00' AND b.[end]))
Live example: https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=506aef05b5a761afaf1f67a6d729446c
Since they're all 8-hour shifts, we can essentially ignore the end (though, generally, trying to say an end time is some specific precision of milliseconds will lead to a bad time if you ever use a different data type (see the first section here) - so if the shift length will change, just put the beginning of the next shift and use >= start AND < end instead of BETWEEN).
;WITH d AS
(
SELECT datetime = [timestamp],
date = CONVERT(datetime, CONVERT(date, [timestamp]))
FROM dbo.datetimestable
)
SELECT date = DATEADD(DAY,
CASE WHEN t.start > t.[end] THEN 1 ELSE 0 END,
CONVERT(date, date)),
t.tag
FROM d
INNER JOIN dbo.tagtable AS t
ON d.datetime >= DATEADD(HOUR, DATEPART(HOUR, t.start), d.date)
AND d.datetime < DATEADD(HOUR, 8, DATEADD(HOUR,
DATEPART(HOUR, t.start), d.date));
Example db<>fiddle
Here's a completely different approach that defines the intervals in terms of starts and durations rather than starts and ends.
This allows the creation of tags that can span multiple days, which might seem like an odd capability to have here, but there might be a use for it if we add some more conditions down the line. For example, say we want to be able say "anything from 6pm friday to 9am monday gets the 'out of hours' tag". Then we could add a day of week predicate to the tag definition, and still use the duration-based interval.
I have defined the duration granularity in terms of hours, but of course this can easily be changed
create table #tags
(
tag varchar(10),
startTimeInclusive time,
durationHours int
);
insert #tags
values ('tag1','04:00:00', 8),
('tag2','12:00:00', 8),
('tag3','20:00:00', 8);
create table #dateTimes (dt datetime)
insert #dateTimes
values ('2022-07-24T23:05:23.120'),
('2022-07-27T13:24:40.650'),
('2022-07-26T09:00:00.000');
select dt.dt,
t.tag
from #datetimes dt
join #tags t on cast(dt.dt as time) >= t.startTimeInclusive
and dt.dt < dateadd
(
hour,
t.durationHours,
cast(cast(dt.dt as date) as datetime) -- strip the time from dt
+ cast(t.startTimeInclusive as datetime) -- add back the time from t
);
Maybe I am looking at this to simple, but,
can't you just take the first tag with an hour greater then your hour in table datetimestable.
With an order by desc it should always give you the correct tag.
This will work well as long as you have no gaps in your tagtable
select case when datepart(hour, tag.tagStart) > 19 then dateadd(day, 1, convert(date, dt.timestamp))
else convert(date, dt.timestamp)
end as [date],
tag.tag
from datetimestable dt
outer apply ( select top 1
tt.tag,
tt.tagStart
from tagtable tt
where datepart(Hour, dt.timestamp) > datepart(hour, tt.tagStart)
order by tt.tagStart desc
) tag
It returns the correct result in this DBFiddle
The result is
date
tag
2022-07-25
tag3
2022-07-27
tag2
2022-07-26
tag1
EDIT
If it is possible that there are gaps in the table,
then I think the most easy and solid solution would be to split that row that passes midnight into 2 rows, and then your query can be very simple
See this DBFiddle
select case when datepart(hour, tag.tagStart) > 19 then dateadd(day, 1, convert(date, dt.timestamp))
else convert(date, dt.timestamp)
end as [date],
tag.tag
from datetimestable dt
outer apply ( select tt.tag,
tt.tagStart
from tagtable tt
where datepart(Hour, dt.timestamp) >= datepart(hour, tt.tagStart)
and datepart(Hour, dt.timestamp) <= datepart(hour, tt.tagEnd)
) tag

SQL Query to list all hours of the day in datetime format in one column

I need a query that returns all the hours of the day in 12 hour format
ex: 12:00 am, 1:00am, 2:00am etc. This is going to be used in SSRS as a selection field for a parameter for time. I need to select records within a date range and then from a time range in that date range. I have this query which returns the time in 24 hour format but it is not working properly in SSRS:
With CTE(N)
AS
(
SELECT 0
UNION ALL
SELECT N+30
FROM CTE
WHERE N+5<24*60
)
SELECT CONVERT(TIME,DATEADD(minute,N,0) ,108)
FROM CTE
OPTION (MAXRECURSION 0)
This is how I would do it:
DECLARE #t time(1) = '00:00'; --I use 1 as when I use REPLACE later it means that I can "identify" the correct :00 to remove
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL)) N(N)),
Tally AS(
SELECT TOP 24 ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 AS I
FROM N N1, N N2),
Times AS(
SELECT DATEADD(HOUR, I,#t) AS [Time]
FROM Tally)
SELECT T.[Time],
REPLACE(CONVERT(varchar(12),T.Time,9),':00.0',' ') AS TimeString
FROM Times T
ORDER BY T.[Time] ASC;
Note that I return both a time and varchar datatype; both are important as the ordering of the data for a varchar would be quite different to start with and if you are using SSRS, I suspect you want the value of TimeString as a presentation thing and not the actual value.

Converting int to Date

How to convert to date in sql Server when i have data type int in my database?
For Example :
5th day in 4th week of 10th month in 2016
Use can DATEFROMPARTS function
Select DATEFROMPARTS(Year,Month,Day) as Dates
From yourtable
If you are using less then 2012 then
Select cast(cast(year as char(4))+'-'+cast(month as varchar(2))+'-'+cast(day as varchar(2)) as date) as dates
From yourtable
This is a truly horrible way to store date information. It is a serious pain to work with and nearly impossible to validate. You really should use the native date datatype for this type of thing.
This produced the desired output for your sample. I think this is what you are looking for.
create table #SomeDate
(
MyDay int
, MyWeek int
, MyMonth int
, MyYear int
)
insert #SomeDate
select 5, 4, 10, 2016
select dateadd(day, s.MyDay + 1, dateadd(week, s.MyWeek - 1, dateadd(month, s.MyMonth - 1, dateadd(year, s.MyYear - 1900, 0))))
from #SomeDate s
drop table #SomeDate

TSQL query for Week Comparison

I have a table that has a TASK_START_DATE and TASK_FINISH_DATE Columns of type datetime
I need help with a query that returns all Tasks when the Task: (date = just the date - I think I can do a conversion to the date from datetime on SQL 2008R2, it works fine)
- is within 2 weeks previous of the current date or two weeks after the current date.
Similarly I also need the records whose TaskEnd values are within 2 weeks previous or two weeks before
I've been trying things like which would get tasks where the start date is within the two previous weeks, but I have to do the same for TASK_FINISH_DATE and I think my and's and or's are all jumbled up, any help is appreciated.
Convert(Date, TASK_START_DATE) <= Convert(Date, DateAdd(ww, -2, GetDate()))
Short version:
How do I correctly write a query that combines all records with the TASK_START_DATE OR TASK_END_DATE within two weeks in the future or past, i.e.
Select Task_ID, TASK_NAME, TASK_START_DATE, TASK_END_DATE
where
???
You can add days to your date for comparision:
Select * from Table
Where column between getdate()-14 and getdate()+14
You don't need to use "Convert" function. "GetDate" function returns datetime value and your columns' types are datetime. You can add day number directly like this:
Select * from Table
Where (TASK_START_DATE between getdate() - 14 and getdate() + 14)
or (TASK_FINISH_DATE between getdate() - 14 and getdate() + 14)
You can declare variables or have the comparison dates right in the where clause. I use GETDATE() to get the date/time for right now as it returns a DATETIME object. Then I use DATEADD to adjust it for days, months, years, etc, and then you have to convert it to a DATE before sticking it in a variable of type DATE. Note in the DATEADD method I pass in the adjustment type (D = days), then adjust it + or - 14 days.
Alternatively you could just use 14 days ago to the minute if you don't do the DATE conversions...you'd have to remove the converts from the variable declarations as well as the where clause. Depends on the results you want though.
DECLARE #twoWeeksAgo DATE = CONVERT(DATE, DATEADD(D, -14, GETDATE()));
DECLARE #twoWeeksAhead DATE = CONVERT(DATE, DATEADD(D, 14, GETDATE()));
SELECT
Task_ID,
TASK_NAME,
TASK_START_DATE,
TASK_END_DATE
FROM
TABLE
WHERE
CONVERT(DATE, TASK_START_DATE) BETWEEN #twoWeeksAgo AND #twoWeeksAhead
OR CONVERT(DATE, TASK_END_DATE) BETWEEN #twoWeeksAgo AND #twoWeeksAhead
Also note that the BETWEEN operator in the WHERE clause is inclusive, meaning it will include records where the TASK_START_DATE is equal to the dates held by the variables. If you wanted to exclude records with the same value as #twoWeeksAhead, for example, you would have to use something like
WHERE
(CONVERT(DATE, TASK_START_DATE) >= #twoWeeksAgo
AND CONVERT(DATE, TASK_START_DATE) < #twoWeeksAhead)
OR (CONVERT(DATE, TASK_END_DATE) >= #twoWeeksAgo
AND CONVERT(DATE, TASK_END_DATE) < #twoWeeksAhead)

SQL Server remove milliseconds from datetime

select *
from table
where date > '2010-07-20 03:21:52'
which I would expect to not give me any results... EXCEPT I'm getting a record with a datetime of 2010-07-20 03:21:52.577
how can I make the query ignore milliseconds?
You just have to figure out the millisecond part of the date and subtract it out before comparison, like this:
select *
from table
where DATEADD(ms, -DATEPART(ms, date), date) > '2010-07-20 03:21:52'
If you are using SQL Server (starting with 2008), choose one of this:
CONVERT(DATETIME2(0), YourDateField)
LEFT(RTRIM(CONVERT(DATETIMEOFFSET, YourDateField)), 19)
CONVERT(DATETIMEOFFSET(0), YourDateField) -- with the addition of a time zone offset
Try:
SELECT *
FROM table
WHERE datetime >
CONVERT(DATETIME,
CONVERT(VARCHAR(20),
CONVERT(DATETIME, '2010-07-20 03:21:52'), 120))
Or if your date is an actual datetime value:
DECLARE #date DATETIME
SET #date = GETDATE()
SELECT CONVERT(DATETIME, CONVERT(VARCHAR(20), #date, 120))
The conversion to style 120 cuts off the milliseconds...
select * from table
where DATEADD(ms, DATEDIFF(ms, '20000101', date), '20000101') > '2010-07-20 03:21:52'
You'll have to trim milliseconds before comparison, which will be slow over many rows
Do one of these to fix this:
created a computed column with the expressions above to compare against
remove milliseconds on insert/update to avoid the read overhead
If SQL Server 2008, use datetime2(0)
Use CAST with following parameters:
Date
select Cast('2017-10-11 14:38:50.540' as date)
Output: 2017-10-11
Datetime
select Cast('2017-10-11 14:38:50.540' as datetime)
Output: 2017-10-11 14:38:50.540
SmallDatetime
select Cast('2017-10-11 14:38:50.540' as smalldatetime)
Output: 2017-10-11 14:39:00
Note this method rounds to whole minutes (so you lose the seconds as well as the milliseconds)
DatetimeOffset
select Cast('2017-10-11 14:38:50.540' as datetimeoffset)
Output: 2017-10-11 14:38:50.5400000 +00:00
Datetime2
select Cast('2017-10-11 14:38:50.540' as datetime2)
Output: 2017-10-11 14:38:50.5400000
For this particular query, why make expensive function calls for each row when you could just ask for values starting at the next higher second:
select *
from table
where date >= '2010-07-20 03:21:53'
Use 'Smalldatetime' data type
select convert(smalldatetime, getdate())
will fetch
2015-01-08 15:27:00
There's more than one way to do it:
select 1 where datediff(second, '2010-07-20 03:21:52', '2010-07-20 03:21:52.577') >= 0
or
select *
from table
where datediff(second, '2010-07-20 03:21:52', date) >= 0
one less function call, but you have to be beware of overflowing the max integer if the dates are too far apart.
One more way I've set up SQL Server queries to ignore milliseconds when I'm looking for events from a particular second (in a parameter in "YYYY-MM-DD HH:TT:SS" format) using a stored procedure:
WHERE
...[Time_stamp] >= CAST(CONCAT(#YYYYMMDDHHTTSS,'.000') as DateTime) AND
...[Time_stamp] <= CAST(CONCAT(#YYYYMMDDHHTTSS,'.999') as DateTime)
You could use something similar to ignore minutes and seconds too.
Please try this
select substring('12:20:19.8470000',1,(CHARINDEX('.','12:20:19.8470000',1)-1))
(No column name)
12:20:19
I'm very late but I had the same issue a few days ago. None of the solutions above worked or seemed fit. I just needed a timestamp without milliseconds so I converted to a string using Date_Format and then back to a date with Str_To_Date:
STR_TO_DATE(DATE_FORMAT(your-timestamp-here, '%Y-%m-%d %H:%i:%s'),'%Y-%m-%d %H:%i:%s')
Its a little messy but works like a charm.
May be this will help..
SELECT [Datetime] = CAST('20120228' AS smalldatetime)
o/p:
2012-02-28 00:00:00
Review this example:
declare #now datetimeoffset = sysdatetimeoffset();
select #now;
-- 1
select convert(datetimeoffset(0), #now, 120);
-- 2
select convert(datetimeoffset, convert(varchar, #now, 120));
which yields output like the following:
2021-07-30 09:21:37.7000000 +00:00
-- 1
2021-07-30 09:21:38 +00:00
-- 2
2021-07-30 09:21:37.0000000 +00:00
Note that for (1), the result is rounded (up in this case), while for (2) it is truncated.
Therefore, if you want to truncate the milliseconds off a date(time)-type value as per the question, you must use:
declare #myDateTimeValue = <date-time-value>
select cast(convert(varchar, #myDateValue, 120) as <same-type-as-#myDateTimeValue>);

Resources