<<

Using Crystal Reports and SQL to Create a Custom Report to Help Close the Care Gaps in Your Practice Steve King North Country HealthCare October 2019 Three Objectives:

1. Learn how to upload a report to Centricity, how to use parameters, and how to use formulas 2. Learn how to use SQL Server Management Studio to explore the data in Centricity 3. Create a report that can close the gaps of care in your practice 1. Mammogram 2. Expand to Diabetes

• Session is for Beginner to Immediate Report creators • Assumes you are a CPS 12.X user • And you use clinical content, not CCC • Bonus Content

 SDID is a filed that is used to link DOCUMENT ( all EHR transactions start) to the various medical tables in the EHR (e.g. PROBLEM, ORDERS, OBS, ALLERGY, MEDICATE)  Translating a EHR timestamp date to a normal date: DATEADD(ss, DOCUMENT.CLINICALDATE / 1000000, ‘01/01/1960’)  SQL Keywords used in this presentation  NULL: there is no value. It is neither true or false, doesn’t have a value. To do a comparison, must use IS [NOT] NULL, not an equality statement: (this is wrong) = NULL  GETDATE():returns the current date/time  ISNULL(var1, var2): If var1 IS NULL, use var2. Also see COALESCE  JOIN – 2 tables or datasets together About me  Steve King, North Country HealthCare

[email protected]  Employed at North Country for over 9 years  North Country has 14 access points throughout northern Arizona and parts of central Arizona  Main campus is in Flagstaff, AZ  55,000 patients a year  160,000 visits  80 providers Tools to Develop Reports For Centricity

 Crystal Reports XI

 I don’t know how to acquire – your VAR should be able to help

 Crystal Report can be integrated differently in Centricity – this session uses Crystal Reports as a stand-alone application  Microsoft SQL Server Management Studio

 Free with your version of SQL Server  Centricity Data Dictionary

 Download aethenahealth web site Crystal Report: Objectives

1. Using Parameters

2. Adding parameters to a SQL query

3. Creating formulas to show parameters in Centricity

4. Toggling the footer Upload a Crystal Report Into Centricity

The Main Report

Parameters in Centricity, Parameters and Formulas in Crystal Reports

A footer that toggles visibility Crystal Report: Parameters

Because these parameters are used to build formulas in Crystal Report, the Crystal Report data type may not be the same if this was a stand-alone report

1. STARTDATE and ENDATE– although they will be a date in Centricity, for the report they are strings 2. FACILITY and PROVIDER – although these will be used in Centricity as number, for the report they are strings 3. HIDE_FOOTER: used as a number in both Crystal Reports and Centricity Crystal Report: Formulas

 Formulas are used to

1. Display information

2. Change information

3. Store information

For this report we are only displaying the data in the parameters FACILITY_CRITERIA and PROVIDER_CRITERIA

Used to show the facility and the provider

1. Because this report requires to choose a facility this formula is different than one that allows the user to choose one or more facilities

'Facility: ' + {?FACILITY}

Formulas to show all facilities or selected facilities/providers or selected providers

If {?FACILITY} = 'NULL' THEN If {?Provider} = 'NULL' THEN "Facilities: all" “Providers: all" ELSE ELSE "Facilities: " & {?FACILITY} “Providers: " & {?Provider} DATE_CRITERIA

 Because we force the user to enter dates, we know that there will be a start date and end date

"Dates from " & {?STARTDATE} & " to " & {?ENDDATE} Toggle a Report Section in Crystal Reports

 In Crystal Reports

 Create a parameter named HIDE_FOOTER

 Field type: number

 In the Report footer enter the text you want to show

 In Section Expert make sure Suppress (No Drill-Down is unchecked and enter this formula: {?HIDE_FOTTER}=1

 This can be done for any section you want to hide Report Design Mode

Toggle between design mode and report mode Centricity: Parameters

 To transfer parameters values from Centricity to Crystal Report, usually use text  The are different ways use parameters from Centricity to the SQL query

 Select Boxes: ?CONTROLNAME.ITEMDATA.U? or ?CONTROLNAME.ITEMDATA?

 “U” shows actual numbers

 No “U” shows numbers as quoted string

 Dates To And FROM: ?CONTROLNAME.DATE1? or ?CONTROLNAME.DATE2?

 Check boxes and radio buttons: ?CONTROLNAME.VALUE? Centricity: Parameters As a Filter in a Query

 This will let you filter on None, One, or Multiple Providers AND ( (?PROVIDER.ITEMDATA? IS NOT NULL AND a.DoctorID IN (?PROVIDER.ITEMDATA.U?)) OR (?PROVIDER.ITEMDATA? IS NULL) )

Note the use or non-use of “U”

AND ( ('2972' IS NOT NULL AND a.DoctorID IN (2972)) OR ('2972' IS NULL) ) Centricity and Crystal Reports: Tying the Parameters Together

If {?Provider} = 'NULL' THEN “Providers: all" ELSE “Providers: " & {?Provider} Centricity and Report Dates

 If your report is date sensitive, always require dates  You can use a default statement ISNULL(,’01/01/1900’), but then you run the chance of getting too much data  We’ve been on Centricity for 10 years – do I really want 10 years of data for an appointment report? RAISERROR

 You can use a RAISERROR statement if your user enters a wrong date

 SQL keyword that raises an error, causing the query to stop

 Centricity has no way to capture the error

 You get an ID Dispatch error

 Full use of the keyword is outside of this discussion IF @StartDate <= '12/31/2018' RAISERROR ('StartDate is too small', 16, 1) Centricity SQL Preview Button

 Lets you see the SQL in the report Don’t Forget to Assign Security!

 Right click on the Report  Assign by Group, not by User  Always uncheck the Delete  Checking Edit will allow user/group to save criteria report

 Carefully think about allowing that – otherwise your report folders will get cluttered. Exploring Data in Your OBSTERMs

 OBSTERMs (or Observation Terms) are what determines what is shown in the Flowsheet  The actual observation lives in the OBS, or in the RPTOBS

 Key fields are OBSDATE, OBSVALUE, PID, and XID  OBSHEAD is a table that contains observation codes and descriptions for OBSTERM

 Key fields are HDID, NAME, and DESCRIPTION OBSTERMs to Close a Gap: Mammogram

 Three ways to determine if a mammogram was performed:

1. OBSTERMS

2. ICD-10 Codes

3. CPT Codes

What to use depends on your clinical workflow – for this example we’re using OBSTERM How to Find The OBSTERM: Clinical Content

 Find forms where the OBSTERM might be used  Start a visit with the form  End the visit  Click “View Clinical Content” How to Find The OBSTERM: Export a Clinical Kit • Administration > System > Export Clinical Kits > Form Component will bring up the Search Screen • Then click Export Clinical Kit, and save it to your computer Clinical Kit: Includes a Lot of Files

• The Microsoft Excel Template file has the OBSTERMS by NAME used in the form • Excel will complain when you open the file – just answer yes • You’ll have to do some detective work to find the possible OBSTERM you’re looking for [This is from the XLT file] /*Function for RUNPROCESS Button Record Mammogram*/ fn fn1151_1503350234_635(){ if (PATIENT._AGEINMONTHS >= 480 and PATIENT._AGEINMONTHS < 600) then fnRecObs_CSF(DOCUMENT.RESULTMAM_1151_1503350234_633,DOCUMENT.DATEMAM_1151_1503350234_634,"MAMMOGRAM","MAMMO DUE",731) else fnRecObs_CSF(DOCUMENT.RESULTMAM_1151_1503350234_633,DOCUMENT.DATEMAM_1151_1503350234_634,"MAMMOGRAM","MAMMO DUE",365) endif View Clinical Content is a lot easier! Finding the OBSTERM HDID

 OBSHEAD is a table that stores all the OBSTERMs  OBSHEAD.NAME is the field that was returned when you clicked View Clinical Content  OBSHEAD.HDID is the field that identifies the OBSTERM in OBS  To get OBSHEAD.HDID for MAMMOGRAM run a query on OBSHEAD  OR do a Join (Shown on next slide) Verifying Your OBSTERM is Being Used

SET NOCOUNT ON SET NOCOUNT ON OBS is the actual Table; SELECT SELECT RPTOBS is a view COUNT(*) COUNT(*) AS [Count] , oh.Name , oh.Name , oh.Description , oh.Description FROM OBS o FROM RPTOBS o JOIN OBSHEAD oh ON o.HDID = oh.HDID JOIN OBSHEAD oh ON o.HDID = oh.HDID WHERE (oh.Name = 'MAMMOGRAM') WHERE o.HDID = 71 GROUP BY GROUP BY oh.Name oh.Name , oh.Description , oh.Description

That’s a lot of mammograms – Guess we have the correct OBSTERM Verifying Your OBSTERM is Being Used Correctly

SET NOCOUNT ON

DECLARE @StartDate datetime = '08/01/2019' , @EndDate datetime = '08/31/2019'

DECLARE @Lookback date = DATEADD(mm, -14, @EndDate) /* 15 months from end of measurement period. Zero month counts as a month */

SELECT DISTINCT o.OBSVALUE FROM OBS o WHERE (o.HDID = 71) AND (o.OBSDATE >= @Lookback AND o.OBSDATE < DATEADD(d, 1, @EndDate)) Hmm, some of those values don’t look valid Verifying the Correct OBSVALUE

 Work with your clinical team  Some of the results are obviously not valid:

 Declines

 Assessment Incomplete  After talking to the clinical team, results that are either Normal, Abnormal, or contain the phrase BIRAD are valid mammograms

 The OBSVALUE is not case sensitive How to Return the Correct OBSVALUE?

 Hold that thought!  Will explain when we look at the report query! Developing a Care Gap Report Thinking ahead…

 Looking for appointments  Looking for patients that meet the denominator for the gaps we are trying to close  Numerator include an observation during a measurement period – but do we need to look outside the measurement period?  Denominators usually only include medical visits  Looking for patients that DON’T meet the numerator for the gaps we are trying to close  There could be a lot of appointments, which can impact the performance of Centricity

 Limit the time of day when the report is ran? 

 Limit the data that is being returned?  Understanding the Requirements: What is a Medical Visit?

 Can use document type (EHR) or CPT code  Internally, we decided to use CPT codes

 Document types are too “fuzzy”, i.e. what’s an office visit, nursing visit, walk in visit, or urgent care visit?

 The table PatientVisitProcs has a column named CPTProcedureCodeQualifierMId. This column should be linked to the MedLists table MedListsId column with a TableName of ProcedureCodeQualifier.

 To see the full list use the query "SELECT * FROM MedLists WHERE TableName = 'ProcedureCodeQualifier'"

HCPC - BH HCPC - DEN UDS encounter types! HCPC - EN HCPC - MED HCPC - OP Understanding the Requirements: Defining the Patient’s Last Visit

 Going to look for the patient’s last visit, based on an unvoided ticket  Visit must be a medical visit  How far to look back?

 The measure is looking for a year’s worth of data, but our report only looking for small window of time

 Lookback period will depend on numerator’s definition Pseudocode For the Query  Do housekeeping tasks  Create a table to store results of multiple queries  Get all patients with upcoming appointments  Include demographic information including age and gender  Get the patients last medical visits  Care Gap 1: Mammogram  Determine if patients are in the mammogram denominator  Determine if patients are in the mammogram exclusion  Determine if patients are in the mammogram numerator  Care Gap 2: Diabetes  Determine if patients are in the diabetes denominator  Determine if patients are in the diabetes numerator  Determine if patients are in the diabetes exclusion (we will skip this logic)  Return results that include upcoming appointment, facility, provider, and care gaps Understanding the Query: Housekeeping tasks

SET NOCOUNT ON /* Prevents a count of returning, which if not included will effect Excel reporting */

DECLARE @StartDate datetime = '07/01/2019' , @EndDate datetime = '07/07/2019' Understanding the Query: Store Appointments in a Temporary Table

 SQL server has tables, temporary tables, and table variables

 Tables: data that lives permanently in the database

 CREATE TABLE PatientProfile…  Temporary tables: live in the tempdb (temporary) database, are not permanent, have database statistics (topic beyond this presentation)

 CREATE TABLE #Results…  Table variables: only live the transaction in which they are created

 DECLARE @Patients TABLE…  Deciding what to use is beyond the scope of this discussion, but I’m going to use a table variable Understanding the Query: Temporary Table to Store Results From Multiple Queries

DECLARE @Patients TABLE ( /* Patient identifiers */ PatientProfileID int /* PM side */ , PID numeric(19) /* EHR side */ /* Basic demographic info to determine denominator */ , Age int , Gender char(1) /* 3 ways to the last medical visit for the patient: , loop, set based */ , LastVisitCursor date , LastVisitLoop date , LastVisitSet date /* ^ Need the patients last medical visit to determine the numerator */ , MammogramDenom tinyint DEFAULT 0 , MammogramNum tinyint DEFAULT 0 , MammogramExclusion tinyint DEFAULT 0 /* If time permits will also look at Diabetes and A1C If we're out of time the code is commented */ , DiabetesDX tinyint DEFAULT 0 , DiabetesDenomimator tinyint DEFAULT 0 , DiabetesNum tinyint DEFAULT 0 , A1cSDID numeric(19) , A1cObsValue decimal(5,2) NULL ) Understanding the Query: INSERT and SELECT to Get Appointments

Since only inserting selected fields into @Patients need to list what fields we want to INSERT INTO @Patients (PatientProfileID, PID, Age, Gender) SELECT DISTINCT a.OwnerID /* For patients, this is PatientProfileID */ , pp.PID /* Get this value from PatientProfile since it doesn't live in table Appointments */ , DATEDIFF (YEAR, pp.birthdate, @EndDate) - CASE WHEN 100 * MONTH(@endDate) + DAY(@endDate) < 100 * MONTH(pp.birthdate) + DAY(pp.birthdate) THEN 1 ELSE 0 END /* Well known solution developed by Steve Kass which accounts for a number of edge cases including leap years. Found on Stackoverflow */ , pp.Sex FROM PatientProfile pp JOIN Appointments a ON pp.PatientProfileID = a.OwnerID WHERE (a.ApptStart >= @StartDate and a.ApptStart < DATEADD(d, 1, @EndDate)) AND (a.ApptKind = 1) AND (ISNULL(a.Canceled,0) = 0) /* Valid appointments only */ AND (a.FacilityId = @FacilityID) /* Limit to a single facility */ Understanding the Query: Getting the Last Visit

 Three ways to get the last visit

1. Using a SQL Server cursor – worst practice

2. Using a WHILE loop – okay practice

3. Using a set based query – best practice Understanding the Query: Getting the Last Visit With a Cursor – General Set Up

DECLARE @PID numeric /* value you want to get */ , @PatientProfileID int /* not needed -- just to show that you can return multiple columns for the cursor */ , @LastVisit datetime

DECLARE db_cursor CURSOR FOR SELECT PID, PatientProfileID FROM @Patients

OPEN db_cursor FETCH NEXT FROM db_cursor INTO @PID, @PatientProfileID

WHILE @@FETCH_STATUS = 0 BEGIN /* Do stuff */ FETCH NEXT FROM db_cursor INTO @PID, @PatientPRofileID /* Bring in the next row */ END

CLOSE db_cursor DEALLOCATE db_cursor Understanding the Query: “Do stuff” in the Cursor Loop SET @LastVisit = NULL SELECT @LastVisit = MAX(pv.Visit) /* Max bring back the last visit */ FROM PatientVisit pv JOIN PatientVisitProcs pvp ON pv.PatientVisitID = pvp.PatientVisitID WHERE (ISNULL(pvp.VOided,0) = 0) /* This is the best way to identify unvoided ticket If a ticket has one or more unvoided procedures then it is a valid ticket */ AND (pvp.CPTProcedureCodeQualifierMId = 7343) /* HCPC - Med */ /* This query is using a magic number. If you have to use, create a comment that explains what it is. Your future self will thank you when you do maintenance on the report. */ AND (pv.PatientProfileID = @PatientProfileID) /* The cursor brings values for each row (Patient) in @Patients */ IF @LastVisit IS NOT NULL /* It's possible that a patient won't have a visit, so check for that */ UPDATE @Patients SET LastVisitCursor = @LastVisit WHERE PatientProfileID = @PatientProfileID Understanding the Query: Using a WHILE Loop Pseudocode /* Table variable -- the actual script uses a temp table */ DECLARE @TABLE_NAME TABLE ( ID int IDENTITY /* Unique numeric field in the table – doesn’t have to be an IDENTITY */ , Some fields...); /* variables that store the ID values */ DECLARE @curID int, @nextID int; /* Get the first ID */ SET @nextID = (SELECT MIN(ID) FROM @TABLE_NAME); /* WHILE loop, instead of cursor */ WHILE (1 = 1) BEGIN SET @curID = @nextID; /* Do stuff here */ SET @nextID = NULL; /* not really necessary, but I do it anyway just to be safe */ SET @nextID = (SELECT MIN(ID) FROM @TABLE_NAME WHERE ID > @curID); /* this finds the next ID that is greater than the current id */ IF @nextID IS NULL /* if out of IDs break out of the loop */ BREAK; END Understanding the Query: “Do stuff” in the WHILE Loop

 Same code as for a cursor /* Do stuff here */ SET @LastVisit = NULL

SELECT @LastVIsit = MAX(pv.Visit) FROM PatientVisit pv JOIN PatientVisitProcs pvp ON pv.PatientVisitID = pvp.PatientVisitID WHERE (ISNULL(pvp.Voided,0) = 0) AND (pv.PatientPRofileID = @curPatientProfileID) AND (pvp.CPTProcedureCodeQualifierMId = 7343) /* HCPC - Med */

IF @LastVisit IS NOT NULL UPDATE @Patients SET LastVisitLoop = @LastVisit WHERE PatientPRofileID = @curPatientProfileID Understanding the Query: Get Last Visit Using Set Based Logic (JOINs)

 SQL is designed around set based, rather than procedural logic UPDATE @Patients SET LastVisitSet = q.LastVisit FROM @Patients p JOIN (SELECT MAX(pv.Visit) AS [LastVisit], pv.PatientProfileID FROM PatientVisit pv JOIN PatientVisitProcs pvp ON pv.PatientVisitID = pvp.PatientVisitID WHERE (ISNULL(pvp.Voided,0) = 0) AND (pvp.CPTProcedureCodeQualifierMId = 7343) /* HCPC - Med */ GROUP BY PatientProfileID) q ON p.PatientProfileID = q.PatientProfileID Verifying the Results for Last Visit

Should be the same – and they are! SELECT PID, LastVisitCursor, LastVisitLoop, LastVisitSet FROM @Patients PID

What’s the NULL doing in there? • Must be a new patient! Care Gap 1: Women 50 – 74 With an Upcoming Appointment That Need a Mammogram

Breast Cancer Screening Ages 50-74 (NQF 2372) Endorser: NQF 2372 / CMS eCQM 125v6 Percentage of women 50-74 years of age who had a mammogram to screen for breast cancer. Numerator: Women with one or more mammograms during the measurement period or the 15 months prior to the measurement period. [Mammogram in the last 27 months] Denominator: Women 51-74 years of age with a visit during the measurement period. [Females Age 52-74 at the end of the measurement period] Qualifying visit (see Technical Specifications) in the measurement period Exclusions: Bilateral mastectomy; 2 unilateral mastectomies; Exclude patients in hospice care Understanding the Query: Mammogram Denominator

UPDATE @Patients SET MammogramDenom = 1 WHERE (Age BETWEEN 52 AND 74) AND (Gender = 'F') AND (LastVisitSet IS NOT NULL) AND (LastVisitSet >= @Lookback AND LastVisitSet < DATEADD(d, 1, @EndDate)) Understanding the Query: Updating the Mammogram Numerator Based on Set

/* Updating numerator based on set */ UPDATE @Patients SET MammogramNum = q.MAMMOGRAM FROM @Patients p JOIN (SELECT 1 AS [Mammogram], PID FROM OBS o WHERE (o.HDID = 71) /* Mammogram */ /* acceptable values */ AND (o.OBSVALUE LIKE 'BiRad%' OR o.OBSVALUE LIKE 'Normal%' OR o.OBSVALUE LIKE 'Benign%') AND (o.OBSDATE >= @Lookback AND o.OBSDATE < DATEADD(d, 1, @EndDate)) ) q ON p.PID = q.PID Understanding the Query: WHERE Clause For Mammogram Numerator Using the Correct OBSVALUE

WHERE (o.HDID = 71) /* Mammogram */ /* acceptable values */ AND (o.OBSVALUE LIKE 'BiRad%' OR o.OBSVALUE LIKE 'Normal%' OR o.OBSVALUE LIKE 'Benign%')  Not happy with 'Normal%' but clinical team said it was ok Understanding the Query: Mammogram Exclusions

 Do we use OBSTERMS, ICD-10 Codes, or CPT Codes?  Won’t look at CPT codes because most FQHC/CHCs don’t perform mastectomies  In our clinical content OBSTERMS can be Mastectomies or Past Surgical History Understanding the Query: Mammogram OBSTERM Exclusion

SELECT COUNT(*), oh.Name, oh.DESCRIPTION FROM OBS o JOIN OBSHEAD oh ON o.HDID = oh.HDID WHERE oh.DESCRIPTION LIKE '%Mastec%' GROUP BY oh.Name, oh.DESCRIPTION  Doesn’t return a lot of results: Count Name DESCRIPTION 93 BILAT MAST bilateral mastectomy, hx of 88 MASTECTRTHX mastectomy, right breast, hx of 5 MASTLNDRTHX mastectomy with lymph node disection, right breast, hx of 1360 MASTECTOMY mastectomy, hx of 6 MASTLNDLTHX mastectomy with lymph node disection, left breast, hx of 104 MASTECTLTHX mastectomy, left breast, hx of Understanding the Query: ICD-10 Code Mammogram Exclusion

SELECT COUNT(*), md.Code, md.ShortDescription FROM PROBLEM p JOIN MasterDiagnosis md ON p.ICD10MasterDiagnosisId = md.MasterDiagnosisId WHERE (md.Code IN ('Z90.10','Z90.11','Z90.12','Z90.13')) GROUP BY md.Code, md.ShortDescription  Doesn’t return a lot of results Count Code ShortDescription 24 Z90.13 Acquired absence of bilateral breasts and nipples 59 Z90.12 Acquired absence of left breast and nipple 14 Z90.11 Acquired absence of right breast and nipple 82 Z90.10 Acquired absence of unspecified breast and nipple Understanding the Query: Final Mammogram Exclusions

 Use both!

 Discuss with clinical team if there are other ways that a mastectomy is documented in the EHR

 Not showing the code except for ICD-10 codes UPDATE @Patients SET MammogramExclusion = q.Exclusion FROM @Patients p JOIN (SELECT 1 as Exclusion, PID FROM PROBLEM prb JOIN MasterDiagnosis md ON prb.ICD10MasterDiagnosisId = md.MasterDiagnosisId WHERE (md.Code IN ('Z90.10','Z90.11','Z90.12')) /* Unilateral, right, left mastectomy */ AND (prb.XID = 1e18) GROUP BY PID HAVING COUNT(*) > 1 /* Right + left mastectomy = full mastectomy, 2 unilateral is assumed to be full mastectomy */ ) q ON p.PID = q.PID Understanding the Query: SELECT Mammogram Gaps in Care Using CASE

 To verify out of SSMS :

 Return all fields from @Patients so you can verify the data in Centricity  Use a CASE statement to determine if there is a gap in care CASE WHEN p.MammogramDenom = 1 AND p.MammogramExclusion = 0 AND p.MammogramNum = 0 THEN 'X' ELSE NULL END AS [MammogramGap] Care Gap 2: Diabetics with Poor A1c Control

Diabetes A1c > 9 or Untested (NQF 0059) Diabetes Endorser: NQF 0059 / CMS eCQM 122v7 Percentage of patients 18-75 years of age with diabetes who had hemoglobin A1c > 9.0% during the measurement period. Numerator: Patients whose most recent HbA1c level (performed during the measurement period or last 12 months) is >9.0% or No A1c in the last 12 months Denominator: Patients 18-75 years of age with diabetes with a visit during the measurement period. Exclusions: Hospice Care overlapping the measurement period Understanding the Query: Finding Diabetics

 Going to skip explaining most of the logic, will show a pitfall instead  Find all patients with diabetes: UPDATE @Patients SET DiabetesDX = q.Diabetes FROM @Patients p JOIN (SELECT 1 AS [Diabetes], PID FROM PROBLEM prb JOIN MasterDiagnosis md ON prb.ICD10MasterDiagnosisId = md.MasterDiagnosisId WHERE (md.Code LIKE 'E10.%' OR md.Code LIKE 'E11.%') /* Type 1, Type 2 */ AND (prb.XID = 1e18) AND (prb.STOPDATE > GETDATE()) ) q ON p.PID = q.PID Understanding the Query: Updating the Diabetic Denominator

 Reset @Lookback to 1 year  See if their last visit with within the lookback period  AND in the proper age range SET @Lookback = DATEADD(mm, -11, @EndDate)

UPDATE @Patients SET DiabetesDenomimator = 1 FROM @Patients p WHERE (DiabetesDx = 1) AND (p.Age BETWEEN 18 AND 75) AND (p.LastVisitSet >= @Lookback AND p.LastVisitSet < DATEADD(d, 1, @EndDate)) Understanding the Query: Getting the Last A1c Lab Value The WRONG Way (OBSVALUE)

UPDATE @Patients SET A1cOBSValue = q.OBSVALUE FROM @Patients p JOIN ( SELECT MAX(o.SDID) AS [MaxSDID], o.OBSVALUE, o.PID /* MAX(o.SDID) should be the last value */ FROM OBS o WHERE (o.HDID = 28) /* HGBA1C */ AND (o.OBSDATE >= @Lookback AND o.OBSDATE < DATEADD(d, 1, @EndDate)) GROUP BY o.OBSVALUE, o.PID) q ON p.PID = q.PID /* Because a patient can have different OBSVALUE, the MAX(SDID) brings all of those back We want the most recent obsvalue */ Understanding the Query: Getting the Last A1C Lab Value The RIGHT Way [MAX(SDID)]

UPDATE @Patients SET A1cSDID = q.SDID FROM @Patients p JOIN (SELECT MAX(o.SDID) AS [SDID], PID FROM OBS o WHERE (o.HDID = 28) /* HGBA1c */ AND (o.OBSDATE >= @Lookback AND o.OBSDATE < DATEADD(d, 1, @EndDate)) GROUP BY o.PID) q ON p.PID = q.PID /* Look for the last document that had a HGBA1c value */ Understanding the Query: Getting the Last OBSVALUE for the A1c Lab Result

UPDATE @Patients SET A1cObsValue = TRY_PARSE(o.OBSVALUE AS decimal(5,2)) /* TRY_PARSE is only for SQL Server 2012 or greater Possible to have OBSVALUE that aren’t numeric */ FROM @Patients p JOIN OBS o ON p.A1cSDID = o.SDID WHERE (o.HDID = 28) /* HGBA1c */ Understanding the Query: Updating the Diabetic Numerator

UPDATE @Patients SET DiabetesNum = 1 WHERE (A1cObsValue < 9.0) AND (LastVisitSet < @LastVisit) AND (DiabetesDenomimator = 1) Understanding the Query: SELECT Diabetics Gaps in Care Using CASE

CASE WHEN p.DiabetesDenomimator = 1 AND p.DiabetesNum = 0 THEN 'X' ELSE NULL END AS [DiabetesGap] We found patients in the numerator, want patients that AREN’T in then numerator Understanding the Query: Final SELECT for the Report – There’s a Design Problem!

SELECT p.* ,[Appointment Info], [Provider],[Facility] FROM @Patients p JOIN PatientProfile pp ON p.PatientProfileID = pp.PatientProfileID JOIN Appointments a ON pp.PatientProfileID = a.OwnerId /* Oops, have to join to appointments */ JOIN DoctorFacility doc ON a.DoctorId = doc.DoctorFacilityId JOIN DoctorFacility fac ON a.FacilityId = fac.DoctorFacilityId WHERE (a.FacilityID = @FacilityID) /* Oops, have to filter on facility Without this filter, appointments for facilities are returned which is not what we want */ Final SELECT: Fixing the “Oops”

 Should have added a field in @Patient that would include AppointmentID  Sometimes it takes several tries to get what you want! Questions?

 Steve King  [email protected]  Github: https://github.com/gophersounds/CHUG2019