How to calculate StartDate and EndDate based on ReportingMonth value - sql

I Need to calculate StartDate and EndDate based on ReportingMonth value
sample data:
CREATE TABLE TestDate (
[ID] [int] NULL,
[PerID] [int] NULL,
[CommID] [int] NULL,
[ReportingMonth] [nvarchar](200) NULL,
[Number] [nvarchar](200) NULL
)
Insert into TestDate ( [ID],[PerID],[CommID],[ReportingMonth],[Number])
Values ('3820','12508','7','Jul-16','31');
Insert into TestDate ( [ID],[PerID],[CommID],[ReportingMonth],[Number])
Values ('10235','12498','58','Aug-16','1');
Insert into TestDate ( [ID],[PerID],[CommID],[ReportingMonth],[Number])
Values ('10235','12498','2','Jul-16','15');
I need to calculate StartDate and EndDate based on ReportingMonth with the difference of Number column
Expected output for StartDate and EndDate should be like this
ID PerID CommID ReportingMonth StartDate Number EndDate
3820 12508 7 Jul-16 30/06/2016 31 31/07/2016
10235 12498 58 Aug-16 15/07/2016 1 1/08/2016
10235 12498 2 Jul-16 15/07/2016 15 1/08/2016
SQL Query that I used to calculate start date for one month is working but when there are more than one same ID then it is not working
select [ID]
,[PerID]
,[CommID]
,[ReportingMonth]
,[Number]
,DATEADD(DAY, -(cast([Number] as int)), eomonth(CAST([Number] as DateTime))) as StartDate
from TestDate
How to get StartDate and EndDate base on just having only ReportingMonth field
Thanks,
Sandeep

SQL Server is pretty good about figuring out month conversions. So, this should work:
select cast('01-' + ReportingMonth as date) as month_start,
eomonth(cast('01-' + ReportingMonth as date)) as month_end

Related

DATEADD function is not bringing the correct data SQL Server

I have a float datatype that is actually a date. I need to use it in the condition to obtain the data for the last 10 minutes.
I used CAST to convert from float to datetime. and then DATEADD to collect the last 10 minutes data but its not working.
select CAST(StartTime AS DATETIME) as StartTime
,CAST(endtime AS DATETIME) as EndTime
from BIORADFM_TASK_AUDIT
where CAST(StartTime AS DATETIME) >= DATEADD(minute, 10, GETDATE());
Result:
StartTime EndTime
----------------------- -----------------------
2017-10-12 16:57:06.997 2017-10-12 16:57:06.997
2017-10-12 06:06:59.997 2017-10-12 06:06:59.997
2017-10-12 06:06:47.997 2017-10-12 06:06:47.997
2017-10-11 16:04:53.000 2017-10-11 16:04:53.000
It´s showing data for the next two days, instead of the last 10 minutes starting from getdate.
Table Structure:
CREATE TABLE [dbo].[BIORADFM_TASK_AUDIT](
[STRGUID] [nvarchar](32) NOT NULL,
[ACTIVITYUSERID] [int] NOT NULL,
[ACTIVITYSESSIONID] [int] NOT NULL,
[ACTIVITYCODE] [int] NOT NULL,
[SERVERNAME] [nvarchar](256) NOT NULL,
[APPNAME] [nvarchar](20) NOT NULL,
[STARTTIME] [float] NOT NULL,
[ENDTIME] [float] NOT NULL,
[STRDESCRIPTION] [nvarchar](1000) NULL,
[STRMODULENAME] [nvarchar](300) NULL,
CONSTRAINT [PK_BIORADFM_TASK_AUDIT] PRIMARY KEY CLUSTERED
Sample Data:
StartTime Endtime
43020.2549421296 43020.2549421296
43020.2550810185 43020.2550810185
43020.6342939815 43020.6342939815
43020.2548032407 43020.2548032407
43020.2548263889 43020.2548263889
43020.2549421296 43020.2549421296
43020.2549305556 43020.2549305556
43020.2549421296 43020.2549421296
43019.2549189815 43019.2549189815
Don't use datediff(). It counts the number of "time boundaries" between two date/times.
Instead, use date arithmetic:
WHERE A.ActivityUserID = B.lUserID and
CAST(A.StartTime AS DATETIME) >= DATEADD(minute, -10, GETDATE())
If you want rows with an start time situated only in the last 10 minutes you have to filter by range, for example with BETWEEN:
SELECT CAST(StartTime AS DATETIME) AS StartTime, CAST(endtime AS DATETIME) AS EndTime
FROM BIORADFM_TASK_AUDIT
WHERE CAST(StartTime AS DATETIME) BETWEEN DATEADD(minute,-10,GETDATE()) AND GETDATE()
Just a wild guess since we have nothing to work with. It would be a good idea to store datetime data as datetime instead of a strings so you don't have to constantly convert your data to the right datatype.
SELECT A.strGUID
, u.sUserName
, A.ServerName
, A.AppName
, A.ActivityCode
, CAST(A.StartTime AS DATETIME) as StartTime
, CAST(A.endtime AS DATETIME) as EndTime
, A.strModuleName
, A.strDescription
, GETDATE()
FROM INTRAWPROD_TASK_AUDIT A
join HSV_ACTIVITY_USERS u on A.ActivityUserID = u.lUserID
where convert(datetime, a.StartTime) >= convert(datetime, dateadd(minute, -10, GETDATE()))

What is the best way to get active employee count per month?

I have Employee like below:
DECLARE #Employees TABLE
(
[EmployeeID] [int] IDENTITY(1,1) NOT NULL,
[HireDate] [datetime] NOT NULL,
[TerminationDate] [datetime] NULL
)
INSERT INTO #Employees (HireDate, TerminationDate) VALUES ('2016/01/01','2016/01/02')
INSERT INTO #Employees (HireDate, TerminationDate) VALUES ('2016/02/01', '2017/01/30')
INSERT INTO #Employees (HireDate, TerminationDate) VALUES ('2016/03/01', '2016/05/05')
If I need to know the count of active employees for Feb 2016, I used below query:
SELECT * FROM #Employees
WHERE HireDate <= '2016-02-28' AND TerminationDate >= '2016-02-28'
However, I'm having difficulty on an easy method to find active employees for each month. For example, I want to know count of active employees from Jan 2016 to Jan 2017 every month.
Do I need to have separate table with each month and use some CTE to cross reference both tables and provide report for every month? Any directions will be grateful.
With the inputs so far, I have got to this. It seems to be working fine except for Jan 2016 where I have one employee active though only for 2 days, it is not reporting since I know I'm validating month-end. Any tweaks?
DECLARE #startDate DATETIME
DECLARE #endDate datetime
SET #startDate='2014-01-31'
SET #endDate='2017-05-31'
DECLARE #Employees TABLE
(
[EmployeeID] [int] IDENTITY(1,1) NOT NULL,
[HireDate] [datetime] NOT NULL,
[TerminationDate] [datetime] NULL
)
INSERT INTO #Employees (HireDate, TerminationDate) VALUES ('2016/01/01','2016/01/02')
INSERT INTO #Employees (HireDate, TerminationDate) VALUES ('2016/02/01', '2017/01/30')
INSERT INTO #Employees (HireDate, TerminationDate) VALUES ('2016/03/01', '2016/05/05')
;With MyListOfDates( MyCalendarMonthEnd )
AS
(
SELECT #startDate MyCalendarMonthEnd
UNION ALL
SELECT DATEADD(MONTH, 1, MyCalendarMonthEnd)
FROM MyListOfDates
WHERE MyCalendarMonthEnd < #endDate
)
SELECT YEAR(mld.MyCalendarMonthEnd) Year, MONTH(mld.MyCalendarMonthEnd) Month, COUNT(*) ActiveEmployeeCount
FROM MyListOfDates mld
JOIN #Employees e on 1 = 1
WHERE e.HireDate <= mld.MyCalendarMonthEnd and e.TerminationDate >= mld.MyCalendarMonthEnd
GROUP BY mld.MyCalendarMonthEnd
One option is to use an ad-hoc tally table. A tally/calendar table would do the trick as well
I opted for the DatePart DAY to capture any portion of the month
Example
Declare #Date1 date = '2016-01-01'
Declare #Date2 date = '2017-01-31'
Select Year = DatePart(YEAR,D)
,Month = DatePart(MONTH,D)
,EmpCnt = count(DISTINCT [EmployeeID])
From (Select Top (DateDiff(DAY,#Date1,#Date2)+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),#Date1) From master..spt_values n1,master..spt_values n2) A
Left Join #Employees B on D between [HireDate] and IsNull([TerminationDate],GetDate())
Group By DatePart(YEAR,D), DatePart(MONTH,D)
Order By 1,2
Returns
Year Month EmpCnt
2016 1 1
2016 2 1
2016 3 2
2016 4 2
2016 5 2
2016 6 1
2016 7 1
2016 8 1
2016 9 1
2016 10 1
2016 11 1
2016 12 1
2017 1 1
As Requested - Some Commentary
First we create a series of dates between X and Y. This is done via an ad-hoc tally table, Row_Number(), and DateAdd(). For example:
Declare #Date1 date = '2016-01-01'
Declare #Date2 date = '2017-01-31'
Select Top (DateDiff(DAY,#Date1,#Date2)+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),#Date1)
From master..spt_values n1,master..spt_values n2
Returns
D
2016-01-01
2016-01-02
2016-01-03
2016-01-04
...
2017-01-29
2017-01-30
2017-01-31
Notice that we are performing a cross join on spt_values (n1 and n2). This is because spt_values has only 2,523 records (or days). Considering that would equate to only 6 years, by using a cross join which expands the potential time span of 6.3 million days --- a ridiculous number, but you would never see that volume because we specify TOP ( nDays )
Once we have this dataset of target days, we then perform a LEFT JOIN to the EMPLOYEE table where D is between Hire and Term dates. This actually create a large temporal dataset. For example if an employee was active for only 10 days, we would see 10 records. 1 for for each day.
Then we perform a simple aggregation COUNT(DISTINCT EmployeeID) group by year and month.
In case anyone interested in the solution using CTEs. Preferred solution is provided by #JohnCappelleti
DECLARE #startDate DATETIME
DECLARE #endDate datetime
SET #startDate='2014-01-31'
SET #endDate='2017-05-31'
DECLARE #Employees TABLE
(
[EmployeeID] [int] IDENTITY(1,1) NOT NULL,
[HireDate] [datetime] NOT NULL,
[TerminationDate] [datetime] NULL
)
INSERT INTO #Employees (HireDate, TerminationDate) VALUES ('2016/01/01','2016/01/02')
INSERT INTO #Employees (HireDate, TerminationDate) VALUES ('2016/02/01', '2017/01/30')
INSERT INTO #Employees (HireDate, TerminationDate) VALUES ('2016/03/01', '2016/05/05')
;With MyListOfDates( MyCalendarMonthEnd )
AS
(
SELECT #startDate MyCalendarMonthEnd
UNION ALL
SELECT DATEADD(DAY, 1, MyCalendarMonthEnd)
FROM MyListOfDates
WHERE MyCalendarMonthEnd < #endDate
)
SELECT YEAR(mld.MyCalendarMonthEnd) Year, MONTH(mld.MyCalendarMonthEnd) Month, COUNT(DISTINCT EmployeeID) ActiveEmployeeCount
FROM MyListOfDates mld
JOIN #Employees e on 1 = 1
WHERE e.HireDate <= mld.MyCalendarMonthEnd and e.TerminationDate >= mld.MyCalendarMonthEnd
GROUP BY YEAR(mld.MyCalendarMonthEnd), MONTH(mld.MyCalendarMonthEnd)
ORDER BY 1,2
OPTION (MAXRECURSION 0)
I have already queried #Techspider to explain the output in tabular form.
I am not using ROW_Number or distinct.
I am not using CROSS Join because My output is Each Month,Each Year (not each day,each month,each year).
Also you have to find each month count
Also finding count for such long duration will slow down
Try this,
DECLARE #startDate DATETIME
DECLARE #endDate datetime
SET #startDate='2016-01-01'
SET #endDate='2017-01-31'
DECLARE #Employees TABLE
(
[EmployeeID] [int] IDENTITY(1,1) NOT NULL,
[HireDate] [datetime] NOT NULL,
[TerminationDate] [datetime] NULL
)
INSERT INTO #Employees (HireDate, TerminationDate) VALUES ('2016/01/01','2016/01/02')
INSERT INTO #Employees (HireDate, TerminationDate) VALUES ('2016/02/01', '2017/01/30')
INSERT INTO #Employees (HireDate, TerminationDate) VALUES ('2016/03/01', '2016/05/05')
SELECT datepart(year,EDT)[Year],datepart(month,edt)[Month]
,count( e.[EmployeeID]) EmpCount
FROM
(SELECT dateadd(month,number,#startDate)STDT
,dateadd(day,-1,dateadd(month,datediff(month,0,(dateadd(month,number,#startDate)))+1,0)) EDT
FROM MASTER.dbo.spt_values
WHERE name is null and number<=datediff(month,#startDate,#endDate)+1)n
left join #Employees E on
HireDate <= n.STDT
AND TerminationDate >= n.EDT
group by datepart(year,EDT),datepart(month,edt)
order by 1,2

Splitting datetime into date/year/month/dayname and count year

I got question to split date, month year, dayname from datetime by using trigger, when I insert a datetime, then in next column will split date, time, month year dayname and count year (to know how old the man in my data) is that possible ?
For example, if I insert
INSERT INTO MAN VALUES ('04/06/1982')
then will be like this
DATETIME DATE MONTH YEAR DAYNAME AGE
04/06/1982 00:00:00 04 06 1982 friday 27
Try this :-
Declare #myDate datetime
set #myDate='19820604' --YYYYMMDD
Select #myDate as DateTime,
datename(day,#myDate) as Date,
month(#myDate) as Month,
datename(year,#myDate) as Year,
Datename(weekday,#myDate) as DayName,
DATEDIFF ( year , #myDate , getdate() ) as Age
Result
╔══════════════════════════════╦══════╦═══════╦══════╦═════════╦══════════╗
║ DateTime ║ DATE ║ MONTH ║ YEAR ║ DAYNAME ║ Age ║
╠══════════════════════════════╬══════╬═══════╬══════╬═════════╬══════════╣
║ April, 06 1982 00:00:00+0000 ║ 4 ║ 6 ║ 1982 ║ Friday ║ 31 ║
╚══════════════════════════════╩══════╩═══════╩══════╩═════════╩══════════╝
SQL Fiddle Demo
The code has been slightky altered to give the age correctly.
Select myDate,myDateDate,myDateMonth,myDateYear,myDateDayName, Convert(varchar(50),Age)+ ' Years and '+Convert(varchar(50),nodays) +'days' {Age] from
(
Select #myDate as myDate,
datename(day,#myDate) as myDateDate,
month(#myDate) as myDateMonth,
datename(year,#myDate) as myDateYear,
Datename(weekday,#myDate) as myDateDayName,
DATEDIFF ( year , #myDate , getdate() ) Age ,
DATEDIFF ( dd , #myDate , getdate() ) -365* DATEDIFF ( year , #myDate , getdate() ) as nodays
) As a
Thanks
Arun
Here there are two approaches for solving the issue:
- Approach 1:
you can add some computed columns in to your table, so when you retrieve the table contents the other remaining fields are computed in that time.
-- 1.1) Create the base of table 'MAN'
CREATE TABLE [dbo].[MAN](
[DATETIME] [datetime] NOT NULL
) ON [PRIMARY]
GO
-- 1.2) Insert a record in it
INSERT INTO MAN VALUES ('04/06/1982')
GO
-- 1.3) Add some computed columns
ALTER TABLE MAN
ADD
[DAY] AS DATENAME(DAY, [DATETIME]),
[MONTH] AS MONTH([DATETIME]),
[YEAR] AS DATENAME(YEAR, [DATETIME]),
[DAYNAME] AS DATENAME(WEEKDAY, [DATETIME]),
[AGE] AS DATEDIFF(YEAR, [DATETIME], GETDATE())
GO
-- 1.4) See the result
SELECT * FROM MAN
- Approach 2:
While you creating the table, you add the remaining fields that you need, so that in the next phase you add insert/update triggers in order to calculate the remaining fields and insert/update them.
-- 2.1) Create the table 'MAN' with all needed columns
CREATE TABLE [dbo].[MAN](
[DATETIME] [datetime] NOT NULL,
[DAY] [int] NULL,
[MONTH] [int] NULL,
[YEAR] [int] NULL,
[DAYNAME] [nvarchar](10) NULL,
[AGE] [int] NULL
) ON [PRIMARY]
GO
-- 2.2) Create Insert and update triggers in order to calculate the values of the rest fields while inserting/updating
CREATE TRIGGER [dbo].[trCalculateRemainingDateFields] ON MAN
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE MAN
SET
[DAY] = DATENAME(DAY, MAN.[DATETIME]),
[MONTH] = MONTH(MAN.[DATETIME]),
[YEAR] = DATENAME(YEAR, MAN.[DATETIME]),
[DAYNAME] = DATENAME(WEEKDAY, MAN.[DATETIME]),
[AGE] = DATEDIFF(YEAR, MAN.[DATETIME], GETDATE())
FROM Inserted i
WHERE i.[DATETIME] = MAN.[DATETIME]
END
GO
-- 2.3) Insert a record in it
INSERT INTO MAN ([DATETIME]) VALUES ('04/06/1985')
GO
-- 2.4) See the result
SELECT * FROM MAN
- Approach 3:
Creating a view of the main table which calculate the remaining fields can be a good option.
-- 3.1) Create the base of table 'MAN'
CREATE TABLE [dbo].[MAN](
[DATETIME] [datetime] NOT NULL
) ON [PRIMARY]
GO
-- 3.2) Insert a record in it
INSERT INTO MAN VALUES ('04/06/1982')
GO
-- 3.3) Create a view which is contained the remaining fields
CREATE VIEW dbo.vMAN
AS
SELECT [DATETIME],
DATENAME(DAY, [DATETIME]) AS [DAY],
MONTH([DATETIME]) AS [MONTH],
DATENAME(YEAR, [DATETIME]) AS [YEAR],
DATENAME(WEEKDAY, [DATETIME]) AS [DAYNAME],
DATEDIFF(YEAR, [DATETIME], GETDATE()) AS [AGE]
FROM MAN
GO
-- 3.4) See the result
SELECT * FROM vMAN

How could I "auto-rotate" appended records in SQL (1 goes to 2, 2 goes 3, 3 goes to 4, 4 goes back to 1)?

I'm working on a system (ASP.NET/MSSQL/C#) for scheduling restaurant employees.
The problem I'm having is I need to "auto-rotate" the shift "InTimes" every week.
The user needs to be able to copy one day's schedule to the same day next week with all the employee shift times rotated one slot.
For example, in the table below, Monica has the 10:30am shift this Monday, so she would have the 11:00am next week, and Adam would go from 12:00pm to 10:30am.
The time between shifts is not constant, nor is the number of employees on each shift.
Any ideas on how to do this (ideally with SQL statements) would be greatly appreciated.
Please keep in mind I'm a relative novice.
RecordID EmpType Date Day Meal ShiftOrder InTime EmployeeID
1 Server 29-Aug-11 Monday Lunch 1 10:30:00 AM Monica
2 Server 29-Aug-11 Monday Lunch 2 11:00:00 AM Sofia
3 Server 29-Aug-11 Monday Lunch 3 11:30:00 AM Jenny
4 Server 29-Aug-11 Monday Lunch 4 12:00:00 PM Adam
5 Server 29-Aug-11 Monday Dinner 1 4:30:00 PM Adam
6 Server 29-Aug-11 Monday Dinner 2 4:45:00 PM Jenny
7 Server 29-Aug-11 Monday Dinner 3 5:00:00 PM Shauna
8 Server 29-Aug-11 Monday Dinner 4 5:15:00 PM Sofia
10 Server 29-Aug-11 Monday Dinner 5 5:30:00 PM Monica
Somehow an employee would need to get his last (few) shifts
SELECT TOP 3 * FROM shift WHERE EmployeeID LIKE 'monica' ORDER BY [date] DESC
Next he/she would need to enter the time and date offset he would like to work next week, relative to a schedule before.
INSERT INTO shift SELECT
recordID
,[date]
,CASE [Intime]
WHEN [Intime] BETWEEN 00:00 AND 10:00 THEN 'Breakfast'
WHEN [Intime] BETWEEN 10:01 AND 04:29 THEN 'Lunch'
WHEN [Intime] BETWEEN 04:30 AND 23:59 THEN 'Dinner'
END as Meal
,No_idea_how_to_generate_this AS ShiftOrder
,[Intime]
,EmployeeID
FROM (SELECT
NULL as recordID
,DATEADD(DAY, 7+#dateoffset, ls.[date]) as [date]
,CAST(DATEADD(MINUTE, #timeoffset, ls.[time] AS TIME) as [Intime]
,EmployeeId
FROM Shift WHERE recordID = #recordID ) AS subselect
Here:
- #recordID is the record the employee choose as the starting point for the new appointment.
- #dateoffset is the number of days to add the the starting record
- #timeoffset is the number of minutes to add to the starting record
All the rest is determined by the row the user used as the starting point.
Here's what I came up with:
CREATE TABLE #tmp
(
[RecordID] INT ,
[EmpType] VARCHAR(20) ,
[Date] DATE ,
[Day] VARCHAR(10) ,
[Meal] VARCHAR(10) ,
[ShiftOrder] INT ,
[InTime] TIME ,
[EmployeeID] VARCHAR(50)
)
INSERT INTO [#tmp]
( [RecordID] ,
[EmpType] ,
[Date] ,
[Day] ,
[Meal] ,
[ShiftOrder] ,
[InTime] ,
[EmployeeID]
)
VALUES (1,'Server','29-Aug-11','Monday','Lunch',1,'10:30:00 AM','Monica'),
(2,'Server','29-Aug-11','Monday','Lunch',2,'11:00:00 AM','Sofia'),
(3,'Server','29-Aug-11','Monday','Lunch',3,'11:30:00 AM','Jenny'),
(4,'Server','29-Aug-11','Monday','Lunch',4,'12:00:00 PM','Adam'),
(5,'Server','29-Aug-11','Monday','Dinner',1,'4:30:00 PM','Adam'),
(6,'Server','29-Aug-11','Monday','Dinner',2,'4:45:00 PM','Jenny'),
(7,'Server','29-Aug-11','Monday','Dinner',3,'5:00:00 PM','Shauna'),
(8,'Server','29-Aug-11','Monday','Dinner',4,'5:15:00 PM','Sofia'),
(10,'Server','29-Aug-11','Monday','Dinner',5,'5:30:00 PM','Monica');
WITH CountByShift AS (SELECT *, COUNT(1) OVER (PARTITION BY EmpType, [Day], [Meal]) AS [CountByShiftByDayByEmpType]
FROM [#tmp]
),
NewShiftOrder AS (
SELECT *, ([ShiftOrder] + 1) % [CountByShiftByDayByEmpType] AS [NewShiftOrder]
FROM [CountByShift]
)
SELECT [RecordID] ,
[EmpType] ,
[Date] ,
[Day] ,
[Meal] ,
[ShiftOrder] ,
CASE WHEN [NewShiftOrder] = 0 THEN [CountByShiftByDayByEmpType] ELSE [NewShiftOrder] END AS [NewShiftOrder],
[InTime] ,
[EmployeeID]
FROM NewShiftOrder
ORDER BY [RecordID]
You need a table with all of the shifts in it:
create table dbo.Shifts (
[Day] varchar(9) not null,
Meal varchar(6) not null,
ShiftOrder integer not null,
InTime time not null,
constraint PK__dbo_Shifts primary key ([Day], Meal, ShiftOrder)
);
If that table is properly populated you can then run this to get a map of the current Day, Meal, ShiftOrder n-tuple to the next in that Day, Meal pair:
with numbers_per_shift as (
select [Day], Meal, max(ShiftOrder) as ShiftOrderCount
from dbo.Shifts s
group by [Day], Meal
)
select s.[Day], s.Meal, s.ShiftOrder,
s.ShiftOrder % n.ShiftOrderCount + 1 as NextShiftOrder
from dbo.Shifts as s
inner join numbers_per_shift as n
on s.[Day] = n.[Day]
and s.Meal = n.Meal;
For the table to be properly populated each of the shift orders would have to begin with one and increase by one with no skipping or repeating within a Day, Meal pair.
Borrowing most of the #tmp table definition from #Ben Thul, assuming you have an identity field, not assuming you are storing dates and times as dates and times...this should run well over and over, copying the latest date into the following week:
CREATE TABLE #tmp
(
[RecordID] INT ,
[EmpType] VARCHAR(20) ,
[Date] VARCHAR(9) ,
[Day] VARCHAR(10) ,
[Meal] VARCHAR(10) ,
[ShiftOrder] INT ,
[InTime] VARCHAR(11) ,
[EmployeeID] VARCHAR(50)
)
INSERT INTO [#tmp]
( [RecordID] ,
[EmpType] ,
[Date] ,
[Day] ,
[Meal] ,
[ShiftOrder] ,
[InTime] ,
[EmployeeID]
)
VALUES (1,'Server','29-Aug-11','Monday','Lunch',1,'10:30:00 AM','Monica'),
(2,'Server','29-Aug-11','Monday','Lunch',2,'11:00:00 AM','Sofia'),
(3,'Server','29-Aug-11','Monday','Lunch',3,'11:30:00 AM','Jenny'),
(4,'Server','29-Aug-11','Monday','Lunch',4,'12:00:00 PM','Adam'),
(5,'Server','29-Aug-11','Monday','Dinner',1,' 4:30:00 PM','Adam'),
(6,'Server','29-Aug-11','Monday','Dinner',2,' 4:45:00 PM','Jenny'),
(7,'Server','29-Aug-11','Monday','Dinner',3,' 5:00:00 PM','Shauna'),
(8,'Server','29-Aug-11','Monday','Dinner',4,' 5:15:00 PM','Sofia'),
(10,'Server','29-Aug-11','Monday','Dinner',5,' 5:30:00 PM','Monica');
with
Shifts as (
select EmpType, [Day], Meal, ShiftOrder, InTime
from #tmp
where [Date] = (select max(cast([Date] as datetime)) from #tmp)
),
MaxShifts as (
select EmpType, [Day], Meal, max(ShiftOrder) as MaxShiftOrder
from #tmp
where [Date] = (select max(cast([Date] as datetime)) from #tmp)
group by EmpType, [Day], Meal
)
insert into #tmp (EmpType, [Date], [Day], Meal, ShiftOrder, InTime, EmployeeID)
select s.EmpType
, replace(convert(varchar(11), dateadd(dd, 7, cast(a.[Date] as datetime)), 6), ' ', '-') as [Date]
, s.Day
, s.Meal
, s.ShiftOrder
, s.InTime
, a.EmployeeID
from #tmp as a
join MaxShifts as m on a.EmpType = m.EmpType
and a.[Day] = m.[Day]
and a.Meal = m.Meal
join Shifts as s on a.EmpType = s.EmpType
and a.[Day] = s.[Day]
and a.Meal = s.Meal
and 1 + a.ShiftOrder % m.MaxShiftOrder = s.ShiftOrder
where a.[Date] = (select max(cast([Date] as datetime)) from #tmp)
I'm assuming that the schedule is really tied to a meal and weekday in a below answer.
Also I would like to note that ShiftOrder and Day columns should not be columns. Day is obviously determined by Date so it is a total waste of space (computed column OR determine it on the UI side) and ShiftOrder is determined by Date and InTime columns (probably easy to calculate in a query with RANK() function or on the UI side). That said it will make this query a bit easier :)
declare #dt date = cast('29-Aug-11' as date)
/* note: the date above may be passed from UI or it maybe calculated based on getdate() and dateadd function or s.t. like that */
INSERT INTO [table] (EmpType,Date,Day,Meal,ShiftOrder,InTime,EmployeeID)
SELECT t1.EmpType, dateadd(day, 7, t1.date), t1.day, t1.meal, t2.ShiftOrder, t2.InTime, t1.EmployeeID
FROM [table] t1
INNER JOIN [table] t2
ON (t1.Date = t2.Date
and t1.Meal = t2.Meal
and (
t1.ShiftOrder = t2.ShiftOrder + 1
or
(
t1.ShiftOrder = (select max(shiftOrder) from [table] where meal = t1.meal and date =t1.date)
and
t2.ShiftOrder = (select min(shiftOrder) from [table] where meal = t1.meal and date =t1.date)
)
)
)
WHERE t1.Date = #dt
This is a pretty straight-forward set-oriented problem. Aggregations (count(*) and max()) and lookup tables are unnecessary. You can do it with one SQL statement.
The first step (set) is to identity those employees who simply slide down in the schedule.
The next step (set) is to identity those employees who need to "wrap around" to the head of the schedule.
Here's what I came up with:
/* Set up the temp table for demo purposes */
DROP TABLE #tmp
CREATE TABLE #tmp
(
[RecordID] INT ,
[EmpType] VARCHAR(20) ,
[Date] DATE ,
[Day] VARCHAR(10) ,
[Meal] VARCHAR(10) ,
[ShiftOrder] INT ,
[InTime] TIME,
[EmployeeID] VARCHAR(50)
)
INSERT INTO [#tmp]
( [RecordID] ,
[EmpType] ,
[Date] ,
[Day] ,
[Meal] ,
[ShiftOrder] ,
[InTime] ,
[EmployeeID]
)
VALUES (1,'Server','29-Aug-11','Monday','Lunch',1,'10:30:00 AM','Monica'),
(2,'Server','29-Aug-11','Monday','Lunch',2,'11:00:00 AM','Sofia'),
(3,'Server','29-Aug-11','Monday','Lunch',3,'11:30:00 AM','Jenny'),
(4,'Server','29-Aug-11','Monday','Lunch',4,'12:00:00 PM','Adam'),
(5,'Server','29-Aug-11','Monday','Dinner',1,' 4:30:00 PM','Adam'),
(6,'Server','29-Aug-11','Monday','Dinner',2,' 4:45:00 PM','Jenny'),
(7,'Server','29-Aug-11','Monday','Dinner',3,' 5:00:00 PM','Shauna'),
(8,'Server','29-Aug-11','Monday','Dinner',4,' 5:15:00 PM','Sofia'),
(10,'Server','29-Aug-11','Monday','Dinner',5,' 5:30:00 PM','Monica');
/* the "fills" CTE will find those employees who "wrap around" */
;WITH fills AS (
SELECT
[d2].[EmpType],
[d2].[Date],
[d2].[Day],
[d2].[Meal],
1 AS [ShiftOrder],
[d2].[InTime],
[d2].[EmployeeID]
FROM
[#tmp] d1
RIGHT OUTER JOIN
[#tmp] d2 ON
([d1].[Meal] = [d2].[Meal])
AND ([d1].[ShiftOrder] = [d2].[ShiftOrder] + 1)
WHERE
[d1].[EmployeeID] IS NULL
)
INSERT INTO [table] (EmpType,Date,Day,Meal,ShiftOrder,InTime,EmployeeID)
SELECT
[d1].[EmpType],
DATEADD(DAY, 7, [d1].[Date]) AS [Date],
DATENAME(dw,(DATEADD(DAY, 7, [d1].[Date]))) AS [Day],
[d1].[Meal],
[d1].[ShiftOrder],
[d1].[InTime],
ISNULL([d2].[EmployeeID], [f].[EmployeeID]) AS [EmployeeID]
FROM
[#tmp] d1
LEFT OUTER JOIN
[#tmp] d2 ON
([d1].[Meal] = [d2].[Meal]) AND ([d1].[ShiftOrder] = [d2].[ShiftOrder] + 1)
LEFT OUTER JOIN
[fills] f ON
([d1].[Meal] = [f].[Meal]) AND ([d1].[ShiftOrder] = [f].[ShiftOrder])
You can use a subquery (for a tutorial on subqueries, see http://www.databasejournal.com/features/mssql/article.php/3464481/Using-a-Subquery-in-a-T-SQL-Statement.htm) to get the last shift time.
After this, its trivial addition and modular division (in case you don't know what that is, have a look at this).
Hope this helped. I'm a bit tired right now, so I can't provide you with an example.
I'm a SQL programmer and DBA for 20 yrs now. With that said, business logic this complex should be in the C# part of the system. Then the TDD built application can handle the inevitable changes, and still be refactor-able and correct.
My recommendation is 'push-back'. Your response should be something along the lines of "This isn't just some look-up/fill-in the blank logic. This kind of complex business logic belongs in the App". It belongs in something that can be unit tested, and will be unit tested every time its changed.
The right answer sometimes is 'No', this is one of them.
How about using a Pivot Table for all employees and then adding shift timings as rows?? Order the names based on Shift for the initial Day.
Something like this..
Date_time Shift_Order Monica Sofia Jenny Adam Shauna
08/29/11 1 10:30AM 11:00AM 11:30AM 12:00PM NULL
08/29/11 2 5:30PM 5:15PM 4:45PM 4:30PM 5:00PM

SQL Select within a select

I'm creating a dataset that will be displayed in an SSRS report.
I have a query in a job that puts a count into a table [dbo].[CountMetersDue] on a rolling basis on the 1st of every month; the value changes throughout the month so need to take a snapshot at beginning.
I have the report set up which uses a custom expression to produce a cumulative trend graph. Basically takes one value, divides by another to work out a percentage. Therefore I have two queries that need combining... Took me ages to get my head round all this!
I just need help with the last bit.
SELECT (SELECT [Count]
FROM [MXPTransferDev].[dbo].[CountMetersDue]
WHERE [MXPTransferDev].[dbo].[CountMetersDue].[DateTime] =
[MXPTransferDev].[dbo].[Readings].[dateRead]) AS [MetersDue],
COUNT(readingid) AS [TotalReadings],
CONVERT(DATE, dateread) AS [dateRead]
FROM [MXPTransferDev].[dbo].[Readings]
WHERE ( [MXPTransferDev].[dbo].[Readings].[dateRead] BETWEEN
'01-may-11' AND '31-may-11' )
AND ( webcontactid IS NOT NULL )
AND ( meter = 1 )
GROUP BY CONVERT(DATE, [MXPTransferDev].[dbo].[Readings].[dateRead])
CREATE TABLE [dbo].[CountMetersDue](
[Count] [int] NULL,
[DateTime] [datetime] NULL
) ON [USER]
GO
ALTER TABLE [dbo].[CountMetersDue]
ADD CONSTRAINT [DF_CountMetersDue_DateTime] DEFAULT (getdate()) FOR [DateTime]
GO
CREATE TABLE [dbo].[Readings](
[readingId] [bigint] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
[dateRead] [datetime] NOT NULL,
[meter] [int] NOT NULL,
[webcontactid] [bigint] NULL,
Readings
readingId meter reading dateRead webcontactid
583089 4 3662 2011-05-25 15:00:33.040 479
583207 3 682 2011-05-25 15:00:33.027 479
583088 2 98064 2011-05-25 15:00:33.007 479
CountMetersDue
Count DateTime
2793 2011-12-01 00:00:00.000
1057 2011-05-01 14:08:20.437
610 2011-03-01 00:00:00.000
Second stab at answering your question (will probably need some clarification from yourself before the answer is correct):
/* DDL: 2 tables [CountMetersDue] & [Readings]
[CountMetersDue]
([DateTime] datetime,
[Count] int)
[Readings]
([ReadingId] bigint,
[dateRead] datetime,
[webcontactid] bigint,
[meter] int)
[CountMetersDue] - contains 1 record on the first of every month, with count of the number of readings at that date
[Readings] - contains all the individual readings
ie:
[CountMetersDue]
01-Jan-2011 1000
01-Feb-2011 2357
01-Mar-2011 3000
[Readings]
1 01-Jan-2011 11 1
2 02-Jan-2011 12 1
3 03-Jan-2011 13 1
...
*/
SELECT
CONVERT(DATE, [dbo].[Readings].[dateRead]) AS dateRead,
COUNT([dbo].[Readings].[readingId]) AS TotalReadings,
[dbo].[CountMetersDue].[Count] AS MetersDue
FROM
[CountMetersDue] /* get all count meters due */
left join [Readings] /* get any corresponding Reading records
where the dateRead in the same month as
the CountMetersDue */
on DATEPART(year, Readings.dateRead) = DATEPART(year, [CountMetersDue].[DateTime]) /* reading in same year as CountMetersDue */
and DATEPART(month, Readings.dateRead) = DATEPART(month, [CountMetersDue].[DateTime]) /* reading in same month as CountMetersDue */
WHERE ([MXPTransferDev].[dbo].[Readings].[dateRead]) BETWEEN
#StartDate AND #EndDate
AND ( webcontactid IS NOT NULL )
AND ( meter = 1 )
GROUP BY
[dbo].[CountMetersDue].[Count],CONVERT(DATE, [dbo].[Readings].[dateRead])
This would be the query you are looking for then?
Subqueries, as they are called, can be included by enclosing them in parentheses '()'.
SELECT (SELECT [Count] FROM [xxxxx].[dbo].[CountMetersDue] AS tabA WHERE tabA.[datefield] = tabB.dateRead) AS [MetersDue], COUNT(readingId) AS [TotalReadings], CONVERT(DATE, dateRead) AS [dateRead]
FROM [xxxxx] AS tabB
WHERE (dateRead BETWEEN #StartDate AND #EndDate) AND (webcontactid IS NOT NULL) AND (meter = 1)
GROUP BY CONVERT(DATE, dateRead)