sql server group by datetime cut-off point of 10:00am - sql

I'm trying to write a Microsoft SQL Server 2005 query that counts a total value, grouped by date with a cut-off point of 10:00am ?
eg: Table Orders
DateReceived Total
01-01-2012 06:10:01 2
01-01-2012 08:10:01 2
01-01-2012 10:10:01 4
02-01-2012 08:00:07 4
02-01-2012 10:00:07 4
I'd like to count the daily total, using 10:00 am as the cut-off point, so any orders before 10:00am appear in the total for the day before, and after 10:00 am in the total for that day.
I'm hoping to see query results like:
DateReceived Total
31-12-2011 4
01-01-2012 8
02-01-2012 4
I know how to group by just the date in Microsoft SQL Server:
SELECT DISTINCT CONVERT(varchar, [DateReceived], 111) AS [dt_DateReceived],
SUM([Total]) AS perday
FROM [Orders]
GROUP BY CONVERT(varchar, [DateReceived], 111)
ORDER BY [DateReceived] DESC
However I am unsure how to add a cut off time of 10:00am using Microsoft SQL Server.
Using MySQL, I can achieve this by grouping on a subtracted interval, however am unsure how to translate this to SQL Server:
GROUP BY
DATE(DATE_SUB( DateReceived , INTERVAL 10 HOUR))
Could anyone advise?
Thank you,
Jack

See the translation:
SELECT
CONVERT(varchar, DATEADD(hour, -10, [DateReceived]), 111) AS [dt_DateReceived],
SUM([Total]) AS perday
FROM [Orders]
GROUP BY CONVERT(varchar, DATEADD(hour, -10, [DateReceived]), 111)
ORDER BY 1 DESC
Test script
Note that my local datetime format is yyyy-mm-dd
;WITH Orders AS (
SELECT * FROM (VALUES
(CAST('2012-01-01 06:10:01' AS DATETIME), 2)
, ('2012-01-01 08:10:01', 2)
, ('2012-01-01 10:10:01', 4)
, ('2012-01-02 08:00:07', 4)
, ('2012-01-02 10:00:07', 4)
) AS Orders (DateReceived, Total)
)
SELECT CONVERT(varchar, DATEADD(hour, -10, [DateReceived]), 111) AS [dt_DateReceived]
, SUM([Total]) AS perday
FROM [Orders]
GROUP BY
CONVERT(varchar, DATEADD(hour, -10, [DateReceived]), 111)
ORDER BY
1
The testscript can be executed here
PS: Distinction is not needed

Related

SQL Server query get count of X on 15th of each month

I would like to create an SQL query that gets the count of employees that were employed as of the 15th of each month of the last 5 years.
This query gets me a single month:
SELECT
SUM(X.CountOfEmployees)
FROM (SELECT
COUNT(CNCEmployeeID) AS CountOfEmployees
FROM dbo.CNCEmployees
GROUP BY CNCEmployeeStartDate,
CNCEmployeeDateLeft
HAVING (CNCEmployeeStartDate < CONVERT(datetime, '2016-07-15 00:00:00', 102))
AND ((CNCEmployeeDateLeft > CONVERT(datetime, '2016-07-15 00:00:00', 102))
OR (CNCEmployeeDateLeft IS NULL))) AS X
What I am looking for would output:
Jan 2016 - 32
Feb 2016 - 33
Mar 2016 - 33
etc. for each month that we have data.
I know how to create a Query and at least make it quick to change the dates by hand by adding a variable and changing that over and over (in fact I will probably do that to get the report done today for the last 12 months). I believe that there is a better way to do this in one step without the need to manually go through each month.
One method generates for the 60 months and use that in the join:
with dates as (
select cast(dateadd(day, 16 - day(getdate()), getdate()) as date) as thedate, 1 as num
union all
select dateadd(month, -1, thedate), num + 1
from dates
where num <= 60
)
select d.thedate, count(e.CNCEmployeeStartDate)
from dates d left join
dbo.CNCEmployees e
on e.CNCEmployeeStartDate <= d.thedate and
(e.CNCEmployeeDateLeft >= d.thedate or e.CNCEmployeeDateLeft is null)
group by d.thedate;
This is not the most efficient method, but if you have a few hundred or thousand employees it should be fine in terms of performance.

i want to return only single record with sum of hours

The below query is working perfect but it return two rows of hours which I don't want
SELECT
USERINFO.name, USERINFO.BADGENUMBER,
departments.deptname, APPROVEDHRS.hours,
sum(workingdays) as workingdays,TotalWorkingDays
FROM
(SELECT DISTINCT
(DATEDIFF(DAY, '2014-06-01', '2014-06-30') + 1) -
DATEDIFF(WEEK, '2014-06-01', '2014-06-30') * 2 -
(CASE WHEN DATEPART(WEEKDAY, '2014-06-01') = 5 THEN 1 ELSE 0 END) -
(CASE WHEN DATEPART(WEEKDAY, '2014-06-30') = 6 THEN 1 ELSE 0 END) AS TotalWorkingDays,
COUNT(DISTINCT DATEADD(d, 0,DATEDIFF(d, 0, CHECKINOUT.CHECKTIME))) AS workingdays,
USERINFO.BADGENUMBER, USERINFO.NAME, hours
FROM
USERINFO
LEFT JOIN
CHECKINOUT ON USERINFO.USERID = CHECKINOUT.USERID
LEFT JOIN
departments ON departments.deptid = userinfo.DEFAULTDEPTID
left join APPROVEDHRS on APPROVEDHRS.userid = userinfo.userid AND
(APPROVEDHRS.DATE >='2014-06-01') AND (APPROVEDHRS.DATE <='2014-06-30')
WHERE
(DEPARTMENTS.DEPTNAME = 'xyz')
AND (CHECKINOUT.CHECKTIME >= '2014-06-01')
AND (CHECKINOUT.CHECKTIME <= '2014-06-30')
GROUP BY
hours, USERINFO.BADGENUMBER, deptname, USERINFO.NAME,
CONVERT(VARCHAR(10), CHECKINOUT.CHECKTIME, 103)) blue
GROUP BY
name, BADGENUMBER, workingdays, TotalWorkingDays, deptname, hours
The output of above query :
name BADGENUMBER deptname hours
---------------------------------------------------
abc 1111 xyz 00:07:59
abc 1111 xyz 00:08:00
pqr 2222 qwe NULL
Now the total hours (APPROVEDHRS table) in table is :
BADGENUMBER NAME DATE HOURS
-------------------------------------------------
1111 xyz 2014-06-15 00:07:59
1111 xyz 2014-06-14 00:08:00
1111 xyz 2014-07-14 00:10:00
I am fetching record from 2014-06-01 to 2014-06-30
So I want the below output:
name BADGENUMBER deptname hours
--------------------------------------------------------
abc 1111 xyz 00:15:59
pqr 2222 qwe NULL
Help me to get this desired output.
Thank you
Clearly, if you want to add your durations together, you should be storing them as something you can add together. Generally, this takes the form of a numeric type representing the smallest granularity you're interested in (apparently minutes, in this case). You can wrap it as an actual defined type, that standard operators work on (I'm sure somebody's defined an INTERVAL type for some version of SQL Server), but essentially it's simply backed by an INTEGER or something.
If you can't change the actual type in the db, then you need to convert it for this statement (and back for the display). That's perhaps easiest by declaring a pair of functions based on this:
CREATE FUNCTION dbo.Minutes_From_Duration_String (#Duration AS CHAR(8))
RETURNS INTEGER
BEGIN
RETURN (CAST(SUBSTRING(#Duration, 1, 2) AS INTEGER) * 24 * 60) +
(CAST(SUBSTRING(#Duration, 4, 2) AS INTEGER) * 60) +
(CAST(SUBSTRING(#Duration, 7, 2) AS INTEGER))
END;
CREATE FUNCTION dbo.Duration_String_From_Minutes (#Minutes AS INTEGER)
RETURNS CHAR(10)
BEGIN
RETURN RIGHT('00' + (#Minutes / 60 / 24), 2) + ':' +
RIGHT('00' + ((#Minutes / 60) % 24), 2) + ':' +
RIGHT('00' + (#Minutes % 60), 2)
END;
SQL Fiddle example
(note that these are extremely basic and will blow up at the slightest provocation. The rest is left as an exercise to the reader).
That taken care of, they can be used in your query, as usual.
Note that I think your query should be modified a bit. It's a bit difficult to tell without starting data, but I believe it can run faster, and be clearer.
First, always query positive contiguous-range types (like dates/times/timestamps) as lower-bound inclusive (>=), upper-bound exclusive (<), especially for the listed types on SQL Server. This means you never have to worry about dealing with fractions of things.
Next, if you don't have one already, you really want a Calendar Table. It is, in my opinion, the most useful Dimension table to have. You can put essentially as many indices as you want on it, which means you can use them (and range queries) to actually get index-based aggregates that you couldn't before (ie, by week, etc). It also makes getting non-working days (holidays) much easier, and is critical for one other thing here: the results of DATEPART(WEEKDAY, ....) are dependent on the culture/locale of the current session. That's probably not what you want.
If you can't create one now, you can generate a simple one easily with the use of a recursive CTE:
WITH Calendar_Range AS (SELECT CAST('20140601' AS DATE) AS Calendar_Date,
dbo.ISO_Day_Of_Week(CAST('20140601' AS DATE)) AS Day_Of_Week
UNION ALL
SELECT DATEADD(day, 1, Calendar_Date),
dbo.ISO_Day_Of_Week(DATEADD(day, 1, Calendar_Date))
FROM Calendar_Range
WHERE Calendar_Date < CAST('20140701' AS DATE))
SELECT Calendar_Date, Day_Of_Week
FROM Calendar_Range
SQL Fiddle demo
(This assumes you have some way to get the ISO Day-of-week - where Monday is 1. The demo includes a sample function that does this.)
We actually have three different aggregates, so we need to get them all separately:
First, the total hours approved:
SELECT userid, SUM(dbo.Minutes_From_Duration_String(hours)) AS totalHours
FROM ApprovedHrs
WHERE date >= CAST('20140601' AS DATE)
AND date < CAST('20140701' AS DATE)
GROUP BY userid
Then, total number of days worked.
SELECT CheckInOut.userid, COUNT(DISTINCT Calendar_Range.calendar_date) AS daysWorked
FROM Calendar_Range
JOIN CheckInOut
ON CheckInOut.checkTime >= Calendar_Range.calendar_date
AND CheckInOut.checkTime < DATEADD(day, 1, Calendar_Range.calendar_date)
WHERE Calendar_Range.calendar_date >= CAST('20140601' AS DATE)
AND Calendar_Range.calendar_date < CAST('20140701' AS DATE)
GROUP BY CheckInOut.userid
(I'm assuming Calendar_Range is a full-on Calendar table here, with all possible dates)
Lastly, number of days "available" to be worked:
SELECT COUNT(*) AS totalWorkingDays
FROM Calendar_Range
WHERE Day_Of_Week NOT IN (6, 7)
AND calendar_date >= CAST('20140601' AS DATE)
AND calendar_date < CAST('20140701' AS DATE)
(I'm assuming that there are other non-working days that shouldn't be counted here, like Christmas, but I didn't include a condition for it. Otherwise, you can do the calculation similar to what you did before, just be careful of day-of-week issues. The query I'm using here assumes ISO day-of-week values)
We now have all the pieces we need, so we can assemble the final query:
SELECT UserInfo.name, UserInfo.badgeNumber,
Departments.deptName,
dbo.Duration_String_From_Minutes(COALESCE(SummedHours.totalHours, 0)) AS totalHours,
COALESCE(DaysWorked.daysWorked, 0) AS daysWorked,
WorkingDays.totalWorkingDays
FROM UserInfo
JOIN Departments
ON Departments.deptId = UserInfo.defaultDeptId
AND Departments.deptName = 'xyz'
CROSS JOIN (SELECT COUNT(*) AS totalWorkingDays
FROM Calendar_Range
WHERE Day_Of_Week NOT IN (6, 7)
AND calendar_date >= CAST('20140601' AS DATE)
AND calendar_date < CAST('20140701' AS DATE)) WorkingDays
LEFT JOIN (SELECT userid, SUM(dbo.Minutes_From_Duration_String(hours)) AS totalHours
FROM ApprovedHrs
WHERE date >= CAST('20140601' AS DATE)
AND date < CAST('20140701' AS DATE)
GROUP BY userid) SummedHours
ON SummedHours.userId = UserInfo.userId
LEFT JOIN (SELECT CheckInOut.userid, COUNT(DISTINCT Calendar_Range.calendar_date) AS daysWorked
FROM Calendar_Range
JOIN CheckInOut
ON CheckInOut.checkTime >= Calendar_Range.calendar_date
AND CheckInOut.checkTime < DATEADD(day, 1, Calendar_Range.calendar_date)
WHERE Calendar_Range.calendar_date >= CAST('20140601' AS DATE)
AND Calendar_Range.calendar_date < CAST('20140701' AS DATE)
GROUP BY CheckInOut.userid) DaysWorked
ON DaysWorked.userId = UserInfo.userId
Try this.
WITH CTE (name,BADGENUMBER,deptname,hours)
AS
(
YOUR FULL QUERY
)
SELECT name,BADGENUMBER,deptname,SUM(hours)
FROM CTE
GROUP BY name,BADGENUMBER,deptname
you can sum hours by converting the varchar to time, then to seconds, sum them, back to varchar, back to time :) this should do the sum and the required conversions:
CONVERT(VARCHAR, dateadd(s,SUM(DATEDIFF(SECOND, 0, CAST(hours AS TIME))),0),114)

MsSQL / Classic ASP - Select statement from time a to time b

I have quite a complex query that i need to expand but am not sure how to go about it.
At the moment, im using the following query to output a few values.
SELECT CONVERT(CHAR(8), pay.PaidUntil, 10) as LastDay,
COUNT(*) As PaymentsDue,
SUM(sales.Amount) As TotalDue
FROM UserPaidUntil pay INNER JOIN Sales sales
ON pay.Sales_ID = sales.Sales_ID
WHERE pay.PaidUntil > getDate()
AND pay.PaidUntil < DateAdd(day, 10, getDate())
AND pay.Billing_ID = 2
GROUP BY CONVERT(CHAR(8), pay.PaidUntil, 10)
ORDER BY CONVERT(CHAR(8), pay.PaidUntil, 10)
At the moment, this script get values between 2 dates, 10 days apart and then groups them by their date.
Now what i need to do is change the sql so that each day is actually from 10:01am on day A through to 10:00am on day B
The UserPaidUntil.PaidUntil is the field that has the dates
Im not sure where to start so i thought i would ask if anyone could help me.
Cheers
I don't have a tester for this statement.. It will be something like this :
SELECT CONVERT(CHAR(8), DateAdd(hour, 10, pay.PaidUntil), 10) as LastDay,
COUNT(*) As PaymentsDue,
SUM(sales.Amount) As TotalDue
FROM UserPaidUntil pay INNER JOIN Sales sales
ON pay.Sales_ID = sales.Sales_ID
WHERE DateAdd(hour, 10, pay.PaidUntil) > getDate()
AND DateAdd(hour, 10, pay.PaidUntil) < DateAdd(day, 10, getDate())
AND pay.Billing_ID = 2
GROUP BY CONVERT(CHAR(8), DateAdd(hour, 10, pay.PaidUntil), 10)
ORDER BY CONVERT(CHAR(8), DateAdd(hour, 10, pay.PaidUntil), 10)

Sql express time Query

am working with MS SQL express and Ignition SCADA by http://www.inductiveautomation.com/
In the SCADA package you are able to create tags from SQL query's. I am trying to use SQL tags to calculate the average packages per minute in a 30min time frame. I was able to do this with two tags and an expression
SELECT MAX(L8Total)
FROM Slicing_tot
WHERE t_stamp BETWEEN DATEADD(minute, -30, GETDATE()) AND GETDATE()
 
SELECT MIN(L8Total)
FROM Slicing_tot
WHERE t_stamp BETWEEN DATEADD(minute, -30, GETDATE()) AND GETDATE()
What I would like to do from here is store the expressions value and find the max and average for the last 30 days based on time. But I have no idea how to filter 30days of information at a certain time
IE what was the max packages per minute we had at 10:30 from the last 30 days
IE what was the average packages per minute we had at 11:45 form the last 30 days
Please keep in mind that I am new to SQL
SELECT DATEPART(MINUTE, t_stamp)
,MAX(L8Total)
,MIN(L8Total)
FROM Slicing_tot
WHERE ( CONVERT(DATE, t_stamp) >= CONVERT(DATE, GETDATE() - 30)
AND CONVERT(DATE, t_stamp) <= CONVERT(DATE, GETDATE())
)
AND ( CONVERT(TIME, #variable) >= '22:30'
AND CONVERT(TIME, #variable) <= '23:00'
)
GROUP BY DATEPART(MINUTE, t_stamp)
GETDATE()-30 will get you datetime of today minus 30 days ago. Since you are working with datetime field it is best to convert it to date to make sure that you get correct date range. Use of >= and <= is better than between because you is always clear what you doing. Read #Aaron's blog
than for the second part just convert your datetime column to time to limit to specific range during the day.
The following would select between 10 & 11 AM over those 30 days
SELECT MIN(L8Total)
FROM Slicing_tot
WHERE t_stamp BETWEEN DATEADD(dd, -30, GETDATE()) AND GETDATE()
and Datepart(hh,t_stamp) between 10 and 11
or you could compare the time part of the t_stamp to time
SELECT MIN(L8Total)
FROM Slicing_tot
WHERE t_stamp BETWEEN DATEADD(dd, -30, GETDATE()) AND GETDATE()
and convert(time, t_stamp) between '10:30:00.000' and '10:31:00.000'
which would give you the results between 10:30 and 10:31 inclusive of the end points over the last 30 days.

How to group daily data on weekly basis using sql

I am trying to group the number of hours that employees worked for the last 4 weeks but I want to group them on a weekly basis. For example:
WEEK HOURS
Feb 24 to March 2 55
March 3 to March 9 40
March 10 to March 16 48
March 17 to March 23 37
This is what I have so far, please help. thanks
SET DATEFIRST 1
SELECT CAST(MIN( [DT]) AS VARCHAR(20))+' TO '+CAST (MAX([DT]) AS VARCHAR(20)) AS DATE,
SUM(HOURS) AS NUM_HRS
FROM MyTable
GROUP BY DATEPART(WEEK,[DT])
HAVING COUNT(DISTINCT[DT])=7
Create a Calendar auxilliary table, with Year, Month, Week, Date columns (you can also add holidays and other interesting stuff to it, it has many potential uses) and populate it for the period of interest.
After that, it's as easy as this:
SELECT sum(hours), cast(min(date) as varchar), cast(max(date) as varchar)
FROM Calendar c
LEFT OUTER JOIN MyTable h on h.Date = c.date
GROUP BY year, week
ORDER BY year, week
SET DATEFIRST 1
SELECT DATEPART(WEEK,DT) AS WEEK,
SUM(HOURS) AS NUM_HRS
FROM MyTable
WHERE DT >= DATEADD(WEEK, -4, GetDate()),
GROUP BY DATEPART(WEEK,[DT])
Try something like
SELECT
DATEADD(DD,
CONVERT(INT, (DATEDIFF(DD, '1/1/1900', t.DT)/7)) * 7,
'1/1/1900') [WeekBeginDate],
DATEADD(DD,
(CONVERT(INT, (DATEDIFF(DD, '1/1/1900', t.DT)/7)) * 7) + 6,
'1/1/1900') [WeekEndDate],
SUM(HOURS) AS NUM_HRS
FROM MyTable t
GROUP BY CONVERT(INT, DATEDIFF(DD, '1/1/1900', t.DT)/7)
Though this is the brute force trick, I think in your case it will work.
EDIT : Modified the query a little bit, the error was caused because of the order in which DATEDIFF calculates the difference.
Also here is a SQL FIDDLE with a working example.
EDIT 2 : Updated the Fiddle with the Date Format. To customize the date format, this article would help.