I need to return a shift-clock style IN/OUT board from a table of logs.
The expected output is:
ENROLLNO.
DAY
AM_IN
AM_OUT
PM_IN
PM_OUT
1
1
8:36
12:06
1:06
6:36
4
1
7:36
12:06
1:09
7:36
Where:
AM_IN is less than 11:29
AM_OUT is BETWEEN '11:30' AND '12:30'
PM_IN is BETWEEN '12:31' AND '14:00'
PM_OUT is greater than '14:01'
Overall the results should be sorted by ENROLLNO and DAY
So far I have tried this query but it doesn't return the values I need:
ALTER PROCEDURE [dbo].[spViewLOGCSF]
#month nvarchar (max),
#year nvarchar (max)
AS
SELECT
t1.EnrollNo, t1.Day,
MIN(CONVERT(varchar(15), HoursAndMinutes, 100)) AM_IN,
(SELECT MIN(CONVERT(varchar(15), HoursAndMinutes, 100))
FROM tblGLog
WHERE Month = 'April'
AND HoursAndMinutes BETWEEN '11:30' AND '12:30') AM_OUT,
(SELECT MIN(CONVERT(varchar(15), HoursAndMinutes, 100))
FROM tblGLog
WHERE Month = 'April'
AND HoursAndMinutes BETWEEN '12:31' AND '14:00') PM_IN,
MAX(CONVERT(varchar(15), HoursAndMinutes, 100)) PM_OUT
FROM
tblGLog AS t1
LEFT JOIN
tblEnroll AS t2 ON t1.EnrollNo = t2.EnrollNumber
WHERE
t1.Month = #month AND t1.Year = #year
GROUP BY
t1.EnrollNo, t1.Day
The source data table:
Table name: TBLLOGS
Columns are: ENROLLNO, HOURS, DAYS, MONTHS, YEARS , DATETIME
ENROLLNO.
HOURS
DAYS
MONTHS
YEARS
DATETIME
1
8:36
1
APRIL
2021
4/01/2021 8:36:48 AM
1
12:06
1
APRIL
2021
4/01/2021 12:06:48 PM
1
1:06
1
APRIL
2021
4/01/2021 1:06:48 PM
1
5:36
1
APRIL
2021
4/01/2021 5:36:48 PM
1
6:36
1
APRIL
2021
4/01/2021 6:36:48 PM
4
7:36
1
APRIL
2021
4/01/2021 7:36:48 AM
4
12:06
1
APRIL
2021
4/01/2021 12:06:48 PM
4
1:09
1
APRIL
2021
4/01/2021 1:09:48 PM
4
5:32
1
APRIL
2021
4/01/2021 5:36:48 PM
4
7:36
1
APRIL
2021
4/01/2021 7:36:48 PM
Your first issue is that your SP is targeting a different schema to the one you have posted, it is querying tables tblGLog and tblEnroll
For the purposes of this response we will disregard the information provided in the SP script and instead will answer in terms of the posted example data!
You should not use CHAR literals to store your time components, the only columns we need from your example dataset are ENROLLNO and DATETIME, the Day can easily be inferred.
Month and Year can help optimise indexes for some types of execution plans, however your SP is not filtering on Day so you will get more value by storing a DATE typed column for grouping and a TIME typed column for the filtering.
The following solution uses a CTE to transform the dataset, this is not 100% necessary but it illustrates the concrete columns that are required for this type of query. You could optimise your schema by including the additional columns from this CTE into your TBLLOGS schema and setting the values appropriately either as default values, or via triggers or in your INSERT commands.
CROSS APPLY is used to transform the query to apply the specifically requested column expressions, however these columns are not candidates for modifications to the original schema.
NOTE: The time boundary conditions have been closed so that logs at precisely 11:29 and 14:01 are included.
WITH TimePeriodData as
(
SELECT ENROLLNO, [DATETIME], DAYS, HOURS
, CAST([DATETIME] AS Date) AS [Date]
, CAST([DATETIME] AS TIME) AS [TIME]
FROM TBLLOGS
)
SELECT g.ENROLLNO, g.[DATE], g.[DAYS]
, MIN(x.AM_IN) AS AM_IN
, MAX(x.AM_OUT) AS AM_OUT
, MIN(x.PM_IN) AS PM_IN
, MAX(x.PM_OUT) AS PM_OUT
FROM TimePeriodData g
CROSS APPLY (SELECT CASE WHEN [TIME] < '11:30' THEN [HOURS] END AS AM_IN
, CASE WHEN [TIME] BETWEEN '11:30' AND '12:30' THEN [HOURS] END AS AM_OUT
, CASE WHEN [TIME] BETWEEN '12:31' AND '14:00' THEN [HOURS] END AS PM_IN
, CASE WHEN [TIME] > '14:00' THEN [HOURS] END AS PM_OUT
) as x
GROUP BY g.ENROLLNO, g.[Date], g.[DAYS];
See this fiddle: http://sqlfiddle.com/#!18/0073d1/1
Your requirement seems incredibly specific, it would be simpler logic to specify the midday break time as 12:30, then we only have a single conversion per-row. If this value is a constant then it is a candidate for inclusion in the data schema.
This next query deliberately DOES NOT format the output for the DAYS and HOURS columns as requested, instead the SQL query returns the exact data type so that you can easily extend this query to other situations. Formatting the specific output should be handled in the representation layer
WITH TimePeriodData as
(
SELECT ENROLLNO, [DATETIME], DAYS, HOURS
, CAST([DATETIME] AS Date) AS [Date]
, CAST([DATETIME] AS TIME) AS [TIME]
, CASE WHEN DATEPART(hour, [DATETIME]) >=14 THEN 1 ELSE 0 END AS [IS_PM]
FROM TBLLOGS
)
SELECT g.ENROLLNO, g.[DATE]
, MIN(x.AM) AS AM_IN
, MAX(x.AM) AS AM_OUT
, MIN(x.PM) AS PM_IN
, MAX(x.PM) AS PM_OUT
FROM TimePeriodData g
CROSS APPLY (SELECT CASE WHEN [IS_PM] = 0 THEN [TIME] END AS AM
, CASE WHEN [IS_PM] = 1 THEN [TIME] END AS PM
) as x
GROUP BY g.ENROLLNO, g.[Date], g.[DAYS];
This will return: http://sqlfiddle.com/#!18/0073d1/10
ENROLLNO
DATE
AM_IN
AM_OUT
PM_IN
PM_OUT
1
2021-04-01
08:36:48
12:06:48
13:06:48
18:36:48
4
2021-04-01
07:36:48
12:06:48
13:09:48
19:36:48
The following fiddle http://sqlfiddle.com/#!18/b09ee3/2 demonstrates how the query could be simplfied:
SELECT g.ENROLLNO, g.[DATE]
, MIN(x.AM) AS AM_IN
, MAX(x.AM) AS AM_OUT
, MIN(x.PM) AS PM_IN
, MAX(x.PM) AS PM_OUT
FROM TBLLOGS g
CROSS APPLY (SELECT CASE WHEN [IS_PM] = 0 THEN [TIME] END AS AM
, CASE WHEN [IS_PM] = 1 THEN [TIME] END AS PM
) as x
GROUP BY g.ENROLLNO, g.[Date];
There is no example here of how to apply this to the original SP because the exact table schema is not provided, no explanation for the un-used LEFT JOIN reference in that SP and the Hardcoded 'April' filtering ignored the input parameters... there is just too much mess there that I choose not to be involved with ;)
Related
I have 2 tables in a database, the first has the following kind of information in it
SECTION_NUMBER
SECTION_ID
MEETING_ID
DAY_TYPE
MEETING_NUMBER
DATE_TIME_BEGIN
DATE_TIME_END
390
166316
102451
1
1
2023-01-23 9:30:00
2023-05-17 10:50:00
390
166316
102451
3
1
2023-01-23 9:30:00
2023-05-17 10:50:00
655
166314
102452
3
1
2023-01-23 12:00:00
2023-05-20 12:00:00
655
166314
102452
7
1
2023-01-23 12:00:00
2023-05-20 12:00:00
283
166315
102453
7
1
2023-01-23 12:00:00
2023-05-20 12:00:00
Of note, a section will have 1 entry for each day the section meets, 1 being monday, 2 tuesday, etc.
In the example screenshot, section 390 meets monday and wednesday, and the first meeting day is 1/23/23 with the end being 5/17/23.
I have a second table with holidays in it
description
DATE_VALUE
DayOfWeek
Day
Winter Break
2023-01-02 0:00:00
1
M
MLK Day
2023-01-16 0:00:00
1
M
Lincoln's Day
2023-02-17 0:00:00
5
F
Non-Teaching Day
2023-02-18 0:00:00
6
S
Washington's Day
2023-02-20 0:00:00
1
M
I have a third table which, for a section number, shows its meeting days. However, it does not take into account holidays.
section_number
NbrOfDays
360
33
655
16
I tried solving the issue in python but then found out that SSRS only supports python scripts in the 2017 version and at my work we are using 2016.
What kind of SQL Server 2016 queries exist that would somehow allow me to iterate through the section rows, and check if a holiday falls between the start and end date, and falls on the day the section meets, and decrement the meeting days by 1 for each holiday which meets that criteria?
Using the data as an example, there are 2 Monday holidays between the section 390 begin and end date, so the NbrOfDays from the third table needs to be updated to 31 from 33 since it has a meeting day type of 1 = Monday.
I don't think the numbers you state quite add up based on your sample data. e.g. in your holiday table, there is only one date that falls between 2023-01-23 and 2023-05-17, not two as you stated.
I'm not sure how you get 16 as the total for section 655 either unless you are not counting weekends?
anyway... Don't worry about the length of this answer the actual answer bit is only a few lines lines of code.
Anyway, I think that you can just create a view that will work this out for you. There is no need for the third table, the view will replace the third table.
Apologies if I'm adding comments that assume you are not familiar with basic querying, I'm only assuming this from the approach you wanted to take.
Set up data to replicate your sample
I first created your sample data with the following using temp tables called #meets and #hols.
CREATE TABLE #meets(SECTION_NUMBER int, SECTION_ID int, MEETING_ID int, DAY_TYPE int, MEETING_NUMBER int, DATE_TIME_BEGIN DATETIME, DATE_TIME_END datetime)
INSERT INTO #meets VALUES
(390, 166316, 102451, 1, 1, '2023-01-23 9:30:00', '2023-05-17 10:50:00'),
(390, 166316, 102451, 3, 1, '2023-01-23 9:30:00', '2023-05-17 10:50:00'),
(655, 166314, 102452, 3, 1, '2023-01-23 12:00:00', '2023-05-20 12:00:00'),
(655, 166314, 102452, 7, 1, '2023-01-23 12:00:00', '2023-05-20 12:00:00'),
(283, 166315, 102453, 7, 1, '2023-01-23 12:00:00', '2023-05-20 12:00:00')
CREATE TABLE #hols([description] varchar(30), DATE_VALUE date, DayOfWeek int, Day char(1))
INSERT INTO #hols VALUES
('Winter Break' , '2023-01-02', 1, 'M'),
('MLK Day' , '2023-01-16', 1, 'M'),
('Lincoln''s Day' , '2023-02-17', 5, 'F'),
('Non-Teaching Day' , '2023-02-18', 6, 'S'),
('Washington''s Day' , '2023-02-20', 1, 'M')
Add a date/calendar table
Then I created a date table. You may already have one so use that if you do but if not, create one in your database as they are incredibly useful for things like this.
This post shows how to create one CreatingADateTable
I've included the code here in case the link is dead.
-- prevent set or regional settings from interfering with
-- interpretation of dates / literals
SET DATEFIRST 1 -- 1 = Monday, 7 = Sunday
DECLARE #StartDate date = '20100101'; -- << change this if required
DECLARE #CutoffDate date = DATEADD(DAY, -1, DATEADD(YEAR, 30, #StartDate));
;WITH seq(n) AS
(
SELECT 0 UNION ALL SELECT n + 1 FROM seq
WHERE n < DATEDIFF(DAY, #StartDate, #CutoffDate)
),
d(d) AS
(
SELECT DATEADD(DAY, n, #StartDate) FROM seq
),
src AS
(
SELECT
TheDate = CONVERT(date, d),
TheDay = DATEPART(DAY, d),
TheDayName = DATENAME(WEEKDAY, d),
TheWeek = DATEPART(WEEK, d),
TheISOWeek = DATEPART(ISO_WEEK, d),
TheDayOfWeek = DATEPART(WEEKDAY, d),
TheMonth = DATEPART(MONTH, d),
TheMonthName = DATENAME(MONTH, d),
TheQuarter = DATEPART(Quarter, d),
TheYear = DATEPART(YEAR, d),
TheFirstOfMonth = DATEFROMPARTS(YEAR(d), MONTH(d), 1),
TheLastOfYear = DATEFROMPARTS(YEAR(d), 12, 31),
TheDayOfYear = DATEPART(DAYOFYEAR, d)
FROM d
)
SELECT *
INTO myDateTable -- << CHANGE TABLE NAME HERE IF YOU NEED TO
FROM src
ORDER BY TheDate
OPTION (MAXRECURSION 0);
Now the answer!
The following will give you each section_number, the day of the week for the meeting and the number of days
SELECT
SECTION_NUMBER
, TheDayName
, NbrOfDays = COUNT(*)
FROM #meets m
JOIN myDateTable d on d.TheDate BETWEEN CAST(m.DATE_TIME_BEGIN as date) AND CAST(m.DATE_TIME_END as date) and m.DAY_TYPE = d.TheDayOfWeek
LEFT JOIN #hols h on d.TheDate = h.DATE_VALUE
WHERE h.DATE_VALUE IS NULL
and d.TheDate >=CAST(GETDATE() as Date) -- optionaly if you want ignore past meetings
GROUP BY SECTION_NUMBER, DayOfWeek, TheDayName
ORDER BY SECTION_NUMBER, DayOfWeek
All this does is join every date in the myDateTable table to the #meets table where the dates fall between the start and end dates in the #meets table, it also joins on the day_type so only matching days are returned. It then left joins to the #hols table and then we only include dates where no match was found in the #hols table. Then we simply group the results and count how many records are in each group.
gives us this
If you just want results to look like your example, we can just remove the DayOfWeek grouping like this.
SELECT
SECTION_NUMBER
, NbrOfDays = COUNT(*)
FROM #meets m
JOIN myDateTable d on d.TheDate BETWEEN CAST(m.DATE_TIME_BEGIN as date) AND CAST(m.DATE_TIME_END as date) and m.DAY_TYPE = d.TheDayOfWeek
LEFT JOIN #hols h on d.TheDate = h.DATE_VALUE
WHERE h.DATE_VALUE IS NULL
and d.TheDate >=CAST(GETDATE() as Date) -- optionaly if you want ignore past meetings
GROUP BY SECTION_NUMBER
ORDER BY SECTION_NUMBER
which gives us this...
I've left a line in there to filter out past meetings but you can comment that out if you don't need it.
If you want to turn these queries into permanent views then you can do that with something like
CREATE VIEW MeetingCountBySectionAndDay AS
[copy query from above here]
Then you can just query the view like a table with something like
SELECT * FROM MeetingCountBySectionAndDay
If holidays are added/removed or meetings are added/edited, the view will automatically reflect the changes without you needing to do any work.
I am trying to create a report that shows how many training records will expire within a chosen date range however when I run the report it excludes months that have no training records going out of date. I have tried various solutions I've seen posted but I haven't been able to get any of them to work in my case.
This is my query:
SELECT COUNT(ISNULL(TRAININGRECORDID, 0)) AS NUMBEROFRECORDS
,DEPARTMENTNUMBER
,DATENAME( Month, EXPIRY ) + '-' + DATENAME( Year, EXPIRY ) AS [MONTHYEAR]
FROM Training_Records TR
JOIN Departments TD ON TR.DEPARTMENTID = TD.DEPARTMENTID
WHERE TR.EXPIRY IS NOT NULL
AND TD.DEPARTMENTNUMBER IN (#DEPTNO)
AND TR.EXPIRY BETWEEN #StartDate AND #EndDate
GROUP BY TD.DEPARTMENTNUMBER, DATENAME(Year, TR.EXPIRY), DATENAME(Month, TR.EXPIRY)
ORDER BY TD.DEPARTMENTNUMBER, [MONTHYEAR]
An example of results from this query looks like this:
NUMBEROFRECORDS DEPARTMENTNUMBER MONTHYEAR
1 21 April-2023
4 23 June-2023
1 83 August-2023
I am displaying the results of this query in a matrix with MONTHYEAR as the columns. In the example above the report will display April, June and August 2023 but will skip over the months May, July 2023 because there are no records going out of date in those months but despite that I still want them displayed in my report/returned in my query.
I've tried various solutions I've found on here but none of them have worked for me. How would I go about including these months with no records going out of date?
You need to first get all of the months, and then outer join to them (not using BETWEEN). Here is an example that gets April, May, June, and July, and then shows how you would outer join that against your table.
DECLARE #StartDate date = '20220405',
#EndDate date = '20220708';
;WITH Months(TheMonth) AS
(
SELECT DATEFROMPARTS(YEAR(#StartDate), MONTH(#StartDate), 1)
UNION ALL
SELECT DATEADD(MONTH, 1, TheMonth)
FROM Months
WHERE TheMonth < DATEFROMPARTS(YEAR(#EndDate), MONTH(#EndDate), 1)
)
SELECT TheMonth -- , COALESCE(SUM({your table}.{column}),0)
FROM Months AS m
-- LEFT OUTER JOIN {your table}
-- ON {your table}.{date column} >= m.TheMonth
-- AND {your table}.{date column} < DATEADD(MONTH, 1, m.TheMonth);
Output:
TheMonth
2022-04-01
2022-05-01
2022-06-01
2022-07-01
Example db<>fiddle
If your range could last more than 100 months, you'll need to add:
OPTION (MAXRECURSION 0);
I have read so many Stack Overflow solutions but I cannot seem to apply them correctly to my query.
Here is my logic statement for reference;
count the # of days between dateStageChangedToPendingApproval to dateApprovalReceived (datediff)
when 'ApprovalRequiredFrom' = GRM renewal for all requests completed in previous month (February) excluding outliers
and then take the average (exclude holidays & weekends).
Match date to Calendar_Date and if Day_Name = 'Sunday' or 'Saturday' then exclude from the datediff count.
Here is my query that I have started but I am fairly new to SQL and this has been my most challenging task to date.
My query;
SELECT AVG(1.00 * DATEDIFF(DAY, xx, yy)) AS Avg_DayDiff
FROM Database1.dbo.table1
where
month(datecompleted) = month(dateadd(month,-1,current_timestamp))
and year(datecompleted) = year(dateadd(month,-1,current_timestamp))
and ApprovalRequiredFrom = 'GRM'
and dateStageChangedToPendingApproval < dateApprovalReceived
join(
select CALENDAR_DATE,
DAY_NAME,
YEAR(CALENDAR_DATE) AS cal_year,
MONTH(CALENDAR_DATE) AS cal_month
from Database1.dbo.table1
where month(CALENDAR_DATE) = month(dateadd(month,-1,current_timestamp))
and year(CALENDAR_DATE) = year(dateadd(month,-1,current_timestamp))
calendar table data sample;
Calendar_Date / Day_Name
2018-02-01 00:00:00:000 / Thursday
2018-02-02 00:00:00:000 / Friday
2018-03-02 00:00:00:000 / Saturday
2018-04-02 00:00:00:000 / Sunday
I have attached my answer as a basis to working with the Calendar Table / Date Dimension. As I said in my comment above, one of the main purposes of a calendar table is to pre-calculate data points (especially date-math-related) so you don't have to repeat them in your query.
NOTE: A Calendar Table / Date Dimension is a very handy thing to have in almost any database. I'd suggest creating a permanent one with commonly needed data points to use in your queries. There are numerous examples of creating one.
SQL Fiddle
MS SQL Server 2017 Schema Setup:
/********** TEST DATA for 2018 ***********/
CREATE TABLE t1 ( primaryKey int identity, whoDat varchar(10), approvalRequiredFrom varchar(10), datecompleted date ) ;
INSERT INTO t1 ( whoDat, approvalRequiredFrom, datecompleted )
SELECT 'Jay', 'GRM', datecompleted
FROM (
SELECT datecompleted = DATEADD(day, rn - 1, '20180101')
FROM
(
SELECT TOP (DATEDIFF(day, '20180101', '20190101'))
rn = ROW_NUMBER() OVER (ORDER BY s1.object_id)
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
ORDER BY s1.object_id
) AS x
) AS y;
/* Make data more "random" */
/* "Randomly" create holes in data -- "Delete every 3rd record" */
DELETE FROM t1 WHERE primaryKey%3=0 ;
/* "Randomly" change the approvalRequiredFrom */
UPDATE t1 SET approvalRequiredFrom = 'Bob' WHERE primaryKey%5=0 ;
/* "Randomly" create more holes in data */
DELETE FROM t1 WHERE approvalRequiredFrom = 'GRM' AND DAY(dateCompleted)%3 = 0 ;
Now build the Calendar Table. You'll want to come up with a way to calculate the holidays for your time frame.
/*******************CALENDAR*******************/
/* The calendar table is for general use in your database, so add whatever calculations you need. */
/* dim is just a holding table for intermediate calculations. */
CREATE TABLE #dim (
theDate date PRIMARY KEY
, theDay AS DATEPART(day, theDate) --int
, theWeek AS DATEPART(week, theDate) --int
, theMonth AS DATEPART(month, theDate) --int
, theYear AS DATEPART(year, theDate) --int
, yyyymmdd AS CONVERT(char(8), theDate, 112) /* yyyymmdd */
, mm_dd_yy AS CONVERT(char(10), theDate, 101) /* mm/dd/yyyy */
);
/****************************************************************************************************************/
/* Use the catalog views to generate as many rows as we need. */
INSERT INTO #dim ( theDate )
SELECT d
FROM (
SELECT d = DATEADD(day, rn - 1, '20180101')
FROM
(
SELECT TOP (DATEDIFF(day, '20180101', '20190101'))
rn = ROW_NUMBER() OVER (ORDER BY s1.object_id)
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
ORDER BY s1.object_id
) AS x
) AS y;
/* Now create the final ref table for the dates. */
CREATE TABLE refDateDimension
(
DateKey int NOT NULL PRIMARY KEY
, theDate date NOT NULL
, theDay tinyint NOT NULL
, theMonth tinyint NOT NULL
, theYear int NOT NULL
, isWeekend bit DEFAULT 0
, isHoliday bit DEFAULT 0
, isWorkDay bit DEFAULT 0
);
/* Insert data in the dimension table. */
INSERT INTO refDateDimension WITH (TABLOCKX)
SELECT
DateKey = CONVERT(int, yyyymmdd)
, theDate = theDate
, theDay = CONVERT(tinyint, theDay)
, theMonth = CONVERT(tinyint, theMonth)
, theYear = theYear
, isWeekend = CONVERT(bit, CASE WHEN DATEPART(dw,theDate) IN (1,7) THEN 1 ELSE 0 END)
, isHoliday = CONVERT(bit, CASE WHEN theDate IN ('2018-01-01','2018-07-01','2018-12-25') THEN 1 ELSE 0 END) /* New Years, Independence Day and Christmas. Calc others. */
, isWorkday = NULL
FROM #dim
OPTION (MAXDOP 1);
/* If not a weekend or holiday, it's a WorkDay. */
UPDATE refDateDimension
SET isWorkDay = CASE WHEN isWeekend = 0 AND isHoliday = 0 THEN 1 ELSE 0 END
;
/* CLEANUP */
DROP TABLE #dim ;
/****************************************************************************************************************/
Now that you've got your test data and a Calendar Table, you can link them together to get the measurements that you're looking for. I didn't know exactly what you were trying to calculate the average dates for, so I showed you the average number of of records with datecompleted during a given range.
Query 1:
/* This will give you an average number of days per month for the time period specified. */
SELECT s1.theYear, s1.theMonth, avg(recordCount) AS Avg_DayDiff
FROM (
SELECT dd.theYear, dd.theMonth, count(*) AS recordCount
FROM refDateDimension dd
LEFT OUTER JOIN t1 ON dd.theDate = t1.datecompleted
AND t1.ApprovalRequiredFrom = 'GRM'
WHERE t1.datecompleted >= '2018-03-01'
AND t1.datecompleted <= '2018-11-10'
GROUP BY dd.theYear, dd.theMonth
) s1
GROUP BY s1.theYear, s1.theMonth
ORDER BY s1.theYear, s1.theMonth
Averages By Month:
| theYear | theMonth | Avg_DayDiff |
|---------|----------|-------------|
| 2018 | 3 | 8 |
| 2018 | 4 | 16 |
| 2018 | 5 | 17 |
| 2018 | 6 | 8 |
| 2018 | 7 | 9 |
| 2018 | 8 | 8 |
| 2018 | 9 | 16 |
| 2018 | 10 | 17 |
| 2018 | 11 | 3 |
Before you try to calculate average days, I'd suggest you simply run a query to pull the number of days that you are looking to average. You might notice some days that were missed or something. For instance, just looking at my averages, I see that there were much fewer days in June, July and August. I don't know if that's because I eliminated them from my test data or if there's an issue with my query. Looking at the data will help figure that out.
Try something like this:
SELECT AVG(Cnt)
FROM (SELECT requestID, COUNT(*) Cnt
FROM table1 t
JOIN calendarTable ct ON t.dateStageChangedToPendingApproval >= ct.Calendar_Date
AND t.dateApprovalReceived < ct.Calendar_Date
WHERE MONTH(datecompleted) = MONTH(DATEADD(MONTH,-1,current_timestamp))
AND YEAR(datecompleted) = YEAR(DATEADD(MONTH,-1,current_timestamp))
AND ApprovalRequiredFrom = 'GRM'
AND ct.Day_Name NOT IN ('Saturday', 'Sunday')
GROUP BY requestID
) A
JOIN to your calendar table to get a record for each day, per request
Filter the records to not include weekends
COUNT() the records remaining to get the number of non-weekend days per request
Wrap the query in an AVG()
Notes: First, you mention also needing to rule out holidays. To do that, include a 'holiday' boolean or something similar in your calendar table that you can filter on. Second, I used >= for the start date, and < for the end date. That's because DATEDIFF() between two days = 1, and using the equals operators on both sides would essentially add an extra day. Last, I removed your outlier catch from the WHERE clause because the JOIN predicate now handles it.
I need query to list all the records for the date, incase if records are not present then query should list 0
SELECT Count(C.ConversionStatusID) Visits, CONVERT(VARCHAR(10),ActionDate,110) ActionDate
FROM Conversion C
WHERE C.ConversionStatusID = 2 AND
ActionDate Between DateAdd(day,-7,GetDate()) AND GETDATE()
GROUP BY CONVERT(VARCHAR(10),ActionDate,110)
Order BY CONVERT(VARCHAR(10),ActionDate,110) DESC
Expected output should always result 7 records, Sample result should be as below
ActionDate Visits
01-09-2015 1
01-08-2015 5
01-07-2015 0
01-06-2015 0
01-05-2015 3
01-04-2015 8
01-03-2015 0
Thanks in advance
you need to have date table for the last 7 days, you can then do left join
with cte (value, n)
as
(
select DATEADD(DAY, DATEDIFF(day,0,getdate()),0) as value,1 as n
UNION ALL
SELECT DATEADD(day, -1, value) as value, n+1
from cte
where n < 7
)
select CONVERT(VARCHAR(10),cte.value,110) as ActionDate , Count(C.ConversionStatusID) Visits
from cte
left join Conversion C
ON CONVERT(VARCHAR(10),C.ActionDate,110) = CONVERT(VARCHAR(10),cte.value,110)
and C.ConversionStatusID = 2
GROUP BY CONVERT(VARCHAR(10),cte.value,110)
Order BY CONVERT(VARCHAR(10),cte.value,110) DESC
Something like this will work, but you're going to have to play around with what should be a date and what should be a datetime. It's not at all clear where or when you want a time component. In particular, your WHERE clause seems to contradict the rest of your query since it doesn't strip the time. For example, if GETDATE() is January 12 at 2 PM, is January 11 at 1 PM one day ago or two? What about January 5 at 1 PM, then, because the WHERE clause is stripping that off.
If a date is 00:00 to 23:59 (although the WHERE clause is still off):
;WITH Dates ([Date]) AS (
SELECT CAST(DATEADD(DAY,-1,GETDATE()) AS DATE) UNION
SELECT CAST(DATEADD(DAY,-2,GETDATE()) AS DATE) UNION
SELECT CAST(DATEADD(DAY,-3,GETDATE()) AS DATE) UNION
SELECT CAST(DATEADD(DAY,-4,GETDATE()) AS DATE) UNION
SELECT CAST(DATEADD(DAY,-5,GETDATE()) AS DATE) UNION
SELECT CAST(DATEADD(DAY,-6,GETDATE()) AS DATE) UNION
SELECT CAST(DATEADD(DAY,-7,GETDATE()) AS DATE))
SELECT D.Date,
COALESCE(Count(C.ConversionStatusID),0) Visits
FROM Dates D
LEFT JOIN Conversion C
ON D.[Date] = CAST(C.ActionDate AS Date)
WHERE C.ConversionStatusID = 2 AND
ActionDate Between DateAdd(day,-7,GetDate()) AND GETDATE()
GROUP BY CONVERT(VARCHAR(10),ActionDate,110)
Order BY CONVERT(VARCHAR(10),ActionDate,110) DESC
You could get away with not using a CTE if you had a Numbers or tally table, but for something this small it really won't matter that much.
I have a program that has the user select a date range then it querys a SQL server database to retrieve the dates they worked with the hours. I want to have the dates in their own columns instead of all in the same one. For example:
Employee Total Hours Monday 3/10 Tuesday 3/11 ....
Doe, John 40 8 8
Here's the query right now:
SELECT e.emp_fullname, t.tc_hours, t.punchDate
FROM WaspTime.dbo.vEmployeeInfo as e, WaspTime.dbo.vTimeCards as t
WHERE e.emp_id = t.tc_emp
AND t.punchDate >= '03/10/2013'
AND t.punchDate <= '03/20/2013'
And this is the result:
Smith, Dan 8 2013-03-10 00:00:00.000
Smith, Dan 8 2013-03-11 00:00:00.000
Something like this, repeated for however many periods you have.
I'm assuming you'd:
need to SUM (or COUNT?) your hours?
use some kind of #period_start_date parameter that each of the periods are then offset by a day, 2 days, 3 days, etc...
Done by hand in text editor so please forgive typos, or if the thing just plain doesn't work - but I think the idea is clear/sound.
CREATE PROCEDURE GetHoursWorkedInPeriod #periodStartDate datetime = getdate() AS
SELECT
e.emp_fullname,
sum(t1.tc_hours) as 'Day1Hours',
min(t1.punchDate) as 'Day1Date'.
sum(t2.tc_hours) as 'Day2Hours',
min(t2.punchDate) as 'Day2Date',
sum(t3.tc_hours) as 'Day3Hours',
min(t3.punchDate) as 'Day3Date',
...and so on
FROM
WaspTime.dbo.vEmployeeInfo as e
inner join WaspTime.dbo.vTimeCards as t1
on e.emp_id = t1.tc_emp
AND t1.punchDate >= DATEADD (day , 0 , #periodStartDate )
AND t1.punchDate < DATEADD (day , 1 , #periodStartDate )
inner join WaspTime.dbo.vTimeCards as t2
on e.emp_id = t2.tc_emp
AND t2.punchDate >= DATEADD (day , 1 , #periodStartDate )
AND t2.punchDate < DATEADD (day , 2 , #periodStartDate )
inner join WaspTime.dbo.vTimeCards as t3
on e.emp_id = t3.tc_emp
AND t3.punchDate >= DATEADD (day , 2 , #periodStartDate )
AND t3.punchDate < DATEADD (day , 3 , #periodStartDate )
...and so on.
GROUP BY
e.emp_fullname
Edited to limit each period to a day as requested by OP, and also explicitly include the date to which the hours apply in the result set.