MSSQL select count where condition is met across a date range - sql

I have a table containing date, employeeID(int) , and ShiftWorked (can be night/day/weekend or evening) . There is a row for each employee and date combination
I would like to construct a query that gives me a count of how many people have worked a night shift in the week before and after each date in the roster period.
--------------------------------------------------------------------------
Date (yyyy-MM-dd) | CountOfNightshifts(for 1 week either side of date)
--------------------------------------------------------------------------
2012-1-1 | 8
2012-1-2 | 12
2012-1-3 | 11
2012-1-4 | 6
etc | etc
I hope this is clear. I have spent days trying to get this to work but I am not getting anywhere.
For example:
SELECT COUNT(id), [date]
FROM ROSTER
WHERE Shift = night AND [date] BETWEEN DATEADD(D,-7,[date]) AND DATEADD(d,7,[date])
GROUP by [date]
group by [date]
This will give me a list of dates and a count of nights on that particular day - not all night shifts in the 7 days before and after the date.

The following query will return two columns: the reference (roster) date and the number of (distinct) people that have worked on the night sift seven days before to seven days after the reference date.
SELECT tmain.date,
(
SELECT COUNT(DISTINCT taux.employeeId)
FROM roster taux
WHERE taux.shiftWorked = 'night'
AND taux.date >= DATEADD(DAY, -7, tmain.date)
AND taux.date <= DATEADD(DAY, 7, tmain.date)
) AS [number_of_distinct_people_with_night_shift]
FROM roster tmain
ORDER BY tmain.date;
Note 1: Usually I prefer joins over sub-queries, but I guess this solution is easier to read.
Note 2: I am assuming the time component of date values are irrelevant and all dates have the same time (i.e. '00:00:00.00'); if it is not the case, there are more adjustments to be done on the date comparison.

how about this?
SELECT
[date]
,count(*)
FROM
Shifts as s
WHERE
s.Date > DATEADD(day,-7,GETDATE())
AND ShiftWorked = 'Night'
GROUP BY
date
http://sqlfiddle.com/#!3/e88cc/1
a bit more data:
http://sqlfiddle.com/#!3/b7793/2
If you are only interested in a specific date then you could use:
DECLARE #target datetime
SET #target = GETDATE()
SELECT
count(*) as NightShifts
FROM
Shifts as s
WHERE
ShiftWorked = 'Night'
AND s.Date > DATEADD(day,-7,#target)
AND s.Date < DATEADD(day,7,#target)
http://sqlfiddle.com/#!3/b7793/20
but if you have another table that actually has the periods in it (e.g. billing or payroll dates):
DECLARE #target datetime
SET #target = GETDATE()
SELECT
p.periodDate
,count(*)
FROM
Shifts as s
INNER JOIN periods as p
ON s.date > dateadd(day,-7,p.periodDate)
AND s.date < dateadd(day,7,p.periodDate)
WHERE
ShiftWorked = 'Night'
GROUP BY p.periodDate
http://sqlfiddle.com/#!3/fc54d/2
OR to get ) when no night shift was worked:
SELECT
p.periodDate
,ISNULL(t.num,0) as nightShifts
FROM
periods as p
LEFT OUTER JOIN (
SELECT
p.periodDate
,count(*) as num
FROM
Shifts as s
INNER JOIN periods as P
ON s.date > dateadd(day,-7,p.periodDate)
AND s.date < dateadd(day,7,p.periodDate)
WHERE
ShiftWorked = 'Night'
GROUP BY p.periodDate
) as t
ON p.periodDate = t.periodDate
http://sqlfiddle.com/#!3/fc54d/11

You can pull it off by joining the ROSTER table to itself, thereby creating several result rows per employee and day. Otherwise your GROUP BY clause will group the resulting rows from the period you are after into the dates of the original table.
SELECT
r.[date],
COUNT(period.id)
FROM ROSTER r
JOIN ROSTER period
ON period.employeeID=r.employeeID
AND period.shift = night
AND r.[date] BETWEEN DATEADD(d,-7,period.[date]) and DATEADD(d,7,period.[date])
WHERE
r.shift = night
GROUP BY r.[date]

Related

SQL creating a pivot function

I have a SQL code that looks like this:
select cast(avg(age) as decimal(16,2)) as 'avg' From
(select distinct acct.Account, cast(Avg(year(getdate())- year(client_birth_date)) as decimal(16,2)) as 'Age'
from WF_PM_ACCT_DB DET
inner join WF_PM_ACCT_DET_DB ACCT
ON det.Account = acct.Account
where (acct_closing_date is null or acct_closing_date > '2017-01-01')
and Acct_Open_Date < '2017-01-01'
group by acct.Account
) x
Then basically what this give me is a simple one cell answer of the average age of accounts in the year Acct_Open_Date < '2017-01-01' . I am an ameture so i change the date everytime and run the query again and again to get the remaining year. Is there an easy way to say lets have all the years as column headings and just one row with the average account age in that year.
Please note that the account closing date being null means accounts never got close and i have to change it to less than the analysis year in order to get a true picture of the average account age that existed at that time
Any help is appreciated. Thanks.
You can run this for multiple dates by including them in a single derived table:
with dates as (
select cast('2017-01-01' as date) as yyyy union all
select cast('2016-01-01' as date)
)
select yyyy, cast(avg(age) as decimal(16,2)) as avg_age
From (select dates.yyyy, acct.Account,
cast(Avg(year(getdate())- year(client_birth_date)) as decimal(16,2)) as Age
from dates cross join
WF_PM_ACCT_DB DET inner join WF_PM_ACCT_DET_DB
ACCT
on det.Account = acct.Account
where (acct_closing_date is null or acct_closing_date > dates.yyyy) and
Acct_Open_Date < dates.yyyy
group by acct.Account, dates.yyyy
) x
group by yyyy
order by yyyy;

Query to check number of records created in a month.

My table creates a new record with timestamp daily when an integration is successful. I am trying to create a query that would check (preferably automated) the number of days in a month vs number of records in the table within a time frame.
For example, January has 31 days, so i would like to know how many days in january my process was not successful. If the number of records is less than 31, than i know the job failed 31 - x times.
I tried the following but was not getting very far:
SELECT COUNT (DISTINCT CompleteDate)
FROM table
WHERE CompleteDate BETWEEN '01/01/2015' AND '01/31/2015'
Every 7 days the system executes the job twice, so i get two records on the same day, but i am trying to determine the number of days that nothing happened (failures), so i assume some truncation of the date field is needed?!
One way to do this is to use a calendar/date table as the main source of dates in the range and left join with that and count the number of null values.
In absence of a proper date table you can generate a range of dates using a number sequence like the one found in the master..spt_values table:
select count(*) failed
from (
select dateadd(day, number, '2015-01-01') date
from master..spt_values where type='P' and number < 365
) a
left join your_table b on a.date = b.CompleteDate
where b.CompleteDate is null
and a.date BETWEEN '01/01/2015' AND '01/31/2015'
Sample SQL Fiddle (with count grouped by month)
Assuming you have an Integers table*. This query will pull all dates where no record is found in the target table:
declare #StartDate datetime = '01/01/2013',
#EndDate datetime = '12/31/2013'
;with d as (
select *, date = dateadd(d, i - 1 , #StartDate)
from dbo.Integers
where i <= datediff(d, #StartDate, #EndDate) + 1
)
select d.date
from d
where not exists (
select 1 from <target> t
where DATEADD(dd, DATEDIFF(dd, 0, t.<timestamp>), 0) = DATEADD(dd, DATEDIFF(dd, 0, d.date), 0)
)
Between is not safe here
SELECT 31 - count(distinct(convert(date, CompleteDate)))
FROM table
WHERE CompleteDate >= '01/01/2015' AND CompleteDate < '02/01/2015'
You can use the following query:
SELECT DATEDIFF(day, t.d, dateadd(month, 1, t.d)) - COUNT(DISTINCT CompleteDate)
FROM mytable
CROSS APPLY (SELECT CAST(YEAR(CompleteDate) AS VARCHAR(4)) +
RIGHT('0' + CAST(MONTH(CompleteDate) AS VARCHAR(2)), 2) +
'01') t(d)
GROUP BY t.d
SQL Fiddle Demo
Explanation:
The value CROSS APPLY-ied, i.e. t.d, is the ANSI string of the first day of the month of CompleteDate, e.g. '20150101' for 12/01/2015, or 18/01/2015.
DATEDIFF uses the above mentioned value, i.e. t.d, in order to calculate the number of days of the month that CompleteDate belongs to.
GROUP BY essentially groups by (Year, Month), hence COUNT(DISTINCT CompleteDate) returns the number of distinct records per month.
The values returned by the query are the differences of [2] - 1, i.e. the number of failures per month, for each (Year, Month) of your initial data.
If you want to query a specific Year, Month then just simply add a WHERE clause to the above:
WHERE YEAR(CompleteDate) = 2015 AND MONTH(CompleteDate) = 1

SQL query count by week with criteria

I have to write a trend report for the amount of standing scaffolds in the database by week.
I can get a count of scaffolds erected by week in the example below and also dismantles using the same query but this isn't what I need.
SELECT COUNT(scaffID) Erected, WeekStart
FROM
(
SELECT ScaffID,
dateadd(week, datediff(day,0,Erected) / 7, 0) AS WeekStart
FROM Scaffolds
) o
GROUP BY WeekStart
I can get my standing scaffolds from this by putting in a date but I want the standing scaffolds on every Friday say.
Declare #staticDate As DateTime
Set #staticDate = '2/1/2015'
Select COUNT(scaffID) As StandingScaffolds
from RequestInfo
Where ( ErectDate<= #staticDate )
And ( DismantleDate>= #staticDate
or DismantleDate Is NULL
)
This is driving me crazy so any help would be extremely appreciated.
Phil
this should give you something to start with..
DECLARE
#startDate date = '2015-01-01',
#endDate date = '2015-01-28';
with myWeeks (myWeek) AS (
select DATEPART(WEEK,#startDate) myWeek
UNION ALL
select myWeek + 1 from myWeeks
where
myWeek < DATEPART(WEEK,#endDate)
)
select
w,
COUNT(s.Erected) standingScaffolds
from myWeeks w
left join Scaffolds s on
w.myWeek between DATEPART(WEEK,s.Erected) and DATEPART(WEEK,s.Dismantled)
You might want to generate a date table for this (=table with one row for each day). You can then join that with this table to get the calculations quite easily.
select d.date, count(s.scaffID)
from date d, scaffolds s
where s.erected <= d.date and
(dismantled>= d.date or dismantled is NULL) and
d.date >= #stardate and
d.date <= enddate
group by d.date
Hopefully this is ok, can't test right now.
The date table is quite useful in other cases too, for example you can have local holidays there.

Data appear at least once for every month in the last X month

My problem:
Table: trans_detail:
PhoneNo | Datetime
01234 | 2013-01-05 20:40:10
01245 | 2013-04-02 21:00:13
05678 | 2013-04-16 01:24:07
04567 | 2013-07-23 07:00:00
etc | etc
I want to get all phoneNo that appears at least once for every month in the last X month (X month can be any month between 1-12).
For example: get all phone no. that appears at least once for Every Month in the last 3 months.
I am using SQL Server 2005.
Here is a quick query that comes close to what you want:
select PhoneNo
from trans_detail d
where d.datetime >= dateadd(mm, -#X, getdate())
group by PhoneNo
having count(distinct year(datetime)*12+month(datetime)) = #X
The where clause filters the data to only include rows in the last #X months. the having clause checks that each month is in the data, by counting the number of distinct months.
The above version of the query assumes that you mean calendar months. So, it has boundary condition problems. If you run it on June 16th, then it looks back one month and makes sure that the phone number appears at least once since May 16th. I am unclear on whether you want to insist that the number appear twice (once in May and once in June) or if once (once during the time period). The solution to this is to move the current date back to the end of the previous month:
select PhoneNo
from trans_detail d cross join
(select cast(getdate() - day(getdate) + 1 as date) as FirstOfMonth const
where d.datetime >= dateadd(mm, -#X, FirstOfMonth) and
d.datetime < FirstOfMonth
group by PhoneNo
having count(distinct year(datetime)*12+month(datetime)) = #X
Here it is. First two CTEs are to find and prepare last X months, third CTE is to group your data by phones and months. At the end just join the two and return where number of matching rows are equal to number of months.
DECLARE #months INT
SET #Months = 3
;WITH CTE_Dates AS
(
SELECT GETDATE() AS Dt
UNION ALL
SELECT DATEADD(MM,-1,Dt) FROM CTE_Dates
WHERE DATEDIFF(MM, Dt,GETDATE()) < #months-1
)
, CTE_Months AS
(
SELECT MONTH(Dt) AS Mn, YEAR(Dt) AS Yr FROM CTE_Dates
)
, CTE_Trans AS
(
SELECT PhoneNo, MONTH([Datetime]) AS Mn, YEAR([Datetime]) AS Yr FROM dbo.trans_detail
GROUP BY PhoneNo, MONTH([Datetime]), YEAR([Datetime])
)
SELECT PhoneNo FROM CTE_Months m
LEFT JOIN CTE_Trans t ON m.Mn = t.Mn AND m.Yr = t.Yr
GROUP BY PhoneNo
HAVING COUNT(*) = #months
SQLFiddle Demo - with added some more data that will match for last 3 months

T-SQL recursive in order to find transactions for a period of time

I have tried but i am not able to figure this out. I have a table transactions (transaction_ID, transaction_Person_ID, Transaction_Date etc).
What i want is to return all the transaction_person_ID's that have more than 3 transactions per week for the last year. That means i have to check for 1-1-10 to 7-1-10 to see if someone had more than 3 transactions for that 7 day period, then for 2-1-10 to 8-1-10 then 3-1-10 to 9-1-10 etc etc.
I now i need to use a recursive select but i what i have writen does not produce the correct time frame.
What i have written so far is this
WITH Dates AS (
SELECT
[Date] = CONVERT(DATETIME,'01/01/2010')
UNION ALL SELECT
[Date] = DATEADD(DAY, 1, [Date])
FROM
Dates
WHERE
Date < '12/31/2010'
)
SELECT transaction_person_Id FROM transactions
JOIN DATES
ON transactions.transaction_date = dates.date
where transactions.Transaction_Date between dateadd(DAYOFYEAR,-7,dates.date) and dates.date
group by transaction_person_Id
having count(transaction_person_ID) >= 4
OPTION (MAXRECURSION 2000)
Thanks a lot
PS:
in simple words what i need to do is this
select transaction_person_ID from transactions
where Transaction_Date between '2010-01-01' and '2010-01-07'
group by transaction_person_Id
having count(transaction_person_ID) >= 4
then
select transaction_person_ID from transactions
where Transaction_Date between '2010-01-02' and '2010-01-08'
group by transaction_person_Id
having count(transaction_person_ID) >= 4
.
.
.
.
.
until it goes
select transaction_person_ID from transactions
where Transaction_Date between '2010-12-25' and '2010-12-31'
group by transaction_person_Id
having count(transaction_person_ID) >= 4
i need to have the results of these 365 queries
This will give one result set with person and weeks, rather then 360 results sets
WITH Weeks
AS (
SELECT
CONVERT(DATETIME,'01 Jan 2010') AS WeekStartMidnight,
CONVERT(DATETIME,'08 Jan 2010') AS WeekEndMidnight
UNION ALL
SELECT
DATEADD(day, 1, WeekStartMidnight),
DATEADD(day, 1, WeekEndMidnight)
FROM
Weeks
WHERE
WeekEndMidnight < '31 Dec 2010'
)
SELECT
t.transaction_person_Id,
w.WeekStartMidnight, w.WeekEndMidnight
FROM
weeks w
JOIN
transactions t ON t.Transaction_Date >= w.WeekStartMidnight AND t.Transaction_Date < w.WeekEndMidnight
GROUP BY
t.transaction_person_Id
HAVING
count(*) >= 4 --COUNT(t.transaction_person_Id) = same
OPTION
(MAXRECURSION 365)
If you want 360 results sets, it's a loop using WHILE or a CURSOR per row in the "weeks" derived table