I have this table in which I am storing TimeIn and Time Out of Employee.
When I get Total Hours any Employee have worked in certain day, it works fine date wise. But in out organization the issue is that a day is considered from 6 AM till 5:59 AM (next day).
Here is my table and sample data.
CREATE TABLE [dbo].[Attendance]
(
[Employee] [varchar](50) NULL,
[TimeIn] [datetime] NULL,
[TimeOut] [datetime] NULL
) ON [PRIMARY]
GO
INSERT [dbo].[Attendance] ([Employee], [TimeIn], [TimeOut]) VALUES (N'Lewis', CAST(N'2018-12-01 06:30:00.000' AS DateTime), CAST(N'2018-12-01 18:22:00.000' AS DateTime))
GO
INSERT [dbo].[Attendance] ([Employee], [TimeIn], [TimeOut]) VALUES (N'Lewis', CAST(N'2018-12-01 20:12:00.000' AS DateTime), CAST(N'2018-12-01 23:50:00.000' AS DateTime))
GO
INSERT [dbo].[Attendance] ([Employee], [TimeIn], [TimeOut]) VALUES (N'Lewis', CAST(N'2018-12-02 00:12:00.000' AS DateTime), CAST(N'2018-12-02 04:50:00.000' AS DateTime))
GO
INSERT [dbo].[Attendance] ([Employee], [TimeIn], [TimeOut]) VALUES (N'Lewis', CAST(N'2018-12-02 07:21:00.000' AS DateTime), CAST(N'2018-12-02 19:54:00.000' AS DateTime))
GO
Here is the query and output of the query I am executing.
SELECT Employee, CAST(COALESCE(TimeIn, TimeOut) AS DATE) DATE, DATEDIFF(HOUR, MIN(TimeIn), MAX(TimeOut)) [Hours Worked]
FROM [dbo].[Attendance]
GROUP BY Employee, CAST(COALESCE(TimeIn, TimeOut) AS DATE)
Output:
Employee DATE Hours Worked
----------------- ---------- ------------
Lewis 2018-12-01 17
Lewis 2018-12-02 19
What I want is to get the working hours calculated from 6 AM to 5:59 AM next day. So the expected output is as below:
Employee DATE Hours Worked
----------------- ---------- ------------
Lewis 2018-12-01 22:20
Lewis 2018-12-02 12:33
Hope this is possible..
You should probably have a calendar table which contains all the dates which you want to appear in your report. In the absence of that, we can just assume that all dates are covered by the time, and we can group by the time in, shifted earlier by 6 hours. The trick here is that we can shift all times backwards by 6 hours, to align everything with the usual 24 hour day. Something like this should work:
SELECT
Employee,
CONVERT(date, DATEADD(HOUR, -6, TimeIn)) AS DATE,
CONVERT(VARCHAR(10), DATEDIFF(HOUR, MIN(TimeIn), MAX(TimeOut))) + ':' +
CONVERT(VARCHAR(10), DATEDIFF(MINUTE, MIN(TimeIn), MAX(TimeOut)) % 60) AS [Hours Worked]
FROM Attendance
GROUP BY
Employee,
CONVERT(date, DATEADD(HOUR, -6, TimeIn));
Demo
Related
I need to run a script that pulls existing data for the last 2 weeks (full weeks Sun-Sat) irrespective of when I run it.
This does not work as it's subtracting the number of days instead of the week giving incorrect data.
Select
convert(varchar(255), cast(cast(Date as varchar(255)) as date), 101) as Date, ID
From Table
where convert(varchar(255), cast(cast(date_key as varchar(255)) as date), 101)>=DATEADD(week,-3,GETDATE())
SELECT
convert(varchar(255), cast(cast(date_key as varchar(255)) as date), 101)as date_Key
FROM [dbo].[Table]
where datename(week, convert(varchar(255), cast(cast(date_key as varchar(255))as date), 101)) >= datename(week, getdate())-2
You should use the built in function DATEPART(weekday, dateValue) here
https://learn.microsoft.com/en-us/sql/t-sql/functions/datepart-transact-sql?view=sql-server-ver15
I try to find all Cust who have membership for at least for one day in each month during 2018.
I came up with solution checking their membership at the beginning / middle / end end of each month like in snippet below, but trying to find more intelligent solution.
I know that I can use tally table for each of 365 days to check this but probably there is more elegant solution ? I'm bit new to SQL, I think I'm missing something in GROUPing area.
In the code snippet shown below, both Cust have at least one day membership.
Desired output:
CustID
------
1
22
Code:
with data as
(
select *
from (values (1, 1, '2017-12-11', '2018-01-16'), (1, 22, '2018-01-28', '2018-03-9' ), (1, 333, '2018-03-1', '2018-12-31') , -- island
(22, 1, '2017-12-31', '2018-01-11'), (22, 2, '2017-2-11', '2019-12-31')) as t (CustID, ContractID, StartDD, EndDD) ---
)
select
isdate(startDD), isdate(EndDD)
from
data
), gaps as
(
select
*,
datediff(day, lag(EndDD, 1, StartDD) over (partition by CustID order by StartDD), StartDD) as BreakDD -- negative is island
from
data
)
select
*,
datepart(month,StartDD) mmS , datepart(month,EndDD) mmE
from
gaps
-- and was active any 1+ day during each of the 12 months in 2018 ????
where
1 = 1
/* and (cast('1/1/2018' as date) between StartDD and EndDD
or cast('1/15/2018' as date) between StartDD and EndDD
or cast('1/31/2018' as date) between StartDD and EndDD)
---- etc.. for each month
and ( cast('12/1/2018' as date) between StartDD and EndDD
or cast('12/15/2018' as date) between StartDD and EndDD
or cast('12/31/2018' as date) between StartDD and EndDD
)
*/
--select CustID, max(BreakDD) Max_Days
--from gaps
--group by CustID
Try this answer.
First create a function to return all the month and year between the given dates.
Function:
--SELECT * FROM dbo.Fn_GetMonthYear('2017-12-11','2018-01-16')
ALTER FUNCTION dbo.Fn_GetMonthYear(#StartDate DATETIME,#EndDate DATETIME)
RETURNS TABLE
AS
RETURN(
SELECT DATEPART(MONTH, DATEADD(MONTH, x.number, #StartDate)) AS [Month]
,DATEPART(YEAR, DATEADD(MONTH, x.number, #StartDate)) AS [Year]
FROM master.dbo.spt_values x
WHERE x.type = 'P'
AND x.number <= DATEDIFF(MONTH, #StartDate, #EndDate)
)
Table Schema:
CREATE TABLE #t(CustID INT, ContractID INT, StartDD date, EndDD date)
INSERT INTO #t values (1, 1, '2017-12-11', '2018-01-16'), (1, 22, '2018-01-28', '2018-03-9' ), (1, 333, '2018-03-1', '2018-12-31') , -- island
(22, 1, '2017-12-31', '2018-01-11'), (22, 2, '2017-2-11', '2019-12-31')
Here is the T-SQL Query for your requirement.
SELECT CustID
,COUNT(DISTINCT [Month]) NoOfMonths
FROM(
SELECT *
FROM #t t
CROSS APPLY dbo.Fn_GetMonthYear(StartDD,EndDD)
)D
WHERE [Year] = 2018
GROUP BY CustID
HAVING COUNT(DISTINCT [Month])=12
Result:
CustID NoOfMonths
1 12
22 12
find all Cust who have membership for at least for one day in each
month during 2018
I think this mean that data must be present between '2018-01-01' and '2018-12-31' for each custid.
CREATE TABLE #t(CustID INT, ContractID INT, StartDD date, EndDD date)
INSERT INTO #t values (1, 1, '2017-12-11', '2018-01-16'), (1, 22, '2018-01-28', '2018-03-9' ), (1, 333, '2018-03-1', '2018-12-31') , -- island
(22, 1, '2017-12-31', '2018-01-11'), (22, 2, '2017-2-11', '2019-12-31')
declare #From Datetime='2018-01-01'
declare #To datetime='2018-12-31'
;with CTE as
(
select CustID,min(StartDD)StartDD
,max(EndDD)EndDD
from #t
group by CustID
)
select CustID,StartDD
,EndDD
from CTE
where StartDD<=#From and EndDD>=#To
This script is not tested across all sample data.
But logic is clear.So it can be corrected accordingly.
So tell for what sample data it is not working.
In my old database, there is a table Album which stores information about ID, AlbumName, Release_Date (e.g. 01/01/2017) etc.
I want to further break down the Release_Date into a time dimension table, so I create a DimDateAlbum table.
This is the time dimension table I have created.
CREATE TABLE [DimDateAlbum]
(
[DateKey] INT PRIMARY KEY,
[Date] DATETIME NOT NULL,
[Year] INT NOT NULL,
[Quarter] TINYINT NOT NULL,
[QuarterName] VARCHAR(6) NOT NULL, -- January to March: First, April to
June: Second etc
[Month] TINYINT NOT NULL,
[MonthName] VARCHAR(9) NOT NULL, -- January, February etc
[Day] TINYINT NOT NULL, -- Field holds day number of Month
[DayofWeek] TINYINT NOT NULL,
[WeekName] VARCHAR(9) NOT NULL, -- Field displays 1: Monday, 2: Tuesday etc
)
As discussed below: I can insert Release_Date into time dimension table as [DateKey], however, how do I further break down the date into year, quarter, day etc.?
INSERT INTO DimDateAlbum
SELECT
a.Release_Date AS [DateKey],
CONVERT (char(8), a.Release_Date, 112) AS [DateKey],
a.Release_Date AS [Date],
DATEPART(YEAR, a.Release_Date) AS [Year], -- calendar year
DATEPART(QQ, a.Release_Date) AS [Quarter], -- calendar quarter
CASE (qq, a.Release_Date)
WHEN 1 THEN 'First'
WHEN 2 THEN 'Second'
WHEN 3 THEN 'Third'
WHEN 4 THEN 'Fourth'
END AS [QuarterName],
DATEPART(MONTH, a.Release_Date) AS [Month], -- month number of the year
DATENAME(MM, a.Release_Date) AS [MonthName], -- month name
DATEPART(DAY, a.Release_Date) AS [Day], -- day number of the month
DATEPART(DW, a.Release_Date) AS [DayofWeek], -- day number of week
CASE datepart(DW, a.Release_Date)
WHEN 1 THEN 'Monday'
WHEN 2 THEN 'Tuesday'
WHEN 3 THEN 'Wednesday'
WHEN 4 THEN 'Thursday'
WHEN 5 THEN 'Friday'
WHEN 6 THEN 'Saturday'
WHEN 7 THEN 'Sunday'
END AS [WeekName]
FROM
dbo.Album AS a
This code does not work, any help on how to fix it? Thank you so much!
If I understood your correctly you want to populate DimDateAlbum table. I've edited a little bit your table( added identity constraint to avoid writing this field manually) and now it looks like this:
CREATE TABLE [DimDateAlbum]
(
[DateKey] INT IDENTITY CONSTRAINT PK_DimDateAlbum_ID PRIMARY KEY,
[Date] DATETIME NOT NULL,
[Year] INT NOT NULL,
[Quarter] TINYINT NOT NULL,
[QuarterName] VARCHAR(50) NOT NULL, -- January to March: First, April to
[Month] TINYINT NOT NULL,
[MonthName] VARCHAR(9) NOT NULL, -- January, February etc
[Day] TINYINT NOT NULL, -- Field holds day number of Month
[DayofWeek] TINYINT NOT NULL,
[WeekName] VARCHAR(50) NOT NULL, -- Field displays 1: Monday, 2: Tuesday etc
)
And now you can insert your data. I've added a test variable to insert one row, however it can be used for inserting from table:
INSERT INTO dbo.DimDateAlbum
(
DateKey,
Date,
Year,
Quarter,
QuarterName,
Month,
MonthName,
Day,
DayofWeek,
WeekName
)
SELECT
CAST(a.Release_Date AS DATETIME)
, YEAR(CAST(a.Release_Date AS DATETIME)) --
, DATEPART(QUARTER, CAST(a.Release_Date AS DATETIME)) -- Quarter
, CASE -- Quarter Name
WHEN DATEPART(QUARTER, CAST(a.Release_Date AS DATETIME)) = 1 THEN 'January to March' -- Quarter Name
WHEN DATEPART(QUARTER, CAST(a.Release_Date AS DATETIME)) = 2 THEN 'April to June' -- Quarter Name
WHEN DATEPART(QUARTER, CAST(a.Release_Date AS DATETIME)) = 3 THEN 'July to September' -- Quarter Name
WHEN DATEPART(QUARTER, CAST(a.Release_Date AS DATETIME)) = 4 THEN 'October to December' -- Quarter Name
END
, MONTH(CAST(a.Release_Date AS DATETIME)) -- Month number
, DATENAME(MONTH, DATEADD( MONTH, MONTH(CAST(a.Release_Date AS DATETIME)), 0) - 1) -- Month name
, DAY(CAST(a.Release_Date AS DATETIME)) -- 6
, DATEPART(dw, CAST(a.Release_Date AS DATETIME)) -- 5
, DATENAME(dw, CAST(a.Release_Date AS DATETIME)) -- Thursday
FROM Album a
Work example:
DECLARE #FooDate VARCHAR(30) = '2018-12-06 12:10:51.727'
INSERT INTO dbo.DimDateAlbum
(
DateKey,
Date,
Year,
Quarter,
QuarterName,
Month,
MonthName,
Day,
DayofWeek,
WeekName
)
SELECT
CAST(#FooDate AS DATETIME)
, YEAR(CAST(#FooDate AS DATETIME)) --
, DATEPART(QUARTER, CAST(#FooDate AS DATETIME)) -- Quarter
, CASE -- Quarter Name
WHEN DATEPART(QUARTER, CAST(#FooDate AS DATETIME)) = 1 THEN 'January to March' -- Quarter Name
WHEN DATEPART(QUARTER, CAST(#FooDate AS DATETIME)) = 2 THEN 'April to June' -- Quarter Name
WHEN DATEPART(QUARTER, CAST(#FooDate AS DATETIME)) = 3 THEN 'July to September' -- Quarter Name
WHEN DATEPART(QUARTER, CAST(#FooDate AS DATETIME)) = 4 THEN 'October to December' -- Quarter Name
END
, MONTH(CAST(#FooDate AS DATETIME)) -- Month number
, DATENAME(MONTH, DATEADD( MONTH, MONTH(CAST(#FooDate AS DATETIME)), 0) - 1) -- Month name
, DAY(CAST(#FooDate AS DATETIME)) -- 6
, DATEPART(dw, CAST(#FooDate AS DATETIME)) -- 5
, DATENAME(dw, CAST(#FooDate AS DATETIME)) -- Thursday
I have a table with multiple record against userid and datetime fields. A user have availability for same date everyhour.
UserId DateTime
1 2018-08-13 08:30:00 +05:30
1 2018-08-13 09:30:00 +05:30
1 2018-08-13 10:30:00 +05:30
1 2018-08-13 15:00:00 +05:30
1 2018-08-13 17:00:00 +05:30
1 2018-08-13 18:00:00 +05:30
Now If I search for date suppose 2018-08-13 11:30:00 +05:30
then I want in
Previous slot = 2018-08-13 **10:30:00** +05:30
and next slot = 2018-08-13 **15:00:00** +05:30
Update
Quick update I need smaller time for the same date not the previous day. Ex. For
2018-08-13 08:30:00 +05:30 it should show null as no small time available.
One more try with CTEs, edited to match the changed question:
DECLARE #SearchDate datetime = '2018-08-13 11:30:00',
#StartDate datetime,
#EndDate datetime,
#UserId int = 1;
SET #StartDate = DATETIMEFROMPARTS(YEAR(#SearchDate), MONTH(#SearchDate), DAY(#SearchDate), 0, 0, 0, 0);
SET #EndDate = DATEADD(day, 1, #StartDate);
WITH
ctePrevious AS
(
SELECT MAX([DateTime]) AS [DateTime]
FROM YourTable
WHERE UserId = #UserId AND [DateTime] BETWEEN #StartDate AND #SearchDate
),
cteNext AS
(
SELECT MIN([DateTime]) AS [DateTime]
FROM YourTable
WHERE UserId = #UserId AND [DateTime] BETWEEN #SearchDate AND #EndDate
)
SELECT ctePrevious.[DateTime] AS prevDate, cteNext.[DateTime] AS nextDate
FROM ctePrevious, cteNext
It should return NULL values if no entry is found for the given date.
You could add a rownumber and a self join. The following example will demonstrate how it works:
CREATE TABLE #T (ID INT, DateT DATETIME)
INSERT INTO #T VALUES (1, GETDATE() - .2)
INSERT INTO #T VALUES (1, GETDATE() - .1)
INSERT INTO #T VALUES (1, GETDATE() - .05)
INSERT INTO #T VALUES (1, GETDATE())
INSERT INTO #T VALUES (1, GETDATE() + .1);
WITH CTE AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS RowNumb
FROM #T AS T
)
SELECT C.ID, C.DateT AS StartDate, C2.DateT AS EndDate
FROM CTE AS C
LEFT JOIN CTE AS C2 ON C.RowNumb = C2.RowNumb - 1
WHERE GETDATE() BETWEEN C.DateT AND C2.DateT -- your date input here
Make use of LEAD and LAG. This is Pseudo SQL, however:
SELECT DateColumn,
LAG(DateColumn) OVER (ORDER BY DateColumn) AS Previousslot,
LEAD(DateColumn) OVER (ORDER BY DateColumn) AS NextSlot
FROM YourTable;
This, unlike SQL_M's answer, means you don't need to do 2/3 scans of the table.
declare #wantedDate datetime = '2018-09-04 12:27:16.570'
select top 1 * from #t tP -- Top of each min and max time
join #T tn
on tp.ID = tn.ID
and tp.DateT <= #wantedDate -- all previous times
and tn.DateT >= #wantedDate -- all next times
order by tp.ID, tp.DateT desc, tn.DateT
Revert me, if query needs updates.
I have a query that works perfectly fine, by summing the values for a day.
SELECT CAST(fldDateTime AS DATE) AS DayValue, SUM(fldValue) AS Val
FROM [dbo].[Data.tblMeterData]
GROUP BY CAST(fldDateTime AS DATE)
ORDER BY DayValue
The problem I have is that I needed to have the data greater than midnight and up until midnight of the next day. To test I can do this I tested the following code, which helped me deal with the first lot of values that do not have a full day.
DECLARE #a DATETIME
DECLARE #b DATETIME
SET #a = CAST('2016-03-21 00:01:00' AS DATETIME)
SET #b = DATEADD(SECOND,-86399 ,#a)
SELECT #a AS a, #b AS b
SELECT CAST(fldDateTime AS DATE) AS DayValue, SUM(fldValue) AS Val
FROM [dbo].[Data.tblMeterData]
WHERE fldDateTime BETWEEN #b AND #a
GROUP BY CAST(fldDateTime AS DATE)
ORDER BY DayValue
The problem with this is that I do not want to loop through dates, as I have thousands of ID's that I need to process in this way.I cannot include midnight for the first date as the first data is recorded after midnight and the final daily reading is at midnight the following day e.g.
Date > '2016-03-20 00:00:00' AND <= 2016-03-21 00:00:00
How can I do what I need to do the following:
Find the first date for an ID and move up to the first midnight of the following day and repeat this for all following days.
Sum these values so that they are greater than midnight and up to midnight of the next day.
My understanding of the requirements is you are looking for the sum of a value group by meter ID and date, but for each date we also want to include the next day's values. This means each value would count in the sum for its day and the previous day.
Code:
--generate test data
declare #tblMeterData table (
[ID] [int] IDENTITY(1,1) NOT NULL,
[tblMeterData_Id] [int] NOT NULL,
[fldDateTime] [datetime] NOT NULL,
[fldValue] [decimal](18, 2) NULL,
[fldBatchId] [uniqueidentifier] NOT NULL);
insert #tblMeterData (tblMeterData_Id, fldDateTime, fldValue, fldBatchId) values
(18, '2016-12-19 23:59:59', 1.0, newid()),
(18, '2016-12-20 00:00:00', 2.0, newid()),
(18, '2016-12-20 00:30:00', 3.0, newid()),
(18, '2016-12-20 01:00:00', 4.0, newid()),
(18, '2016-12-20 01:30:00', 5.0, newid()),
(18, '2016-12-21 00:00:00', 6.0, newid()),
(18, '2016-12-21 00:30:00', 7.0, newid()),
(18, '2016-12-22 00:00:00', 8.0, newid()),
(18, '2016-12-23 00:00:00', 9.0, newid()),
(19, '2016-12-20 00:00:00', 10.0, newid());
--select * from #tblMeterData order by ID;
--main query
with cte as (
--0:00:00 reports on previous day
select *, cast(dateadd(S, -1, fldDateTime) as date) group_date
from #tblMeterData
union all
--duplicate all records to also group on previous day
select *, cast(dateadd(D, -1, dateadd(S, -1, fldDateTime)) as date) group_date
from #tblMeterData
)
select tblMeterData_Id, group_date, sum(fldValue) sum_value
from cte
group by tblMeterData_Id, group_date
order by tblMeterData_Id, group_date;
Results:
tblMeterData_Id group_date sum_value
18 2016-12-18 3.00
18 2016-12-19 21.00
18 2016-12-20 33.00
18 2016-12-21 24.00
18 2016-12-22 9.00
19 2016-12-18 10.00
19 2016-12-19 10.00
The answer is as follows:
WITH cte AS (
SELECT *, CAST(DATEADD(S, -1, fldDateTime) AS DATE) group_date
FROM [dbo].[Data.tblMeterData]
)
SELECT tblMeterData_Id, group_date, SUM(fldValue) sum_value
FROM cte
GROUP BY tblMeterData_Id, group_date
ORDER BY tblMeterData_Id, group_date;
It works perfectly.