Calculate employee shift based on 1st login of the day - sql

I want to calculate employees shift based on his system login time ,i am able to calculate shifts based on login times .now i want to calculate shift only for 1st login entry for given day if there are multiple logins for same day.Please help how i can do this .
**
SQL Query :
WITH shiftalloence AS
(
SELECT (timesheet.start_time_server) ,
users.first_name + ' ' + users.last_name AS employee_name ,
CASE
WHEN Dateadd(d, -Datediff(d, 0, dbo.[timesheet].start_time_server), dbo.[timesheet].start_time_server) BETWEEN '05:30:00' AND '08:00:00' THEN 'Morning'
WHEN Dateadd(d, -Datediff(d, 0, dbo.[timesheet].start_time_server), dbo.[timesheet].start_time_server) BETWEEN '08:01:00' AND '11:00:00' THEN 'General'
WHEN Dateadd(d, -Datediff(d, 0, dbo.[timesheet].start_time_server), dbo.[timesheet].start_time_server) BETWEEN '11:05:00' AND '18:00:00' THEN 'Evening'
ELSE '0'
END AS shift
FROM timesheet
JOIN users
ON timesheet.user_id=users.user_id
WHERE Month(start_time_server)=Month(Getdate())
AND Datename(weekday,timesheet.start_time_server) IN 'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday')
AND
start_time_server NOT IN
(
SELECT timesheet.start_time_server
FROM timesheet
JOIN leaves
ON timesheet.user_id=leaves.user_id
WHERE CONVERT(varchar(10),timesheet.start_time_server,111) =CONVERT(varchar(10),leaves.start_time_server,111)
AND task_type_name IN ('leave',
'Half Day') )), tt AS
(
SELECT shiftalloence.employee_name ,
count(
CASE
WHEN shift = 'Morning' THEN 1
ELSE NULL
END) AS 'Morning',
count(
CASE
WHEN shift = 'General' THEN 1
ELSE NULL
END) AS 'General' ,
count(
CASE
WHEN shift = 'Evening' THEN 1
ELSE NULL
END) AS 'Evening'
FROM shiftalloence
GROUP BY employee_name )SELECT *
FROM tt
**

Could you just simply distinct select and order by ASC or DESC depending on the dates? This would allow you to show just the shift/day/id you're wanting and showing you the first time they logged in because you're ordered it in the way you're wanting.
This works because if you get back 3 rows and order by the datetime asc/desc you can also simply do select top(1). It'll grab just the top record in that list for that shift/day/person.
Small example of what I am saying:
SELECT TOP 1 * FROM Employees
order by datetime asc;
Something like that.

Related

How to solve a nested aggregate function in SQL?

I'm trying to use a nested aggregate function. I know that SQL does not support it, but I really need to do something like the below query. Basically, I want to count the number of users for each day. But I want to only count the users that haven't completed an order within a 15 days window (relative to a specific day) and that have completed any order within a 30 days window (relative to a specific day). I already know that it is not possible to solve this problem using a regular subquery (it does not allow to change subquery values for each date). The "id" and the "state" attributes are related to the orders. Also, I'm using Fivetran with Snowflake.
SELECT
db.created_at::date as Date,
count(case when
(count(case when (db.state = 'finished')
and (db.created_at::date between dateadd(day,-15,Date) and dateadd(day,-1,Date)) then db.id end)
= 0) and
(count(case when (db.state = 'finished')
and (db.created_at::date between dateadd(day,-30,Date) and dateadd(day,-16,Date)) then db.id end)
> 0) then db.user end)
FROM
data_base as db
WHERE
db.created_at::date between '2020-01-01' and dateadd(day,-1,current_date)
GROUP BY Date
In other words, I want to transform the below query in a way that the "current_date" changes for each date.
WITH completed_15_days_before AS (
select
db.user as User,
count(case when db.state = 'finished' then db.id end) as Completed
from
data_base as db
where
db.created_at::date between dateadd(day,-15,current_date) and dateadd(day,-1,current_date)
group by User
),
completed_16_days_before AS (
select
db.user as User,
count(case when db.state = 'finished' then db.id end) as Completed
from
data_base as db
where
db.created_at::date between dateadd(day,-30,current_date) and dateadd(day,-16,current_date)
group by User
)
SELECT
date(db.created_at) as Date,
count(distinct case when comp_15.completadas = 0 and comp_16.completadas > 0 then comp_15.user end) as "Total Users Churn",
count(distinct case when comp_15.completadas > 0 then comp_15.user end) as "Total Users Active",
week(Date) as Week
FROM
data_base as db
left join completadas_15_days_before as comp_15 on comp_15.user = db.user
left join completadas_16_days_before as comp_16 on comp_16.user = db.user
WHERE
db.created_at::date between '2020-01-01' and dateadd(day,-1,current_date)
GROUP BY Date
Does anyone have a clue on how to solve this puzzle? Thank you very much!
The following should give you roughly what you want - difficult to test without sample data but should be a good enough starting point for you to then amend it to give you exactly what you want.
I've commented to the code to hopefully explain what each section is doing.
-- set parameter for the first date you want to generate the resultset for
set start_date = TO_DATE('2020-01-01','YYYY-MM-DD');
-- calculate the number of days between the start_date and the current date
set num_days = (Select datediff(day, $start_date , current_date()+1));
--generate a list of all the dates from the start date to the current date
-- i.e. every date that needs to appear in the resultset
WITH date_list as (
select
dateadd(
day,
'-' || row_number() over (order by null),
dateadd(day, '+1', current_date())
) as date_item
from table (generator(rowcount => ($num_days)))
)
--Create a list of all the orders that are in scope
-- i.e. 30 days before the start_date up to the current date
-- amend WHERE clause to in/exclude records as appropriate
,order_list as (
SELECT created_at, rt_id
from data_base
where created_at between dateadd(day,-30,$start_date) and current_date()
and state = 'finished'
)
SELECT dl.date_item
,COUNT (DISTINCT ol30.RT_ID) AS USER_COUNT
,COUNT (ol30.RT_ID) as ORDER_COUNT
FROM date_list dl
-- get all orders between -30 and -16 days of each date in date_list
left outer join order_list ol30 on ol30.created_at between dateadd(day,-30,dl.date_item) and dateadd(day,-16,dl.date_item)
-- exclude records that have the same RT_ID as in the ol30 dataset but have a date between 0 amd -15 of the date in date_list
WHERE NOT EXISTS (SELECT ol15.RT_ID
FROM order_list ol15
WHERE ol30.RT_ID = ol15.RT_ID
AND ol15.created_at between dateadd(day,-15,dl.date_item) and dl.date_item)
GROUP BY dl.date_item
ORDER BY dl.date_item;

How to calculate prior year sales data in SQL

I'm attempting to build a table summarizing sales data by week. In it, I'm trying to have one of the adjacent columns show the sales figures for the same fiscal week during the prior year (which due to my organizations fiscal calendar, had a 53rd week last year). I also have need to compare (Comp Units/Comp Sales) to a period 52 weeks ago which is an entirely different fiscal week (Think Week 9 of 2019 comparing to Week 10 2018).
I've tried using both unions and full outer joins, but given the way the way my data is, they're inefficient (Because this is weekly data, unions ended up being inefficient as I needed to leave the date information out of the initial query, then updating columns in my table to reflect the week the data is for. This is obviously rife with opportunity for error, but also time consuming to do 105 times), or just didn't work (attempting a full outer join was returning the wrong answers for all columns). I've also tried utilizing CTEs as well, and that's not working for me either. I'm currently trying a CASE Statement, but that's also returning a null value. I'm not quite sure where to go next
#STANDARDSQL
SELECT
DTL.SKU_NBR AS SKU_NBR
, SLS.STR_NBR AS STR_NBR
, CONCAT(TRIM(CAST(SKU_HIER.SKU_NBR AS STRING)), ' ', '-', ' ', TRIM(SKU_HIER.SKU_DESC)) AS SKU
, CONCAT(TRIM(CAST(SKU_HIER.EXT_SUB_CLASS_NBR AS STRING)), ' ', '-', ' ', TRIM(SKU_HIER.SUB_CLASS_DESC)) AS SUB_CLASS
, CONCAT(TRIM(CAST(SKU_HIER.EXT_SUB_SC_NBR AS STRING)), ' ', '-', ' ', TRIM(SKU_HIER.SUB_SC_DESC)) AS SUB_SUB_CLASS
, LOCATION.MKT_NM AS MARKET_NAME
, LOCATION.RGN_NM AS REGION_NAME
, LOCATION.DIV_NM AS DIVISION_NAME
, LOCATION.DIV_NBR AS DIVISION_NUMBER
, LOCATION.RGN_NBR AS REGION_NUMBER
, LOCATION.MKT_NBR AS MARKET_NUMBER
, COMP.STR_COMP_IND AS COMP_IND
, COMP.PY_STR_COMP_IND AS PRIOR_COMP_IND
, CALENDAR.FSCL_WK_DESC AS FISCAL_WEEK
, CALENDAR.FSCL_PRD_DESC AS FISCAL_PERIOD
, CALENDAR.FSCL_WK_END_DT AS END_DATE
, CALENDAR.FSCL_WK_BGN_DT AS BEGIN_DATE
, CALENDAR.FSCL_YR AS FISCAL_YEAR_NBR
, CALENDAR.FSCL_WK_NBR AS WEEK_NUMBER
, CALENDAR.FSCL_YR_WK_KEY_VAL AS FISCAL_KEY
, CALENDAR.LY_FYR_WK_KEY_VAL AS LY_FISCAL_KEY
, SUM(COALESCE(DTL.UNT_SLS,0)) AS UNITS
, SUM(COALESCE(DTL.EXT_RETL_AMT,0) + COALESCE(DTL.TOT_GDISC_DTL_AMT,0))
AS SALES
, SUM(CASE
WHEN 1=1 THEN (COALESCE(DTL.EXT_RETL_AMT,0) + COALESCE(DTL.TOT_GDISC_DTL_AMT,0)) * COMP.STR_COMP_IND
ELSE 0 END) AS COMP_SALES
, SUM(CASE
WHEN 1=1 THEN (COALESCE(DTL.UNT_SLS,0)) * COMP.STR_COMP_IND
ELSE 0 END) AS COMP_UNITS
, SUM(CASE
WHEN 1=1 AND SLS.SLS_DT = DATE_SUB(SLS.SLS_DT, INTERVAL 364 DAY)
THEN (COALESCE(DTL.EXT_RETL_AMT,0) +
COALESCE(DTL.TOT_GDISC_DTL_AMT,0)) * COMP.PY_STR_COMP_IND
ELSE NULL END)
AS LY_COMP_SALES
, SUM(CASE
WHEN 1=1 AND SLS.SLS_DT = DATE_SUB(SLS.SLS_DT, INTERVAL 364 DAY)
THEN (COALESCE(DTL.UNT_SLS,0)) * COMP.PY_STR_COMP_IND
ELSE NULL END)
AS LY_COMP_UNITS
, SUM(CASE
WHEN SLS.SLS_DT = DATE_SUB(SLS.SLS_DT, INTERVAL 371 DAY)
THEN (COALESCE(DTL.EXT_RETL_AMT,0) +
COALESCE(DTL.TOT_GDISC_DTL_AMT,0))
ELSE NULL END)
AS LY_SALES
, SUM(CASE
WHEN SLS.SLS_DT = DATE_SUB(SLS.SLS_DT, INTERVAL 371 DAY)
THEN (COALESCE(DTL.UNT_SLS,0))
ELSE NULL END)
AS LY_UNITS
FROM `pr-edw-views.SLS.POS_SLS_TRANS_DTL` AS SLS
INNER JOIN
UNNEST (SLS.DTL) AS DTL
JOIN `pr-edw-views.SHARED.MVNDR_HIER` AS MVNDR
ON DTL.DERIV_MVNDR.MVNDR_NBR = MVNDR.MVNDR_NBR
JOIN `pr-edw-views.SHARED.SKU_HIER_FD` AS SKU_HIER
ON SKU_HIER.SKU_NBR = DTL.SKU_NBR
AND SKU_HIER.SKU_CRT_DT = DTL.SKU_CRT_DT
JOIN `pr-edw-views.SHARED.LOC_HIER_FD` AS LOCATION
ON LOCATION.LOC_NBR = SLS.STR_NBR
JOIN `pr-edw-views.SHARED.CAL_PRD_HIER_FD` AS CALENDAR
ON CALENDAR.CAL_DT = SLS_DT
JOIN `pr-edw-views.SLS.STR_COMP_DAY` AS COMP
ON COMP.CAL_DT = CALENDAR.CAL_DT
AND COMP.STR_NBR = SLS.STR_NBR
WHERE CALENDAR.FSCL_WK_END_DT BETWEEN '2018-01-29' AND '2019-04-07'
AND SLS.SLS_DT BETWEEN '2018-01-29' AND '2019-04-07'
AND POS_TRANS_TYP_CD in ('S', 'R')
AND SKU_HIER.EXT_CLASS_NBR = '025-004'
AND MVNDR.MVNDR_NBR IN (74798, 60002238, 73059, 206820, 76009, 40263, 12879, 76722, 10830, 206823, 87752, 60052261, 70401, 51415, 51414)
AND SKU_HIER.LATEST_SKU_CRT_DT_FLG = TRUE
GROUP BY
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
I'm currently getting null values in my LY_SALES, LY_UNITS, LY_COMP_SALES and LY_COMP_UNITS columns, though I know there should have been locations with sales of those items from the same period the previous year. What I'm trying to get to is having those prior year values showing up along side the current year values. Any help would be hugely appreciated!
Thanks!
Such a condition can never be fulfilled : SLS.SLS_DT = DATE_SUB(SLS.SLS_DT, INTERVAL 371 DAY). Simply because a SLS_DT is not equal to SLS_DT-371.
You can pre-aggregate the table in a CTE (adding SLS_DT to the group by columns) and then replace the CASE with a join to the pre-aggregated table. Aim at something like this: and it will become something like (notice - no SUM in the case):
CASE WHEN AGGSLS.SLS_DT = DATE_SUB(SLS.SLS_DT, INTERVAL 371 DAY)
THEN (COALESCE(AGGSLS.SUM_EXT_RETL_AMT,0) +
COALESCE(AGGSLS.SUM_TOT_GDISC_DTL_AMT,0))
ELSE NULL END
Two things:
1) WHEN 1=1 can be expressed simply as WHEN TRUE, this way it is easier to move statements around without breaking the AND/OR chaining
2) to get the last year's sales. You can either omit the year from the final query and limit the output with a where clause or create a smaller table that has the sales this year, sales last year per week.
In my humble opinion sales last year for weeknum is the best option, as you can use it elsewhere. But it's pretty similar to what you wr
It would look something like:
SELECT CALENDAR.FSCL_WK_DESC as week_num,
sum(case when year = year(current_date()) then (COALESCE(DTL.UNT_SLS,0)) * COMP.STR_COMP_IND else 0 end) as this_year
sum(case when year = year(current_date())-1 then (COALESCE(DTL.UNT_SLS,0)) * COMP.STR_COMP_IND else 0 end) as last_year
And then you join back to the original table using week_num
Hope you find it useful
Cheers!

SQL Match employee intime (punch time) with employee shift

I have a Stored Procedure that retrieves employee daily summary intime - outtime:
SELECT ads.attendancesumid,
ads.employeeid,
ads.date,
ads.day, -- month day number
ads.intime,
ads.outtime
--employee shift intime and outtime
ss.intime,
ss.outtime
FROM employee_attendance_daily_summary ads
JOIN employee emp
ON emp.employeeid = ads.employeeid
JOIN setup_shift ss
ON ss.shiftcode = emp.shiftcode
AND DATEPART(dw, ads.date) = ss.day
WHERE ads.employeeid = 4 -- just to filter one employee
The result of the query is something like this:
Each day is repeated 3 times because table setup_shift (employee shifts) has:
Monday to Sunday for 3 different shift types: DAY, AFTERNOON and NIGHT.
Here is the same info but with the shift type column:
What I need is to ONLY get 1 row per day but with the closest employee shift depending on the intime and outtime.
So the desire result should looks like this:
Any clue on how to do this? Appreciate it in advance.
I have also these case where intime is 00:00:00 but outtime has a value:
UPDATE:
HERE IS THE SQL FIDDLE
http://sqlfiddle.com/#!6/791cb/7
select ads.attendancesumid,
ads.employeeid,
ads.date,
ads.day,
ads.intime,
ads.outtime,
ss.intime,
ss.outtime
from employee_attendance_daily_summary ads
join employee emp
on emp.employeeid = ads.employeeid
join setup_shift ss
on ss.shiftcode = emp.shiftcode
and datepart(dw, ads.date) = ss.day
where ads.employeeid = 4
and ((abs(datediff(hh,
cast(ads.intime as datetime),
cast(ss.intime as datetime))) between 0 and 2) or
(ads.intime = '00:00:00' and
ss.intime =
(select min(x.intime)
from setup_shift x
where x.shiftcode = ss.shiftcode
and x.intime > (select min(y.intime)
from setup_shift y
where y.shiftcode = x.shiftcode))))
This would be much easier if the times were in seconds after midnight, rather than in a time, datetime, or string format. You can convert them using the formula:
select datepart(hour, intime) * 3600 + datepart(minute, intime) * 60 + datepart(second, intime)
(Part of this is just my own discomfort with all the nested functions needed to handle other data types.)
So, let me assume that you have a series of similar columns measured in seconds. You can then approach this problem by taking the overlap with each shift and choosing the shift with the largest overlap.
with t as (
<your query here>
),
ts as (
select t.*,
(datepart(hour, ads.intime) * 3600 + datepart(minute, ads.intime) * 60 +
datepart(second, ads.intime)
) as e_intimes,
. . .
from t
),
tss as (
select ts.*,
(case when e_intimes >= s_outtimes then 0
when e_outtimes <= s_inttimes then 0
else (case when e_outtimes < s_outtimes then e_outtimes else s_outtimes end) -
(case when e_intimes > s_intimes then e_intimes else s_intimes end)
end) as overlap
from ts
)
select ts.*
from (select ts.*,
row_number() over (partition by employeeid, date
order by overlap desc
) as seqnum
from ts
) ts
where seqnum = 1;
Try this man,I just take the minimum time difference of the each set datediff(mi,intime,shift_intime)
Select * from
(select
row_number() over(partition by employeeid
order by datediff(mi,intime,shift_intime) asc) as id,
attendance,employeeid,date,day,intime,outime,shiftintime,shiftoutime from table
)
where id=1

Select first and last record each day

I have a table with an engineerID, DateTimeCreated as DateTime, JobID and AuditTypeID
I need a query shows first (engineerID, JobID with AuditTypeID 1) and last (engineerID, JobID with AuditTypeID 2) on each row of the query.
SELECT TOP (100) PERCENT
dbo.AuditTrail.EngineerId,
dbo.AuditTrail.AuditTypeId,
dbo.Engineers.Name,
dbo.Engineers.EngineerTypeCode,
dbo.AuditTrail.JobId,
CAST(dbo.AuditTrail.DateTimeCreated AS Date) AS _Date
FROM
dbo.AuditTrail
INNER JOIN
dbo.Engineers
ON dbo.AuditTrail.EngineerId = dbo.Engineers.EngineerId
WHERE
(dbo.AuditTrail.AuditTypeId = 1) AND
(dbo.Engineers.EngineerTypeCode = 'p') AND
(dbo.Engineers.EngineerTypeCode = 'p') AND
(DATEPART(mm, dbo.AuditTrail.DateTimeCreated) = 6) AND
(DATEPART(YYYY, dbo.AuditTrail.DateTimeCreated) = 2014)
group by
AuditTrail.engineerID,
JobID,
AuditTypeId,
Engineers.name,
Engineers.EngineerTypeCode,
CAST(dbo.AuditTrail.DateTimeCreated AS Date)
ORDER BY
dbo.AuditTrail.EngineerID DESC
for the first part of my query. Unfortunatly I cannot see to select the first record for each day
Any help will be greatly appreciated
First just get the data you need, including the create date. Then grouping that data by date, select the min of each day. Finally, join the two sets, selecting only the minimum of each day -- that is, the first occurrence of each day.
with
AllMonth( EngineerId, AuditTypeId, Name, EngineerTypeCode, JobId, DateTimeCreated )as(
SELECT TOP (100) PERCENT
a.EngineerId,
a.AuditTypeId,
e.Name,
e.EngineerTypeCode,
a.JobId,
a.DateTimeCreated
FROM dbo.AuditTrail a
JOIN dbo.Engineers e
ON e.EngineerId = a.EngineerId
AND e.EngineerTypeCode = a.EngineerTypeCode
WHERE
a.AuditTypeId = 1
AND a.EngineerTypeCode = 'p'
AND a.DateTimeCreated >= DateAdd( mm, DateDiff( mm, 0, GetDate()), 0)
AND a.DateTimeCreated < DateAdd( mm, DateDiff( mm, 0, GetDate()) + 1, 0)
),
FirstByDay( MinDate )as(
select Min( DateTimeCreated )
from AllMonth
group by cast( DateTimeCreated AS Date )
)
select *
from AllMonth a
join FirstByDay f
on f.MinDate = a.DateTimeCreated
ORDER BY a.EngineerID DESC;
To get the last item of each day, just add a max to FirstByDay and add to the join. Work it into one long row if you really want to.
Btw, didn't I hear a few years back that the later versions of MSSQL ignored top (100) percent? I don't work with it much these days, and my memory is...well, just...somewhere around here...

Calculating number of days between Admission Date and today

I found the following in Stackoverflow and have been working with it to find the number of Patient Days each month. It works very well if I have both the Admit and Discharge dates.
I can't figure out how to edit it to calculate the Patient days when the discharge date has not been completed... the patient is still in the hospital. If feels like I should use Coalesce or ISNULL to find the records where the Discharge Date is NULL, but I'm not a programmer and would appreciate your help.
WITH Mos AS (
SELECT
D.ED_ADMIT_DATE,
D.ED_DISCHARGE_DATE,
Number,
DateAdd(Month, Number, D.ED_ADMIT_DATE - Day(D.ED_ADMIT_DATE) + 1) MoDate
FROM
cases_cstm D
INNER JOIN master.dbo.spt_values V ON V.Number <= DateDiff(Month, D.ED_ADMIT_DATE, D.ED_DISCHARGE_DATE)
WHERE
V.Type = 'P'), Dys AS (
SELECT
MoDate,
DateDiff(
Day,
CASE WHEN Number = 0 THEN ED_ADMIT_DATE ELSE MoDate END,
CASE WHEN Number = DateDiff(Month, ED_ADMIT_DATE, ED_DISCHARGE_DATE) THEN ED_DISCHARGE_DATE ELSE DateAdd(Month, 1, MoDate) -1
END
) + 1 Cnt
FROM Mos)
SELECT Year(MoDate) Yr,
Coalesce(DateName(Month, MoDate), 'Total') Mo,
Convert(varchar(11), Sum(Cnt)) + ' day' + CASE WHEN Sum(Cnt) = 1 THEN '' ELSE 's' END Descr
FROM Dys
GROUP BY MoDate
WITH ROLLUP
ORDER BY
Grouping(MoDate),
MoDate;
You could replace each occurance of ED_DISCHARGE_DATE with:
IsNull(ED_DISCHARGE_DATE,getdate())
This uses the current time whenever the discharge date is unavailable.