I'm struggling with what I thought would be a simple SQL query. Running SQL Server 2014
I have an SQL table, "Visits":
Id | EntryTime | Duration
And I want to find the average entry TIME OF DAY between two dates, taking into account all records between those dates.
so if my EntryTime field between my dates is:
2016-04-28 12:00:00
2016-04-20 10:00:00
2016-04-19 08:00:00
2016-04-17 10:00:00
Then the average time returned should just be:
10:00:00
The date should not be taken into account at all, and it should be returned in string format, or a manner which returns ONLY 10:00:00.
create table mytimes(
id int identity,
mydatetime datetime
)
insert into mytimes (mydatetime) values ('2016-04-28 12:00:00')
insert into mytimes (mydatetime) values ('2016-04-20 10:00:00')
insert into mytimes (mydatetime) values ('2016-04-19 08:00:00')
insert into mytimes (mydatetime) values ('2016-04-17 10:00:00')
SELECT Cast(DateAdd(ms, AVG(CAST(DateDiff( ms, '00:00:00', cast(mydatetime as time)) AS BIGINT)), '00:00:00' ) as Time )
from mytimes
-- where mydatetime between XXX and YYY
SELECT convert(varchar(8), Cast(DateAdd(ms, AVG(CAST(DateDiff( ms, '00:00:00', cast(mydatetime as time)) AS BIGINT)), '00:00:00' ) as Time ))
from mytimes
-- where mydatetime between XXX and YYY
output-1 10:00:00.0000000 - this is an actual Time type that you can do more with if needed
output-2 10:00:00 - this is output as a varchar(8)
Add your where clause as you see fit
The steps include
Casting to a Time type from a DateTime.
Using the AVG on Time, this is not supported by type Time so you have to first convert Time to milliseconds.
Converting the milliseconds back to a Time type
To avoid Arithmetic overflow error converting expression to data type int you can cast the result of DateAdd to a BigInt. Alternatively you can use seconds instead of milliseconds in the DateDiff function which should work unless your result set is overly large.
SO Sources:
T-SQL calculating average time
How to get Time from DateTime format in SQL?
Operand data type time is invalid for avg operator…?
SELECT CONVERT(TIME, DATEADD(SECOND, AVG(DATEDIFF(SECOND, 0, CONVERT(TIME, EntryTime ))), 0))
FROM Visits
WHERE EntryTime >= #begin AND EntryTime <= #end
The idea came from here: T-SQL calculating average time
Yap, you can use the Time() to get this done.
Your query becomes like this (modify accordingly)
SELECT COUNT(*) FROM Visits WHERE TIME(EntryTime) > '06:00' AND EntryTime < '18:00';
Related
select DATENAME(month,29*5)
Can any one please tell me logic behind the above query.
How it always returns correct month name when provided month number as integer.
Datetime values in Sql server are stored on 8 bytes.
The first 4 bytes represents the date and the last 4 byte represents the time.
On the date part, date is stored as the number of days since 1900-01-01.
On the time part, it's the number of clock ticks since midnight.
There are 300 clock ticks per second, so a tick is 3.33333 milliseconds.
That's also the reason why datetime is only accurate to .003 of a second.
This query will hopefully help to explain:
SELECT CAST(0 As datetime) As Date_0,
29*5 As NumberOfDays,
CAST(29*5 as datetime) As TheDate,
DATENAME(month,29*5) As TheMonthName
Results:
Date_0 NumberOfDays TheDate TheMonthName
----------------------- ------------ ----------------------- ------------
1900-01-01 00:00:00.000 145 1900-05-26 00:00:00.000 May
As for the last part of your question, 29 (28 would work as well) is the magic number here - 30 is too big (May would be returned for 4 and 5) and 27 is too small - (September would be returned for 9 and 10).
Basically i'ts just math - get the number correctly so that each time you double it with any number between 1 and 12 will give you a number of days that sums up to a day that belongs to the correct month.
You can test it yourself using this script:
DECLARE #MagicNumber int = 28
;With cte as
(
select 1 as num
union all
select num + 1
from cte
where num < 12
)
SELECT num, DATENAME(month, #MagicNumber * num ) As TheMonthName
from cte
Just change the value of #MagicNumber and see the results you get.
I think I will able to explain.
The default year-month-day for any date data type is 1900-01-01. If we consider above select query, it add 29*5 days into default date and gives the MONTHNAME.
Select DATENAME(month,29*5)
Now understand the DATENAME
DateName - Returns a character string that represents the specified datepart of the specified date. Its have different -2 argument and give the different-2 result as per datepart.
Argument 1 - Is the part of the date to return.
Argument 2 - Is a any date (Is an expression that can be resolved to a
time, date, smalldatetime, datetime, datetime2, or datetimeoffset
value.)
Here we given month as a first argument. Which means it return monthname.
The calculation of 29*5 gives 145 answer and if we simply cast into date it consider as a days and calculate as 1900-01-01 + 145 and gives the date 1900-05-26 00:00:00.000.
Means if we get the month of this will give the 5 - MAY as a answer.
Execute this query and check the answer for the above logic.
Select DATENAME(month,29*5), (29*5) , DATENAME(month, '12:10:30.123'), DATENAME(month, getdate())
select cast (145 as datetime)
DECLARE #t datetime = '12:10:30.123';
SELECT DATENAME(month, 29*5), 145/30.00;
Check for further.
MSDN Link
Convert Month Number to Month Name Function in SQL (check the #user275683 answer)
If you are simply want to show the month corresponding to month number then you should have to use like this.
declare #intMonth as int
set #intMonth = 5
Select DateName( month , DateAdd( month , #intMonth , -1 ))
I want to get last month's last date (whether 30 or 31) and time till last second, whenever the query is executed.
Eg. 11/30/2015 11:59:59 PM
So I have a query like
SELECT DATEADD(ss, (60*60*24)-1, DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), -1))
It solves my problem. But what is the difference between the query written above and the one below, when I change the DATEDIFF part and replace 0 with 1?
SELECT DATEADD(ss, (60*60*24)-1, DATEADD(MONTH, DATEDIFF(MONTH, 1, GETDATE()), -1))
Will both of these queries gives the same result whenever they are run, or which should I consider as the permanent solution?
Do NOT do this; attempt to get the "last second" of the last day of the previous month
I make this bold statement on the assumption you are attempting to use BETWEEN and you are concerned with the accuracy of something like this:
select sum(value) from Atable
where [Adate] BETWEEN '20151201' AND '21051231 23:59:59'
But the complexity of arriving at the last point in time on the last day of any month is solved so easily by using the first day of the next month instead. All that you also need to do is drop the use of BETWEEN. Like this:
select sum(value) from Atable
where [Adate] >= '20151201' and [Adate] < '21060101'
LESS THAN "the first day of the this month"
That is how you solve your conundrum.
& by the way: The precision (accuracy) of smalldatetime, datetime and datetime2 all differ, all the more reason not to use BETWEEN.
see "Be careful about rounding errors." at http://sqlmag.com/t-sql/t-sql-best-practices-part-2
Specifically, do this:
DateLogged < SELECT DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0)
This will be 100% accurate for date, smalldatetime, datetime and datetime2 columns.
Here is another attempt to explain why LESS THAN [the_next_day_at_00:00:00+0000000] is accurate and using 22:59:59 is NOT accurate. Please take note of the sample data accuracy
SQL Fiddle
MS SQL Server 2014 Schema Setup:
Query 1:
DECLARE #Tbl TABLE
( [ID] int identity(1,1)
, [DT_a] datetime
, [DT_b] datetime
, [DT_c] datetime2
)
INSERT INTO #Tbl
([Dt_a], [Dt_b], [Dt_c])
VALUES
(
'20151231 23:59:59'
, '20151231 23:59:59.997'
, '20151231 23:59:59.9999999'
)
select
'where [DT_b] <= 20151231 23:59:59' as FilterString
, max([Dt_a]) as [Dt_a]
, max([Dt_b]) as [Dt_b]
, max([Dt_c]) as [Dt_c]
from #Tbl
where [DT_b] <= '20151231 23:59:59'
UNION ALL
select
'where [DT_b] < 20160101'
, max([Dt_a]) as [Dt_a]
, max([Dt_b]) as [Dt_b]
, max([Dt_c]) as [Dt_c]
from #Tbl
where [DT_b] < '20160101'
Results:
| FilterString | Dt_a | Dt_b | Dt_c |
|-----------------------------------|----------------------------|----------------------------|-----------------------------|
| where [DT_b] <= 20151231 23:59:59 | (null) | (null) | (null) |
| where [DT_b] < 20160101 | December, 31 2015 23:59:59 | December, 31 2015 23:59:59 | 2015-12-31 23:59:59.9999999 |
Data accuracy
smalldatetime: one minute
datetime: rounded to increments of .000, .003, or .007 seconds
datetime2: 100 nanoseconds
To avoid possible errors from rounding by time units DO NOT USE <= 23:59:59
Instead use LESS THAN [the_next_day]
AND, as a consequence AVOID USING BETWEEN for date ranges.
See this link for how DATEDIFF is used or should be used in SQL Server. The 2nd argument, the one which does not seem to make a difference in your case, is supposed to be the start date which is subtracted from the end date (getdate()) to get the difference and then converted to months. I would try and use this function the typical way and provide a proper start date.
Also below is an alternative way of getting the same result
SELECT DATEADD(ss, -1, '01/' + CONVERT(VARCHAR, DATEPART(MONTH, getdate())) + '/' + CONVERT(VARCHAR, DATEPART(YEAR, getdate())));
It is because of DATEDIFF( MONTH, 0, GETDATE()) Function
If you use an integer as the second argument, this is interpreted as the number of days since 1900-01-01 regardless of the Interval you are using in the datediff function.
For eg:
SELECT YEAR(0), MONTH(0), DAY(0);
year month day
1900 1 1
Now if I Increment 0 to 1 in year, month, day
SELECT YEAR(1), MONTH(1), DAY(1);
year month day
1900 1 2
Now if I Increment values to 365,
SELECT YEAR(365), MONTH(365), DAY(365);
year month day
1901 1 1
You can see the Year got incremented by 1.
there are many ways to find out the previous month's last date. Here is the one I am using.
SELECT DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,GETDATE()),0))
Well it would be expected that substracting one millisecond to the first day of the current month you would get the last millisecond of the previous month but it doesn't work that whay, with datediff millisecond,-1 you still get the first day of the month you have to do datediff millisecond,-2 to reach 997 milliseconds, no way to get 999 nor 998.(without using text).
select dateadd(MILLISECOND,-2,dateadd(month, datediff(month, 0, getdate()), 0))
And you get 2020-01-31 23:59:59.997
To get last second of current month use:
SELECT DATEADD(MILLISECOND, -10, CAST (EOMONTH(GETDATE()) AS DATETIME))
and you get:
2021-12-30 23:59:59.000
Explanation: takes begin of next month (2021-12-31) and convert to datetime (2021-12-31 00:00:00.000) then takes 1 second to get (2021-12-30 23:59:59.000)
Looking for assistance with a strange issue if anyone has ideas:
I have a SQL that statement works most of the time in a T-SQL script but crashes occasionally. I have identified the data that a crash occurs on and cannot identify any difference between data rows that work.
The goal of this code is to add the time to an already existing datetime value that has 00:00:00 as the time from the second time column (as outlined below). My goal is to combine both columns into YYYY-MM-DD HH:MM:SS format, but I had to convert them to char first to trim off the orignal 00:00:00.
Columns
LogDate - contains date only in DateTime format (YYYY-MM-DD HH:MM:SS)
LogTime - contains the time of the action and is in varchar format (HH:MM)
SQL Conversion
SELECT CONVERT(DATETIME, CONVERT(CHAR(8), LogDate, 112) + ' ' + CONVERT(CHAR(8), LogTime, 108))
FROM TestTable
WHERE EventSerial = '100001'
However, if I change the EventSerial in the above statement to a different row, such as '100002', the statement works.
The data for each row is below:
EventSerial 100001's values:
LogDate: 2015-04-02 00:00:00.000
LogTime: 10:04
EventSerial 100002's values:
LogDate: 2015-04-02 00:00:00.000
LogTime: 10:48
Running with data set 1 fails, running with data set 2 produces output. Also, running the code without the final datetime conversion works, or if I run the code with the string manually it works (as outlined below:)
SELECT CONVERT(CHAR(8), LogDate, 112) + ' ' + CONVERT(CHAR(8), LogTime, 108)
FROM TestTable
WHERE EventSerial = '100001'
SELECT CONVERT(DATETIME, '20150402 10:48')
SELECT CONVERT(DATETIME, '20150402 10:04')
Any suggestions, I'm sure its something silly that I'm missing (and I probably took the long way around the issue anyway. The desired output would be 2015-04-02 10:04:00
First, datetime has no format. (why?)
Second, you don't need to convert the datetime value to char to add hours and minutes, just use DateAdd:
SELECT DATEADD(Minute,
CAST(RIGHT(LogTime, 2) as int),
DATEADD(Hour,
CAST(LEFT(LogTime, 2) as int),
LogDate
)
)
FROM TestTable
WHERE EventSerial = '100001'
Also, note that convert does not hold a style for yyyymmdd hh:mm
Note: code was written directly here, there might be some mistakes.
I'm not sure why you're getting the error... possibly there are some unseen characters in your varchar time field... like a tab or something maybe? Try this query:
SELECT ascii(substring(LogTime,1,1)) Char1,
ascii(substring(LogTime,2,1)) Char2,
ascii(substring(LogTime,3,1)) Char3,
ascii(substring(LogTime,4,1)) Char4,
ascii(substring(LogTime,5,1)) Char5
FROM TestTable
WHERE EventSerial = '100001'
It should show these results:
Char1 Char2 Char3 Char4 Char5
----------- ----------- ----------- ----------- -----------
49 48 58 48 52
(1 row(s) affected)
This would be a bit more efficient:
select dateadd(minute, datediff(minute,0, LogTime), LogDate)
FROM TestTable
But this assumes that your date field always has 00:00:00 time information. If you want to be sure that is stripped out as well you could use:
select dateadd(minute, datediff(minute,0, LogTime), dateadd(day, datediff(day, 0, Logdate),0))
FROM TestTable
I am getting some problems in getting one query done.
I have 2 strings which are suppose to store time in this format (HH:mm Ex: Date1 -> '20:20' and Date2 -> '21:20'). I want to get the current time (CONVERT(VARCHAR(5),getdate(),108) -> to get only 'HH:mm') and get all rows that are between the current date.
The idea is: Select * where "getdate() between date1 and date2", this is not a query, only the idea.
Edit:
Time1 Time2
08:20 09:05
09:05 09:50
10:05 10:50
10:50 11:35
11:45 12:30
12:30 13:15
13:35 14:20
Thx for the solutions, I will check them, but one question how can i "fix" the problem when it's 9:05 (for example), I will get both rows/records, I assume i have to go to the seconds right?
The following constructs two DATETIMEs, based on TIME only, from your input strings (#TimeStr1 and #TimeStr2) then strips the DATE part from GETDATE(). Then you can easily do the query with BETWEEN.
DECLARE
#CurrentTime DATETIME,
#TimeStr1 VARCHAR(12),
#TimeStr2 VARCHAR(12),
#Time1 DATETIME,
#Time2 DATETIME
SET #TimeStr1 = '20:20'
SET #TimeStr2 = '21:20'
SET #Time1 = CAST('1900-01-01 ' + #TimeStr1 AS DATETIME)
SET #Time2 = CAST('1900-01-01 ' + #TimeStr2 AS DATETIME)
-- The following strips the date from datetime leaving only a time
SET #CurrentTime = DateAdd(ss, DateDiff(ss, DateAdd(dd, DateDiff(dd, 0, GetDate()), 0), GetDate()), 0)
SELECT * FROM YourTable WHERE #CurrentTime BETWEEN #Time1 AND #Time2
This will work for all versions of SQL SERVER. If you are using a version >= 2008 then a simpler version of the above exists.
If you are worried about overlaps (eg your "09:05" problem) use the following query that avoids the problem by using > and <= rather then BETWEEN
SELECT * FROM YourTable WHERE #CurrentTime > #Time1 AND #CurrentTime <= #Time2
I'm trying to create utilization graph for a telephone system. I have sets of data which is in the table format
ID *
StartDate
EndDate
From
To
What I'm trying to do is get SQL to to output me a list of plot points every 5 minutes, so basically
The count of active calls (between StartDate and EndDate) for every 5 minutes in a day.
The result beign something like
Date Time Count
2000-01-01 00:00:00 10
2000-01-01 00:05:00 2
2000-01-01 00:10:00 7
Can anyone suggest a way to generate said data? I'm at a loss here! The stuff I've been thinking abut all involves a creating a big loop and running a query for every 5 seconds which seems super inefficient.
The method I was originally thinking was :-
storedProc GetSamples(SampleStartDate, SampleEndDate)
Create memory table for result data
for every 5mins as sample between SampleStartDate and SampleEndDate
SELECT #SampleCount = COUNT(1) FROM Samples where 5mins BETWEEN StartDate AND EndDate
UPDATE memoryTable SET count=#SampleCount WHERE time = 5mins
end
end
If you have a recent enough version of SQL Server (2008+), you ought to be able to do this with a CTE joined to your phone call log, like this (CTE base found here):
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
SET #StartDate = '2000-01-01 00:00:00.000'
SET #EndDate = '2000-01-02 00:00:00.000'
;WITH DateSequence( [PlotPointDate] ) AS
(
SELECT #StartDate AS [PlotPointDate]
UNION ALL
SELECT DATEADD(MINUTE, 5, [PlotPointDate] )
FROM DateSequence
WHERE [PlotPointDate] < #EndDate
)
--select result
SELECT
PlotPointDate, COUNT(YourTable.ID) AS TotalActiveCalls
FROM
DateSequence
LEFT JOIN
YourTable ON
YourTable.StartDate <= DateSequence.PlotPointDate AND
(YourTable.EndDate >= DateSequence.PlotPointDate OR YourTable.EndDate IS NULL)
GROUP BY PlotPointDate
OPTION (MaxRecursion 10000)