Related
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)
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.
I want to return results where if the date falls on 10 & 28 of each month, but if either is a weekend return the result for Friday (first working day before).
Eg. if the following lines to be returned are
10 Oct 2010 Sunday
28 Oct 2010 Thursday
In the table I have
LineId Date
1 08 Oct 2010
2 28 Oct 2010
so, because 10 October is a Sunday, therefore won't be in the table, it will return LineID 1 as the is first working day before.
Thank you.
DATEPART(WEEKDAY and DATEPART(DW are dependant on the DATEFIRST setting. To avoid incorrect results the ##DATEFIRST function can be used.
WITH T(D) AS
(
SELECT CAST('20111008' AS DATE) UNION ALL
SELECT CAST('20111009' AS DATE) UNION ALL
SELECT CAST('20111010' AS DATE) UNION ALL
SELECT CAST('20111011' AS DATE) UNION ALL
SELECT CAST('20111012' AS DATE) UNION ALL
SELECT CAST('20111013' AS DATE) UNION ALL
SELECT CAST('20111014' AS DATE)
)
SELECT CASE
WHEN ( ##DATEFIRST + DATEPART(WEEKDAY, D) ) % 7 > 1 THEN D
ELSE DATEADD(DAY, -( 1 + ( ##DATEFIRST + DATEPART(WEEKDAY, D) ) % 7 ), D)
END AS WeekDayOrPrecedingFriday
FROM T
Select Case
When DatePart(dw,SampleData.[Date]) = 1 Then DateAdd(d,-2,SampleData.[Date])
When DatePart(dw,SampleData.[Date]) = 7 Then DateAdd(d,-1,SampleData.[Date])
Else SampleData.[Date]
End
From (
Select Cast('2010-10-10' As datetime) As [Date]
Union All Select '2010-10-28' As [Date]
) As SampleData
You may find that it is easier to have a Calendar table with one row for all days you need where you indicate whether the given day is a "working" day. In this way, you can easily account for holidays and the actual day off for holidays (e.g. if July 4th, in the US, is on a Saturday, mark the preceding Friday as a day off.).
If you are really worried about dealing with DateFirst, just set it prior to running your query:
Set DateFirst 7;
The above is the US default setting which is that Sunday is the first day of the week.
I really like to use a calendar table for queries like these.
-- For convenience, I'll use a view. The view "weekdays" is a proper
-- subset of the table "calendar".
create view weekdays as
select * from calendar
where day_of_week in ('Mon', 'Tue', 'Wed', 'Thu', 'Fri');
Having done that, the query is not only dead simple, it can easily be seen to be right.
select max(cal_date)
from weekdays
where cal_date <= '2010-10-10' -- Returns 2011-10-08
Doesn't account for holidays that might fall on the 10th or 28th, but that's easy enough to remedy.
I am looking for the number of Mon,Tues, Wed, Thur, Fri, Sat, Sun in the past 30 days. Can I select the last 30 days date and day of week without an actual database table? Something like
SELECT --everything between
convert(date,GETDATE()), DATENAME(DW, GETDATE())
--and
convert(date,GETDATE() - 30), DATENAME(DW, GETDATE())
You can use a recursive CTE:
;WITH CTE AS
(
SELECT convert(date,GETDATE()) sDate, DATENAME(DW, GETDATE()) sDayofWeek
UNION ALL
SELECT DATEADD(DAY,-1,sDate), DATENAME(DW, DATEADD(DAY,-1,sDate))
FROM CTE
WHERE sDate > GETDATE()-29
)
SELECT * FROM CTE
WITH cteCount AS (
SELECT DATENAME(dw, GETDATE()) dw, 1 ix
UNION ALL
SELECT DATENAME(dw, DATEADD(d, -ix, GETDATE())), ix+1 FROM cteCount WHERE ix<30
)
SELECT dw, COUNT(1) cnt FROM cteCount GROUP BY dw
A couple solutions:
SELECT ... From ... WHERE date > DATEADD(year, -1, GETDATE())
Also, I think this statement will work with MySQL:
select date_sub(now(),interval 30 day)as Datebefore30days;
Well, there are a couple of ways to do it.
You could fill a temp table, using a loop and INSERT statements, and then select the contents of the table. You could create a table-valued UDF to do this, in fact.
You could also create 30 SELECT statements, and UNION them all together. But, frankly, I think you're better off with option 1.
ETA: Thinking about it, if all you want is the number of each day of the week in the past 30 days, you can probably do that just with some math, without returning 30 records.
There are 4 instances of each day of the week in any 30 day period, plus 2 extra days. So all you really need is to know what day of the week the first day in your period is, and the second day. Those days of the week have 5 instances.
I'm pretty lazy and just load a temp table and then do a group by select on that temp table
DECLARE #tmpDates TABLE (calDate DATETIME)
DECLARE #beginDate DATETIME
SET #beginDate = DATEADD(day,-30,GETDATE())
WHILE #beginDate < GETDATE()
BEGIN
INSERT INTO #tmpDates ([calDate]) VALUES (#beginDate)
SET #beginDate = DATEADD(DAY,1,#beginDate)
END
SELECT DATEPART(dw,[calDate]) AS [weekDay], COUNT(1) AS [dayCount]
FROM #tmpDates
GROUP BY DATEPART(dw,[calDate])
Number of times each day of the week got hit in the last 30 days:
SELECT DATENAME(dw,GETDATE())+' 5 times' as results
UNION ALL
SELECT DATENAME(dw,DATEADD(day,-1,GETDATE()))+' 5 times'
UNION ALL
SELECT DATENAME(dw,DATEADD(day,-2,GETDATE()))+' 4 times'
UNION ALL
SELECT DATENAME(dw,DATEADD(day,-3,GETDATE()))+' 4 times'
UNION ALL
SELECT DATENAME(dw,DATEADD(day,-4,GETDATE()))+' 4 times'
UNION ALL
SELECT DATENAME(dw,DATEADD(day,-5,GETDATE()))+' 4 times'
UNION ALL
SELECT DATENAME(dw,DATEADD(day,-6,GETDATE()))+' 4 times'
This really is about dividing 30 by 7
This gives me
results
Thursday 5 times
Wednesday 5 times
Tuesday 4 times
Monday 4 times
Sunday 4 times
Saturday 4 times
Friday 4 times
I have a table named dbstorage which has columns like year and weekno.
I need to select the year and weekno which is less than current year and current week.
I used:
select * from dbstorage where year<=2011 and weekno<=13.
But it is not giving the correct data like if I have a value
year:2010 and weekno:25 means, the query is not selecting this record.
How can this be done?
You can split it out into two cases and use or
SELECT * /*<-- But don't use * List the columns explicitly*/
FROM dbstorage
WHERE ( year = 2011
AND weekno <= 13 )
OR year < 2011
Edit
But And logic may well be more efficient
SELECT *
FROM dbstorage
WHERE ( year <= 2011 )
AND ( year < 2011
OR weekno <= 13 )
you have to split up the search into multiple parts:
select * from dbstorage
where
(year = 2011 and weekno <= 13) -- will catch all weeks in 2011 PRIOR to this one
or (year < 2011) -- will catch all weeks in 2010 and earlier
For SQL Server you can also do:
SELECT *
FROM dbstorage
WHERE (year = DATEPART(yyyy, GETDATE())
AND weekno <= DATEPART(ww, GETDATE()))
OR year < DATEPART(yyyy, GETDATE())