How can I get a count every 60 days using datepart()? - sql-server

The following query (on MSSQL) gives a correct answer to the question : how many IPs were collected on a monthly basis.
I would like to know how can I get the count per every 60 days?
select MIN(rowdate) min_rowdate,
MAX(rowdate) max_rowdate,
count(distinct IP),
DATEPART(MONTH, rowdate) month_
from t_tbl tl (nolock)
where rowdate between '2015-01-01 00:00:00' and '2015-12-31 23:59:59'
group by DATEPART(MONTH, rowdate)

You can get it using DATEDIFF and some simple maths:
select MIN(rowdate) min_rowdate, MAX(rowdate) max_rowdate, count(distinct IP),
DATEDIFF(day, 0, rowdate) / 60 as day60
from t_tbl tl (nolock)
where rowdate >= '20150101' and rowdate < '20160101'
group by DATEDIFF(day, 0, rowdate) / 60
In this instance, it's using 1900-01-01 (what 0 gets converted to) as the start of the first 60 day period and all subsequent periods follow on from there.
If you want to use a different "fixed point" for the reporting periods, you'd put that in in place of 0 as the second parameter to DATEDIFF.
(I've also corrected your WHERE clause so that it doesn't exclude any events that happened during the last second of 2015, i.e. with a non-zero milliseconds value)

Related

Get result of last six months using DATEDIFF

I'm trying to create a query in SQL that retrieves rows based on their date.
I want to get the result of the last 6 months using DATEDIFF() function (and not another function ) but my query still returns rows that are greater than GETUTCDATE().
The query that I use is:
SELECT * FROM CARS
WHERE DATEDIFF(d, c.ExpiredWarranty, GETUTCDATE()) < 180
Why am i still getting results that are greater than GETUTCDATE() ?
First, you may think you want to use datediff, but the fact that you are using it (or any other function, for that matter) on a column makes it impossible for SQL Server to use any indexes defined with this column - and that might be a real performance penalty for that.
Second, the reason you get records for future dates is that if the first date is later than the second date, the DateDiff function will return a negative number. All negative numbers I know of are smaller than 180.
A better query would be this:
SELECT *
FROM CARS
WHERE c.ExpiredWarranty <= GETUTCDATE()
-- If you want 6 months, don't bother with days...
AND c.ExpiredWarranty > DATEADD(MONTH, -6, GETUTCDATE())
DATEDIFF returns a positive number whenever the third argument is greater than the second. In your case, you want records whose warranties have expired within 6 months. On one extreme, this is 180 days, and the other extreme, this is 0 days. For warranties expiring in the future, your current call to DATEDIFF would return a negative number.
To fix this, just restrict the DATEDIFF output to between 0 and 180 days, and don't allow negative diffs:
SELECT *
FROM CARS
WHERE DATEDIFF(d, c.ExpiredWarranty, GETUTCDATE()) BETWEEN 0 AND 180;
Because if ExpiredWarranty > GETUTCDATE() then DATEDIFF between them returns a negative number which is definitely less then 180.
Try:
SELECT *
FROM CARS
WHERE DATEDIFF(d, c.ExpiredWarranty, GETUTCDATE()) < 180
AND DATEDIFF(d, c.ExpiredWarranty, GETUTCDATE()) >= 0;
Or:
SELECT *
FROM CARS
WHERE DATEDIFF(d, c.ExpiredWarranty, GETUTCDATE()) BETWEEN 0 AND 180;
Try this:
WHERE DATEDIFF(d, c.ExpiredWarranty, GETUTCDATE()) >= 0 AND DATEDIFF(d, c.ExpiredWarranty, GETUTCDATE()) < 180
Or:
WHERE DATEDIFF(d, c.ExpiredWarranty, GETUTCDATE()) BETWEEN 0 AND 180;

TSQL - How can I get the total count in a group as part of a subquery

I need to get the total calls per hour per user for the current day, and then calculate the percentage of incoming calls taken by that user. The purpose in the final analysis is to calculate the percentage of a bonus pot per user per hour.
I can get most of the bits, but am stuck calculating the total calls per hour i.e. total calls for each hour group...
Assume the following data (change the date to the current date if necessary):
EnteredBy EnteredOn
Lisa Scandaleyes 05/07/2017 07:40:04
Fred Smith 05/07/2017 07:54:17
A User 05/07/2017 08:15:06
Johnny Johnson 05/07/2017 08:20:57
A User 05/07/2017 09:27:29
A User 05/07/2017 09:36:16
A User 05/07/2017 09:42:36
A User 05/07/2017 10:09:57
I can easily get the calls per hour:
SELECT DATEPART(HOUR, [EnteredOn]) AS Hour, Count(*) as CallsPerHour
FROM CallHandlingCallData
WHERE DATEDIFF(d, EnteredOn, GetDate())= 0
GROUP BY DATEPART(HOUR, [EnteredOn])
ORDER BY Hour;
I can also get the calls per hour per user:
SELECT DATEPART(HOUR, [EnteredOn]) AS Hour, EnteredBy, Count(*) as CallsTaken
FROM CallHandlingCallData
WHERE DATEDIFF(d,EnteredOn, GetDate())= 0
GROUP BY DATEPART(HOUR, [EnteredOn]), EnteredBy
ORDER BY Hour, EnteredBy;
I can even get almost everything I want EXCEPT that the TotalCalls value and therefore the percentage is based on ALL CALLS for the day:
SELECT Hour, EnteredBy, CallsTaken, TotalCalls,
CAST(CallsTaken AS Decimal(10,2)) * 100 / CASE TOTALCALLS WHEN 0 THEN 1 ELSE TotalCalls END AS Percentage
FROM
(
SELECT DATEPART(HOUR, [EnteredOn]) AS Hour, EnteredBy, Count(*)as CallsTaken,
(SELECT Count(*) FROM CallHandlingCallData WHERE DATEDIFF(d, EnteredOn, GetDate())= 0) AS TotalCalls
FROM CallHandlingCallData
WHERE DATEDIFF(d,EnteredOn, GetDate())= 0
GROUP BY DATEPART(HOUR, [EnteredOn]), EnteredBy
) data
ORDER BY Hour, EnteredBy;
This is really close, I just need to get the sub-query to give me the total calls for that hour, instead of for the whole day.
Notes
I am unable to use OVER() as it is not supported by one of the database engines that may be used with the final query.
I would prefer to do such calculations in the UI, but the result has to be output to a spreadsheet removing that option
Any suggestions would be most gratefully received.
Calculate the group totals separately, then join them up.
SELECT
DATEPART(HOUR, C1.EnteredOn) AS [Hour],
C1.EnteredBy,
COUNT(*) AS CallsTaken,
Totals.TotalCalls,
CAST(COUNT(*) * 100.0 / Totals.TotalCalls AS DECIMAL(10, 2)) AS [Percentage]
FROM CallHandlingCallData AS C1
JOIN (
SELECT DATEPART(HOUR, C2.EnteredOn) AS [Hour], COUNT(*) AS TotalCalls
FROM CallHandlingCallData AS C2
WHERE DATEDIFF(d, C2.EnteredOn, GetDate()) = 0
GROUP BY DATEPART(HOUR, C2.EnteredOn)
) Totals ON Totals.[Hour] = DATEPART(HOUR, C1.EnteredOn)
WHERE DATEDIFF(d, C1.EnteredOn, GetDate()) = 0
GROUP BY
DATEPART(HOUR, C1.EnteredOn),
C1.EnteredBy,
Totals.TotalCalls
If it looks like I'm overdoing it with the aliases: I find it's much better to be safe than sorry when it comes to disambiguating inner and outer column references.
If you need rows with zeroes for hours where nothing happened as well, that's a whole 'nother kettle of fish with UNION ALL and number tables and I'm not going there unless we have to.

BETWEEN clause in DATEPART function

I want to use BETWEEN clause in this query and don't know how to do this
SELECT * FROM record
WHERE (DATEPART(yy, register_date) = 2009
AND DATEPART(mm, register_date) = 10
AND DATEPART(dd, register_date) = 10)
This gives the records from 5 hours ago until now
SELECT * FROM record
WHERE register_date BETWEEN DATEADD(HOUR, -5, GETDATE()) AND GETDATE()
It appears this might be what you want
select * from record
where register_date between '2017-10-1' and '2017-12-31'
Do note that if you want all records from the last day you might want to add time or pick the day after as the default time is 0:00

Why is using hardcoded minutes in DateAdd way faster than using a field's value?

Dealing with a SQL issue and I'm not a SQL person, so need some guidance.
Given the SQL statement below, note that the first one uses a hardcoded value of "-360" in the DateAdd function, whereas the second uses a field value (OFFSET) that exists on every record (which has the value of either "-360" or "-300" depending on DST time of year).
Running the first query is extremely fast, while the second takes about 40 seconds longer.
Can someone tell me what the difference is that takes the second so much longer to execute, and because I HAVE to use that record's value and not hard code it, how can I speed up that query?
Query 1 (FAST):
SELECT 0 AS 'TempIndex', COUNT(*) AS 'TotalLY'
FROM CLOGS15 h
WHERE h.EVTYPE = 1
AND DateAdd(minute, -360, h.EVDATE) BETWEEN '2015-01-01 00:00:00.000' AND '2015-01-24 00:00:00.000'
Query 2 (SLOW):
SELECT 0 AS 'TempIndex', COUNT(*) AS 'TotalLY'
FROM CLOGS15 h
WHERE h.EVTYPE = 1
AND DateAdd(minute, OFFSET, h.EVDATE) BETWEEN '2015-01-01 00:00:00.000' AND '2015-01-24 00:00:00.000'
I could only imagine that the issue is sargability (the user of an index). However, I thought that dateadd() would prevent the use of an index. If you want to fix this though, perhaps this will work:
SELECT 0 AS TempIndex, COUNT(*) AS TotalLY
FROM CLOGS15 h
WHERE h.EVTYPE = 1 AND offset = 360 AND
DateAdd(minute, -360, h.EVDATE) BETWEEN '2015-01-01' AND '2015-01-24'
UNION ALL
SELECT 0 AS TempIndex, COUNT(*) AS TotalLY
FROM CLOGS15 h
WHERE h.EVTYPE = 1 AND offset = 300 AND
DateAdd(minute, -300, h.EVDATE) BETWEEN '2015-01-01' AND '2015-01-24';
EDIT:
Oops, the above returns two rows and you want one. So, use a subquery:
SELECT TempIndex, SUM(TotalLY) as TotalLY
FROM (SELECT 0 AS TempIndex, COUNT(*) AS TotalLY
FROM CLOGS15 h
WHERE h.EVTYPE = 1 AND offset = 360 AND
DateAdd(minute, -360, h.EVDATE) BETWEEN '2015-01-01' AND '2015-01-24'
UNION ALL
SELECT 0 AS TempIndex, COUNT(*) AS TotalLY
FROM CLOGS15 h
WHERE h.EVTYPE = 1 AND offset = 300 AND
DateAdd(minute, -300, h.EVDATE) BETWEEN '2015-01-01' AND '2015-01-24'
) h
GROUP BY TempIndex;
In your case, I think that the difference between the fast and slow queries relies in the index usage.
The SQL Server, in your fast query, might be rewriting your DATEADD to enable the index usage on EVDATE. As you are adding the date and a constant and checking if it is between to constant dates, it is probably moving the DATEADD from the left side (before the BETWEEN) to the right side (the dates after BETWEEN, reversing the signal of the constant in the DATEADD.
Your original is:
DateAdd(minute, -360, h.EVDATE)
BETWEEN '2015-01-01 00:00:00.000'
AND '2015-01-24 00:00:00.000'
It might be turning to:
h.EVDATE
BETWEEN DateAdd(minute, 360, '2015-01-01 00:00:00.000')
AND DateAdd(minute, 360, '2015-01-24 00:00:00.000')
Which is just the same as your original filter, but enables the index usage and the processing of DATEADD, as only two dates need to be processed, instead of all dates in your table.
In your slow query, as your DATEADD is using a variable offset, SQL Server can't apply the same rule described above and so it processes all dates in your table with DATEADD, which is really slower.
I think you should try a filter like the fast query, maybe something like this:
SELECT 0 AS 'TempIndex', COUNT(*) AS 'TotalLY'
FROM CLOGS15 h
WHERE 1=1
AND h.EVTYPE = 1
AND
(0=1
OR
(1=1
AND OFFSET = -360
AND h.EVDATE >= DateAdd(minute, 360, '2015-01-01 00:00:00.000')
AND h.EVDATE <= DateAdd(minute, 360, '2015-01-24 00:00:00.000')
)
OR
(1=1
AND OFFSET = -300
AND h.EVDATE >= DateAdd(minute, 300, '2015-01-01 00:00:00.000')
AND h.EVDATE <= DateAdd(minute, 300, '2015-01-24 00:00:00.000')
)
)
Would be great to this query an index with columns EVTYPE, OFFSET, EVDATE respectively.

Adding Month-to-date and Year-to-date in a query using SQL Server 2012?

so I'm trying to make a query that includes a daily sum of the amount from the first instance the database starts collecting data to the last available instance of that date (database collects data every hour). And while I have done this, now I have to make it show a month to date and a year to date sum amount. I have tried various ways to come up with this but have had no luck. Below is the code that I believe is the closest I have gotten to achieve this. Can someone help me make my code work or suggest another way around this?
Select * from
(
SELECT Devices.DeviceDesc,
SUM(DeviceSummaryData.Amount) AS MTD,
Devices.Area,
MIN(DeviceSummaryData.StartDate) AS FirstOfStartDate,
MAX(DeviceSummaryData.EndDate) AS LastOfStartDate
FROM Devices INNER JOIN DeviceSummaryData ON Devices.DeviceID = DeviceSummaryData.DeviceID
WHERE (DeviceSummaryData.StartDate = MONTH(getdate())) AND (DeviceSummaryData.EndDate <= CAST(DATEADD(DAY, 1, GETDATE())
AS date))
GROUP BY Devices.DeviceDesc, Devices.Area, DATEPART(day, DeviceSummaryData.StartDate)
--
) q2
UNION ALL
SELECT * FROM (
SELECT Devices.DeviceDesc,
Sum(Amount) as Daily,
Devices.Area,
MIN(StartDate) as FirstDate,
MAX(DeviceSummaryData.EndDate) AS LastOfStartDate
FROM Devices INNER JOIN DeviceSummaryData ON Devices.DeviceID = DeviceSummaryData.DeviceID
WHERE (DeviceSummaryData.StartDate >= CAST(DATEADD(DAY, 0, GETDATE()) AS date)) AND (DeviceSummaryData.EndDate <= CAST(DATEADD(DAY, 1, getdate()) AS date))
GROUP BY Devices.Area,
Devices.DeviceDesc,
DATEPART(day, DeviceSummaryData.StartDate)
ORDER BY Devices.DeviceDesc
) q2
Another type of attempt I have tried would be this:
SELECT Devices.DeviceDesc,
Sum(case
when DeviceSummaryData.StartDate >= CAST(DATEADD(DAY, 0, getdate()) AS date)
THEN Amount
else 0
end) as Daily,
Sum(case
when Month(StartDate) = MONTH(getdate())
THEN Amount
else 0
end) as MTD,
Devices.Area,
MIN(StartDate) as FirstDate,
MAX(DeviceSummaryData.EndDate) AS LastOfStartDate
FROM Devices INNER JOIN DeviceSummaryData ON Devices.DeviceID = DeviceSummaryData.DeviceID
WHERE (DeviceSummaryData.StartDate >= CAST(DATEADD(DAY, 0, GETDATE()) AS date)) AND (DeviceSummaryData.EndDate <= CAST(DATEADD(DAY, 1, getdate()) AS date))
GROUP BY Devices.Area,
Devices.DeviceDesc,
DATEPART(day, DeviceSummaryData.StartDate)
ORDER BY Devices.DeviceDesc
I'm not the best with Case When's, but I saw somewhere that this is a possible way to do this. I'm not too concerned with the speed or efficiency, I just need it to generate the query to be able to get the data. Any help and Suggestions are greatly appreciated!
The second attempt is on the right track but a bit confused. In the CASE statements you are trying to compare months etc, but your WHERE clause restricts the data you're looking at to a single day. Also, your GROUP BY should not include the day anymore. If you say in English what you want, it's "For each device area and type, I want to see a total, a MTD total and a YTD total". It's that "For each" bit that should define what appears in your GROUP BY.
Just remove the WHERE clause entirely and get rid of DATEPART(day, DeviceSummaryData.StartDate) from your GROUP BY and you should get the results you want. (Well, a daily and monthly total, anyway. Yearly is achieved much the same way).
Also note that DATEADD(DAY, 0, GETDATE()) is identical to just GETDATE().

Resources