Displaying student absent dates - sql

Here is the table I want to display:
tblAttendance table
CustomerId
Id
Attendence
Date
and
tblStudent
CustomerId
Name
Now I want to search by from to date and want absent date.
How can I achieve this?
I tried below code:
ALTER PROCEDURE spExceptDate
AS
declare #StartDate DATE, #EndDate DATE
set #StartDate = '2020-02-15';
set #EndDate = '2020-02-25';
BEGIN
SELECT CustomerId,FirstName+' '+LastName,Date,Attendance
FROM
[dbo].[tblAttendance] att
LEFT JOIN
[dbo].[tblStudent] st
ON att.CustomerId = st.Code
EXCEPT
SELECT CustomerId,FirstName+' '+LastName,Date,Attendance
FROM
[dbo].[tblAttendance] att
LEFT JOIN
[dbo].[tblStudent] st
ON att.CustomerId = st.Code
where att.Date>='2020-02-15' and att.Date<='2020-02-25'
END
GO
i want date for which student absend

Basically what you need is list of possible dates between From and To
DECLARE #StartDate DATE = '2020-02-15',
#EndDate DATE = '2020-02-25' ;
--Create a CTE to get all dates between from and to (you should filter holidays and weekends)
WITH SchoolCalendar (WorkingDay)
AS (SELECT #StartDate
UNION ALL
SELECT DATEADD(DAY, 1, WorkingDay)
FROM SchoolCalendar
WHERE WorkingDay< #EndDate
)
--Use the CTE to determine the Absense records
SELECT st.Code CustomerId, st.FirstName+' '+st.LastName Name,st.WorkingDay Date, COALESCE(Attendance,'A') Attendance
FROM (SELECT * from SchoolCalendar, tblStudent) st
LEFT JOIN [dbo].[tblAttendance] att ON att.Date = st.WorkingDay AND att.CustomerId = st.Code
WHERE st.WorkingDay>=#StartDate and st.WorkingDay<=#EndDate
ORDER BY st.Code, st.WorkingDay

You may need only one query if you want to fetch only absent student names from the given date range
SELECT CustomerId,FirstName+' '+LastName,Date,Attendance
FROM [dbo].[tblAttendance] att
LEFT JOIN [dbo].[tblStudent] st ON att.CustomerId = st.Code
WHERE att.Date>='2020-02-15' and att.Date<='2020-02-25' AND att.Attendance <> 'P'
^^^^^^^^^^^^^^^^^^^^

Related

SQL Help - Finding order before current records timestamp

Forgive me if this question is confusing:
I have a patient level data with records created by Nurses and doctors.
For each patient the doctor inputs a Target daily value for each patient. The doctor may modify this target multiple time
During the course of the day the nurse enters a value with the goal of total value being equal to the target set by the doctor.
I'm trying to write a query that
1. For each patient return every value entered by the nurse and look for the most recent doctor order made prior to the nurse's recording his/her value.
The following is the SQL I've been working on
--*********************************************-***************************************************
-- As of 10/11/2018 - Runtime = 1:40 min
DECLARE #StartDate DATETIME, #EndDate DATETIME
-- Change the following 2 variables to select a different date range for patient attribution
SET #StartDate = '2018-03-01 00:00:00.000'
SET #EndDate = '2018-10-31 00:00:00.000'
--*************************************************
Select Distinct
results.LOCATION_ABBR
,results.PROTOCOL_NAME
,results.PROV_NAME
,results.MRN
,results.OSU_CSN
,results.PAT_NAME
,results.AGE_YEARS,
results.gender,
results.Race,
results.HOSP_ADMSN_TIME,
results.HOSP_DISCH_TIME,
results.OrderDate,
results.TF_ORDERED
-- ,results.TF_Datetime
,results.TF_Date
,results.TF_Time
,results.TF_Daily
,SUM(results.TF_Daily) OVER (partition by results.OSU_CSN, results.TF_Date) AS Daily_TF_Total_v2
,results.Total_Required
,results.Daily_TF_Total
-- ,results.First_TF
-- ,results.Last_TF
From (
Select Distinct
OSU_Data.LOCATION_ABBR
,OSU_Data.PROTOCOL_NAME
,OSU_Data.PROV_NAME
,OSU_Data.MRN
,OSU_Data.OSU_CSN
,OSU_Data.PAT_NAME
,OSU_Data.AGE_YEARS,
OSU_Data.gender,
Race.PatRace as Race,
OSU_Data.HOSP_ADMSN_TIME,
OSU_Data.HOSP_DISCH_TIME,
OSU_Data.DATE_USED,
OSU_Data.OrderDate,
OSU_Data.TF_ORDERED
,TF_Daily.TF_Datetime
,TF_Daily.TF_Date
,TF_Daily.TF_Time
,TF_Daily.TF_Daily
,Max(cast(OSU_Data.TF_ORDERED as numeric(9,2))) OVER (partition by OSU_Data.OSU_CSN, OSU_Data.DATE_USED) AS Total_Required
,SUM(TF_Daily.TF_Daily) OVER (partition by OSU_Data.OSU_CSN, TF_Daily.TF_Date) AS Daily_TF_Total
,row_number() over (partition by OSU_Data.OSU_CSN order by TF_Daily.TF_Datetime) First_TF
,row_number() over (partition by OSU_Data.OSU_CSN order by TF_Daily.TF_Datetime desc) Last_TF
-- ,LAG(OSU_Data.OrderDate) OVER (ORDER BY OSU_Data.OSU_CSN,OSU_Data.OrderDate,TF_Datetime) PreviousTFOrderDT
-- ,LEAD(OSU_Data.OrderDate) OVER (ORDER BY OSU_Data.OSU_CSN,OSU_Data.OrderDate,TF_Datetime) NextTFOrderDT
From
(
Select
preOSU_Data.Orderid
,preOSU_Data.PROTOCOL_ID
,preOSU_Data.PROTOCOL_NAME
,preOSU_Data.LOCATION_ABBR
,preOSU_Data.PROV_NAME
,preOSU_Data.PAT_ID
,preOSU_Data.MRN
,preOSU_Data.OSU_CSN
,preOSU_Data.PAT_NAME
,preOSU_Data.AGE_YEARS
,preOSU_Data.gender
,Race.PatRace
,preOSU_Data.OrderDate
-- ,preOSU_Data.TF_GoalHr
,preOSU_Data.HOSP_ADMSN_TIME
,preOSU_Data.HOSP_DISCH_TIME
,preOSU_Data."YEAR USED"
,preOSU_Data."MONTH USED"
,preOSU_Data.DATE_USED
,preOSU_Data."For Sorting"
,preOSU_Data.TF_ORDERED
From
(Select Distinct
ORDERS.ORDER_ID as Orderid
,CL_PRL_SS.PROTOCOL_ID
,CL_PRL_SS.PROTOCOL_NAME
,CLARITY_LOC.LOCATION_ABBR
,CLARITY_SER.PROV_NAME
,PAT_ENC_HSP.PAT_ID
,V_PAT_FACT.PAT_MRN_ID as MRN
,PAT_ENC_HSP.PAT_ENC_CSN_ID as OSU_CSN
,V_PAT_FACT.PAT_NAME
,V_PAT_FACT.AGE_YEARS
,V_PAT_FACT.SEX_NAME as gender
,ORDER_INST as OrderDate
,DATEPART(HOUR, ORDER_INST) as TF_GoalHr
,PAT_ENC_HSP.HOSP_ADMSN_TIME
,PAT_ENC_HSP.HOSP_DISCH_TIME
,YEAR(ORDER_INST) "YEAR USED"
,DATENAME(MONTH, ORDER_INST) "MONTH USED"
,DATEFROMPARTS(YEAR(ORDER_INST),MONTH(ORDER_INST),DAY(ORDER_INST)) DATE_USED
,DATEFROMPARTS(YEAR(ORDER_INST),MONTH(ORDER_INST),1) "For Sorting"
,ORD_SPEC_QUEST.ORD_QUEST_RESP as TF_ORDERED
,Max(ORDER_INST) OVER (partition by PAT_ENC_HSP.PAT_ENC_CSN_ID, DATEFROMPARTS(YEAR(ORDER_INST),MONTH(ORDER_INST),DAY(ORDER_INST))) AS Last_DailyTFGoalTimestamp
From
PAT_ENC_HSP
,ORDERS
,ORDER_PROC
,ORDER_SMARTSET
,CL_PRL_SS
,ZC_PAT_SERVICE
,ORD_SPEC_QUEST
,CL_QQUEST
,V_PAT_FACT
,CLARITY_SER
,CLARITY_ADT
,CLARITY_DEP
,CLARITY_LOC
,ZC_PATIENT_RACE
Where
(PAT_ENC_HSP.PAT_ENC_CSN_ID = ORDER_SMARTSET.PAT_ENC_CSN_ID
AND ORDERS.ORDER_ID = ORDER_SMARTSET.ORDER_ID
AND ORDER_PROC.ORDER_PROC_ID=ORDER_SMARTSET.ORDER_ID
AND ORDER_SMARTSET.SS_PRL_ID=CL_PRL_SS.PROTOCOL_ID
AND ORDERS.ORDER_ID = ORD_SPEC_QUEST.ORDER_ID
AND ORD_SPEC_QUEST.ORD_QUEST_ID = CL_QQUEST.QUEST_ID)
and PAT_ENC_HSP.PAT_ENC_CSN_ID = CLARITY_ADT.PAT_ENC_CSN_ID
and CLARITY_ADT.DEPARTMENT_ID = CLARITY_DEP.DEPARTMENT_ID
and CLARITY_DEP.REV_LOC_ID = CLARITY_LOC.LOC_ID
and PAT_ENC_HSP.PAT_ID = V_PAT_FACT.PAT_ID
and ORDER_PROC.AUTHRZING_PROV_ID = CLARITY_SER.PROV_ID
and PAT_ENC_HSP. HOSP_ADMSN_TIME between #StartDate and #EndDate
AND CL_PRL_SS.PROTOCOL_ID = 799
AND CAST(CL_QQUEST.QUEST_ID as varchar(25)) = '101960'
UNION
Select Distinct
ORDERS.ORDER_ID as Orderid
,CL_PRL_SS.PROTOCOL_ID
,CL_PRL_SS.PROTOCOL_NAME
,CLARITY_LOC.LOCATION_ABBR
,CLARITY_SER.PROV_NAME
,PAT_ENC_HSP.PAT_ID
,V_PAT_FACT.PAT_MRN_ID as MRN
,PAT_ENC_HSP.PAT_ENC_CSN_ID as OSU_CSN
,V_PAT_FACT.PAT_NAME
,V_PAT_FACT.AGE_YEARS
,V_PAT_FACT.SEX_NAME as gender
,ORDER_INST as OrderDate
,DATEPART(HOUR, ORDER_INST) as TF_GoalHr
,PAT_ENC_HSP.HOSP_ADMSN_TIME
,PAT_ENC_HSP.HOSP_DISCH_TIME
,YEAR(ORDER_INST) "YEAR USED"
,DATENAME(MONTH, ORDER_INST) "MONTH USED"
,DATEFROMPARTS(YEAR(ORDER_INST),MONTH(ORDER_INST),DAY(ORDER_INST)) DATE_USED
,DATEFROMPARTS(YEAR(ORDER_INST),MONTH(ORDER_INST),1) "For Sorting"
,ORD_SPEC_QUEST.ORD_QUEST_RESP as TF_ORDERED
,Max(ORDER_INST) OVER (partition by PAT_ENC_HSP.PAT_ENC_CSN_ID, DATEFROMPARTS(YEAR(ORDER_INST),MONTH(ORDER_INST),DAY(ORDER_INST))) AS Last_DailyTFGoalTimestamp
From
PAT_ENC_HSP
,ORDERS
,ORDER_PROC
,ORDER_METRICS
,CL_PRL_SS
,ZC_PAT_SERVICE
,ORD_SPEC_QUEST
,CL_QQUEST
,V_PAT_FACT
,CLARITY_SER
,CLARITY_ADT
,CLARITY_DEP
,CLARITY_LOC
Where
(PAT_ENC_HSP.PAT_ENC_CSN_ID = ORDER_METRICS.PAT_ENC_CSN_ID
and ORDERS.ORDER_ID = ORDER_METRICS.ORDER_ID
AND ORDER_PROC.ORDER_PROC_ID=ORDER_METRICS.ORDER_ID
AND ORDER_METRICS.PRL_ORDERSET_ID=CL_PRL_SS.PROTOCOL_ID
AND ORDERS.ORDER_ID = ORD_SPEC_QUEST.ORDER_ID
AND ORD_SPEC_QUEST.ORD_QUEST_ID = CL_QQUEST.QUEST_ID)
and PAT_ENC_HSP.PAT_ENC_CSN_ID = CLARITY_ADT.PAT_ENC_CSN_ID
and CLARITY_ADT.DEPARTMENT_ID = CLARITY_DEP.DEPARTMENT_ID
and CLARITY_DEP.REV_LOC_ID = CLARITY_LOC.LOC_ID
and PAT_ENC_HSP.PAT_ID = V_PAT_FACT.PAT_ID
and ORDER_PROC.AUTHRZING_PROV_ID = CLARITY_SER.PROV_ID
and PAT_ENC_HSP. HOSP_ADMSN_TIME between #StartDate and #EndDate
AND CL_PRL_SS.PROTOCOL_ID = 799
AND CAST(CL_QQUEST.QUEST_ID as varchar(25)) = '101960'
) as preOSU_Data
--********************************************************
-- PATIENT_RACE has multiple lines for some patients
-- Added the following to return Line 1 from Patient_Race
Inner Join(
SELECT DISTINCT
PATIENT_RACE.PAT_ID,
ZC_PATIENT_RACE.NAME as PatRace
FROM PATIENT_RACE, ZC_PATIENT_RACE
WHERE
PATIENT_RACE.PATIENT_RACE_C = ZC_PATIENT_RACE.PATIENT_RACE_C
and LINE = 1
) as Race on preOSU_Data.PAT_ID = Race.PAT_ID
WHERE preOSU_Data.OrderDate = Last_DailyTFGoalTimestamp
-- ORDER BY
-- preOSU_Data.OSU_CSN
-- ,preOSU_Data.OrderDate
) as OSU_Data
--********************************************************
-- PATIENT_RACE has multiple lines for some patients
-- Added the following to return Line 1 from Patient_Race
Inner Join(
SELECT DISTINCT
PATIENT_RACE.PAT_ID,
ZC_PATIENT_RACE.NAME as PatRace
FROM PATIENT_RACE, ZC_PATIENT_RACE
WHERE
PATIENT_RACE.PATIENT_RACE_C = ZC_PATIENT_RACE.PATIENT_RACE_C
and LINE = 1
) as Race on OSU_Data.PAT_ID = Race.PAT_ID
-- WHERE
-- OSU_Data.OrderDate = OSU_Data.Last_DailyTFGoalTimestamp
--
---------------------------------------------------------
-- Daily TF given
---------------------------------------------------------
Inner join
(
select distinct
PATIENT.PAT_NAME,
PAT_ENC.HOSP_DISCHRG_TIME,
pat_enc.pat_enc_csn_id,
IP_FLWSHT_MEAS.recorded_time as TF_Datetime,
CONVERT (DATE,IP_FLWSHT_MEAS.recorded_time) as TF_Date,
Format(IP_FLWSHT_MEAS.recorded_time, 'h:mm tt') as TF_Time,
DATEPART(HOUR, IP_FLWSHT_MEAS.recorded_time) as TF_Hour,
isnull(cast(IP_FLWSHT_MEAS.MEAS_VALUE as float),0) as TF_Daily
FROM
PAT_ENC
LEFT OUTER JOIN PATIENT ON PAT_ENC.PAT_ID = PATIENT.PAT_ID
LEFT OUTER JOIN PAT_ENC_2 ON PAT_ENC.PAT_ENC_CSN_ID = PAT_ENC_2.PAT_ENC_CSN_ID
LEFT OUTER JOIN ZC_PAT_CLASS ON PAT_ENC_2.ADT_PAT_CLASS_C = ZC_PAT_CLASS.ADT_PAT_CLASS_C
LEFT OUTER JOIN IP_FLWSHT_REC ON PAT_ENC.INPATIENT_DATA_ID = IP_FLWSHT_REC.INPATIENT_DATA_ID
LEFT OUTER JOIN IP_FLWSHT_MEAS ON IP_FLWSHT_REC.FSD_ID = IP_FLWSHT_MEAS.FSD_ID
where
PAT_ENC. HOSP_ADMSN_TIME between #StartDate and #EndDate
and IP_FLWSHT_MEAS.FLO_MEAS_ID = '3043040002'
-- and IP_FLWSHT_MEAS.FLT
Group by
PATIENT.PAT_NAME,
PAT_ENC.HOSP_DISCHRG_TIME,
PAT_ENC_2.ADT_PAT_CLASS_C,
pat_enc.pat_enc_csn_id,
IP_FLWSHT_MEAS.recorded_time,
IP_FLWSHT_MEAS.MEAS_VALUE
) as TF_Daily on OSU_Data.OSU_CSN = TF_Daily.pat_enc_csn_id and TF_Datetime > OSU_Data.OrderDate -- and Osu_Data.TF_GoalHr = TF_Daily.TF_Hour
--**********************************************************
--and TF_Daily.TF_Datetime >=
Group by
OSU_Data.OSU_CSN
,OSU_Data.OrderDate
,OSU_Data.DATE_USED
,TF_Datetime
,OSU_Data.LOCATION_ABBR
,OSU_Data.PROTOCOL_NAME
,OSU_Data.PROV_NAME
,OSU_Data.MRN
,OSU_Data.PAT_NAME
,OSU_Data.AGE_YEARS,
OSU_Data.gender,
Race.PatRace,
-- OSU_Data.Race,
OSU_Data.HOSP_ADMSN_TIME,
OSU_Data.HOSP_DISCH_TIME
,TF_Daily.TF_Date
,TF_Daily.TF_Daily
--,TF_Daily2
,TF_Daily.TF_Time
,TF_Daily.TF_Hour
,OSU_Data.TF_ORDERED
) as results
-- Exclusions and Clean-up
Where
results.Total_Required >= 800
and results.Daily_TF_Total > 0

SQL: How to get min date associated with patient value

Trying to get earliest date associated with each PatientID for this period of time.
Current SQL returns multiple visits/documents within the time period for a patient I need to show only earliest date for patient tied to particular provider in date range.
Multiple Dates for PatientID
USE EHR
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
DECLARE #PROV NVARCHAR (255) ='KCOOPER0'
DECLARE #START_DATE DATETIME = '2017-09-18 00:00:00.000'
DECLARE #END_DATE DATETIME = '2017-12-17 23:59:59.999'
--DECLARE #START_DATE DATETIME = '2017-10-02 00:00:00.000'
--DECLARE #END_DATE DATETIME = '2017-12-31 23:59:59.999'
SELECT DISTINCT
PS.ID AS AppointmentID
, CL.Code AS PatientID
-- , SU.NameFirst AS PROVFNAME
-- , SU.NameLast AS PROVLNAME
-- , SU.NameSuffix AS PROVSUFFIX
, PS.ProviderId
, PS.ScheduledDateTime AS AppointmentDT
, PS.Duration
, PS.[TYPE] AS TypeDescription
, PS.IsActive as [Status]
, PS.ExternalId AS VisitID
-- , REPLACE(REPLACE(LOC.[Description],'[',''),']','') AS LOCATIONPLACE
, CDA.CreatedOn AS CDA
FROM PatientSchedule PS
INNER JOIN ContactsList CL WITH(NOLOCK) ON PS.PatientID=CL.ReferenceID
AND CL.Relation = 0
AND PS.ScheduledDateTime BETWEEN #START_DATE AND #END_DATE
INNER JOIN SystemUsers SU WITH(NOLOCK) ON PS.InterfaceCode=SU.InterfaceCode AND SU.Status='1'
INNER JOIN EMRDocuments ED ON PS.ID=ED.PatientScheduleId
AND ED.IsActive=1
LEFT JOIN
(SELECT DISTINCT ED.ID
,SU.NPI
,ED.PATIENTSCHEDULEID
,EDE.CreatedOn
FROM
EMRDOCUMENTS ED
INNER JOIN SystemUsers SU ON ED.ModifiedByID=SU.ID
AND ED.IsActive = 1
AND ED.IsSignedOff ='TRUE'
INNER JOIN EMRDocumentExport EDE ON ED.ID=EDE.DocumentId
AND EDE.LabCompanyName = 'FollowMyHealth_CCDA'
) CDA ON PS.ID=CDA.PatientScheduleId
WHERE --CL.Code = #PatientID
su.RegisteredProvider =1
AND SU.UserID =#PROV
ORDER BY CL.Code, CDA.CreatedOn
This is the general idea. You can fill in the details.
select your fields
from your tables
join (select patientId, min(the date field you want) minDate
from your tables
where whatever
group by patientId) minDates
on minDates.patientId = sometable.patientId
and the date field you want = minDate
etc
A join-less alternative, using window function and either a Common Table Expression or sticking with a subselect.
using CTE:
with mindates as (
select field1, field2, ...,
AppointmentDT,
min(AppointmentDT) OVER (PARTITION BY PatientID) minptAppointmentDT
from table
)
select field1, field2, ... , AppointmentDT from mindate_table
where AppointmentDT = minptAppointmentDT
using subselect:
select field1, field2, ... , AppointmentDT from
(select field1, field2, ...,
AppointmentDT,
min(AppointmentDT) OVER (PARTITION BY PatientID) minptAppointmentDT
from table) mindates
where AppointmentDT = minptAppointmentDT

Can this Full Outer Join query be simplified?

I have bill data from two sources. I want to join them to be able to see where there is a record in one set that is not in the other and vice versa. This query does what I want but I feel like it could be written more elegantly? I only want one field for the account number and bill date (M and Y), and then separate fields for the the charges in each source.
DECLARE #BillDate datetime ='2/1/2016'
SELECT ACCT_NO = COALESCE(BPFACCT,CSTACCT) ,
BillDate = COALESCE(BPFBillDate,CSTBillDate) ,
BPFCharge ,
CSTCharge ,
Delta = ROUND(COALESCE(BPFBill.BPFCharge,0)-COALESCE(CSTBill.CSTCharge,0),2)
FROM
(
SELECT
BPFACCT = acct_no ,
BPFBillDate = cast(billdate as date) ,
BPFCharge = SUM(charge)
FROM
cisbill b
JOIN
cisbilldetail bd ON b.billid=bd.billid
WHERE
billdate>=#BillDate
AND
billdate<DATEADD(MONTH, 1, #BillDate)
GROUP BY
acct_no, billdate
) BPFBill
FULL OUTER JOIN
(
SELECT CSTACCT = acct_no ,
CSTBillDate = cast(bill_date as date) ,
CSTCharge = SUM(new_charges)
FROM
cst_bill
WHERE
bill_date>=#BillDate
AND
bill_date<DATEADD(MONTH, 1, #BillDate)
GROUP BY
acct_no, bill_date
) CSTBill
ON BPFBill.BPFACCT=CSTBill.CSTACCT
AND
BPFBill.BPFBillDate=CSTBill.CSTBillDate
Appreciate any feedback!
I feel like this would give you a more accurate result..
DECLARE #StartDate DATETIME = '2/1/2016',
#EndDate DATETIME
SET #EndDate = DATEADD(MONTH, 1, #BillDate)
SELECT ACCT_NO,
MM,
YY,
BPFCharge = SUM(BPFCharge),
CSTCharge = SUM(CSTCharge)
FROM
(
SELECT acct_no,
MM = MONTH(billdate),
YY = YEAR(billdate),
BPFCharge = charge
CSTCharge = 0.00
FROM cisbill b
JOIN cisbilldetail bd ON b.billid = bd.billid
WHERE billdate >= #StartDate
AND billdate < #EndDate
UNION ALL
SELECT acct_no,
MONTH(bill_date),
YEAR(Bill_date),
0.00,
new_charges
FROM cst_bill
WHERE bill_date >= #StartDate
AND bill_date < #EndDate
) t
GROUP BY ACCT_NO,
MM,
YY

For every row with data I need a row for each category

I have timesheet data that I need to create a report for by date range. I need to have a row for each person for each day, and each time type. If there's no entry for that time type on a given day, i want null data. I've tried a left join, but it doesn't seem to be working. A cross join will give erroneous data.
The tables I have are a Person table (personID, Name), a TimeLog table (TimeLogID, StartDate, EndDate, TimeLogTypeID), and a TimeLogType table (TimeLogTypeID, PersonID, Description, DeletedInd)
All I can get in the result set is the rows with data, and not the empty rows for each TimeLogType
Here's what I have so far:
DECLARE
#startDate DATE,
#endDate DATE
SET #startDate = '2014-05-01'
SET #endDate = '2014-05-30'
SELECT
CONVERT(DATE, TimeLog.StartDateTime, 101) AS TimeLogDay,
SUM(dbo.fnCalculateHoursAsDecimal(TimeLog.StartDateTime, TimeLog.EndDateTime)) AS Hours,
TimeLog.PersonID,
TimeLog.TimeLogTypeID
INTO #HourTable
FROM
TimeLog
WHERE
TimeLog.StartDateTime BETWEEN #startDate AND #endDate
GROUP BY
CONVERT(DATE, TimeLog.StartDateTime, 101),
TimeLog.TimeLogTypeID,
TimeLog.PersonID
SELECT
TimeLogType.Description,
#HourTable.*
FROM
TimeLogType LEFT JOIN
#HourTable ON TimeLogType.TimeLogTypeID = #HourTable.TimeLogTypeID
WHERE
ISNULL(TimeLogType.DeletedInd, 0) = 0
ORDER BY
PersonID, TimeLogDay, TimeLogType.TimeLogTypeID
The data goes something like this:
TimeLogType:
1, Billable
2, Non-Billable
Person:
1, Billy
2, Tom
TimeLog:
1, 1, 2014-05-01 08:00:00, 2014-05-01 09:00:00, 1, 0
2, 1, 2014-05-01 09:00:00, 2014-05-01 10:00:00, 1, 0
3, 2, 2014-05-01 08:00:00, 2014-05-01 08:30:00, 2, 0
4, 2, 2014-05-01 08:30:00, 2014-05-01 09:00:00, 1, 0
5, 1, 2014-05-02 08:00:00, 2014-05-02 09:00:00, 2, 0
Expected Output: (order by person, date, timelog type)
Day, Person, Bill Type, Total Hours
2014-05-01, Billy, Billiable, 2.0
2014-05-01, Billy, Non-Billiable, NULL
2014-05-02, Billy, Billiable, 1.0
2014-05-02, Billy, Non-Billiable, NULL
etc...
2014-05-01, Tom, Billiable, 0.5
2014-05-01, Tom, Non-Billiable, 0.5
etc...
You need to generate all the combinations first and then use left join to bring in the information you want. I think the query is like this:
with dates as (
select dateadd(day, number - 1, mind) as thedate
from (select min(StartDate) as mind, max(EndDate) as endd
from TimeLogType
) tlt join
master..spt_values v
on dateadd(day, v.number, mind) <= tlt.endd
)
select p.PersonId, tlt.TimeLogTypeId, d.thedate,
from Person p cross join
(select tlt.* from TimeLogType tlt where ISNULL(TimeLogType.DeletedInd, 0) = 0
) tlt cross join
date d left join
TimeLog tl
on tl.Person_id = p.PersonId and tl.TimeLogTypeId = tlt.TimeLogTypeId and
d.thedate >= tl.StartDate and d.thedate <= tl.EndDate
After reading Gordon's answer here's what I've come up with. I created it in steps so I could see what was going on. I created the dates w/o the master..spt_values table. I also created a temp table of people so I could select just the ones that had a TimeLogRecord, and then re-use it to pull in details for the final select. Let me know if there's any way to make this run faster.
DECLARE
#startDate DATE,
#endDate DATE
SET #startDate = '2014-01-01'
SET #endDate = '2014-01-31'
-- create day rows --
;WITH dates(TimeLogDay) AS
(
SELECT #startDate AS TimeLogDay
UNION ALL
SELECT DATEADD(d, 1, TimeLogDay)
FROM dates
WHERE TimeLogDay < #enddate
)
-- create a type row for each day --
SELECT
dates.TimeLogDay,
tlt.TimeLogTypeID
INTO #TypeDate
FROM
dates CROSS JOIN
(SELECT
TimeLogType.TimeLogTypeID
FROM
TimeLogType
WHERE
ISNULL(TimeLogType.DeletedInd, 0) = 0
) AS TLT
-- create a temp person table for referance later ---
SELECT * INTO #person FROM Person WHERE Person.personID IN
(SELECT Timelog.PersonID FROM TimeLog WHERE TimeLog.StartDateTime BETWEEN #startDate AND #endDate)
-- sum up the log times and tie in the date/type rows --
SELECT
#TypeDate.TimeLogDay,
#TypeDate.TimeLogTypeID,
#person.PersonID,
SUM(dbo.fnCalculateHoursAsDecimal(TimeLog.StartDateTime, TimeLog.EndDateTime)) AS Hours
INTO #Hours
FROM
#person CROSS JOIN
#TypeDate LEFT JOIN
TimeLog ON
TimeLog.PersonID = #person.PersonID AND
TimeLog.TimeLogTypeID = #TypeDate.TimeLogTypeID AND
#TypeDate.TimeLogDay = CONVERT(DATE, TimeLog.StartDateTime, 101)
GROUP BY
#TypeDate.TimeLogDay,
#TypeDate.TimeLogTypeID,
#person.PersonID
-- now tie in the details to complete --
SELECT
#Hours.TimeLogDay,
TimeLogType.Description,
Person.LastName,
Person.FirstName,
#Hours.Hours
FROM
#Hours LEFT JOIN
Person ON #Hours.PersonID = Person.PersonID LEFT JOIN
TimeLogType ON #Hours.TimeLogTypeID = TimeLogType.TimeLogTypeID
ORDER BY
Person.FirstName,
Person.LastName,
#Hours.TimeLogDay,
TimeLogType.SortOrder

SQL grouping and running total of open items for a date range

I have a table of items that, for sake of simplicity, contains the ItemID, the StartDate, and the EndDate for a list of items.
ItemID StartDate EndDate
1 1/1/2011 1/15/2011
2 1/2/2011 1/14/2011
3 1/5/2011 1/17/2011
...
My goal is to be able to join this table to a table with a sequential list of dates,
and say both how many items are open on a particular date, and also how many items are cumulatively open.
Date ItemsOpened CumulativeItemsOpen
1/1/2011 1 1
1/2/2011 1 2
...
I can see how this would be done with a WHILE loop,
but that has performance implications. I'm wondering how
this could be done with a set-based approach?
SELECT COUNT(CASE WHEN d.CheckDate = i.StartDate THEN 1 ELSE NULL END)
AS ItemsOpened
, COUNT(i.StartDate)
AS ItemsOpenedCumulative
FROM Dates AS d
LEFT JOIN Items AS i
ON d.CheckDate BETWEEN i.StartDate AND i.EndDate
GROUP BY d.CheckDate
This may give you what you want
SELECT DATE,
SUM(ItemOpened) AS ItemsOpened,
COUNT(StartDate) AS ItemsOpenedCumulative
FROM
(
SELECT d.Date, i.startdate, i.enddate,
CASE WHEN i.StartDate = d.Date THEN 1 ELSE 0 END AS ItemOpened
FROM Dates d
LEFT OUTER JOIN Items i ON d.Date BETWEEN i.StartDate AND i.EndDate
) AS x
GROUP BY DATE
ORDER BY DATE
This assumes that your date values are DATE data type. Or, the dates are DATETIME with no time values.
You may find this useful. The recusive part can be replaced with a table. To demonstrate it works I had to populate some sort of date table. As you can see, the actual sql is short and simple.
DECLARE #i table (itemid INT, startdate DATE, enddate DATE)
INSERT #i VALUES (1,'1/1/2011', '1/15/2011')
INSERT #i VALUES (2,'1/2/2011', '1/14/2011')
INSERT #i VALUES (3,'1/5/2011', '1/17/2011')
DECLARE #from DATE
DECLARE #to DATE
SET #from = '1/1/2011'
SET #to = '1/18/2011'
-- the recusive sql is strictly to make a datelist between #from and #to
;WITH cte(Date)
AS (
SELECT #from DATE
UNION ALL
SELECT DATEADD(day, 1, DATE)
FROM cte ch
WHERE DATE < #to
)
SELECT cte.Date, sum(case when cte.Date=i.startdate then 1 else 0 end) ItemsOpened, count(i.itemid) ItemsOpenedCumulative
FROM cte
left join #i i on cte.Date between i.startdate and i.enddate
GROUP BY cte.Date
OPTION( MAXRECURSION 0)
If you are on SQL Server 2005+, you could use a recursive CTE to obtain running totals, with the additional help of the ranking function ROW_NUMBER(), like this:
WITH grouped AS (
SELECT
d.Date,
ItemsOpened = COUNT(i.ItemID),
rn = ROW_NUMBER() OVER (ORDER BY d.Date)
FROM Dates d
LEFT JOIN Items i ON d.Date BETWEEN i.StartDate AND i.EndDate
GROUP BY d.Date
WHERE d.Date BETWEEN #FilterStartDate AND #FilterEndDate
),
cumulative AS (
SELECT
Date,
ItemsOpened,
ItemsOpenedCumulative = ItemsOpened
FROM grouped
WHERE rn = 1
UNION ALL
SELECT
g.Date,
g.ItemsOpened,
ItemsOpenedCumulative = g.ItemsOpenedCumulative + c.ItemsOpened
FROM grouped g
INNER JOIN cumulative c ON g.Date = DATEADD(day, 1, c.Date)
)
SELECT *
FROM cumulative