Patients by Dataset, Dx, and UserSort2

I need a count of the number of undischarged patients who have one or more dx codes, by dataset and program. We store the program in UserSort2 in the Patient Info screen. It does not matter whether the diagnosis is found in the first, second, third, of fourth Dx field. Sometimes I need the data in just a summary format, but other times I need the names and id’s of the patients in each group.

There are three queries below. The first is the list of patients, which is straightforward enough. We use a conditional expression for the second column so if there is no data, or just spaces, in UserSort2, we can display the word “None” instead. In the WHERE clause we have two IN expressions. These expressions evaluate to TRUE if the value on the left is found in the list on the right in parentheses. In our example, we are looking for just the Dx codes ‘296.22’ or ‘296.23’, but you can replace them with one or more codes of interest to you. Be sure to enclose each Dx code in apostrophes, and separate them with commas.

Next, we have a summary version of the same query, using the GROUP BY ROLLUP( ) syntax to provide group totals and subtotals. In the resulting matrix of results remember that anywhere you see “(NULL)”, you should interpret it as meaning “All”. Just as with the list query, be sure to adjust the Dx codes in the WHERE condition to meet your needs.

Finally, we have a consolidation of the two queries above using the UNION keyword to combine the results of the two queries. In order to do so, it is necessary to match the order and type of columns in the two result sets. In this case, we simply insert some dummy strings for columns 4, 5, and 6 in the summary query. Another quirk of UNIONed queries is that the ORDER BY clause just uses column numbers rather than column names.

Here is the first query, which includes a detailed list of patient names and ID’s:

SELECT  
  b.licnum AS "Dataset",
  (IF TRIM(b.usersort2) = '' THEN 'None' ELSE COALESCE(b.usersort2,'None') END IF) AS "Program", 
  a.DxCode,
  b.lastname AS "Last Name", 
  b.firstname AS "First Name", 
  b.id AS "Account ID",
  1 AS "Total"
FROM 
  sos.dx a, sos.rv_patients b
WHERE
  a.dxnum IN (b.dx1,b.dx2,b.dx3,b.dx4)
  AND "Dataset" > 100
  AND b.dischargedate IS NULL
  AND a.dxcode IN ('296.22','296.23')
ORDER BY
  "Dataset", "Program", a.dxcode, "Last Name", "First Name", "Account ID"

Next, this one is just the summary counts:

SELECT 
  b.licnum AS "Dataset",
  (IF TRIM(b.usersort2) = '' THEN 'None' ELSE COALESCE(b.usersort2,'None') END IF) AS "Program", 
  a.DxCode,
  COUNT(b.id) AS "Total"
FROM 
  sos.dx a, sos.rv_patients b
WHERE
  a.dxnum IN (b.dx1,b.dx2,b.dx3,b.dx4)
  AND "Dataset" > 100
  AND b.dischargedate IS NULL
  AND a.dxcode IN ('296.22','296.23')
GROUP BY
  ROLLUP("Dataset", "Program", a.dxcode)
ORDER BY
 "Dataset", "Program", a.dxcode

Finally, below we have the consolidated query, using UNION to combine the results of the two separate queries above:

SELECT  
  b.licnum AS "Dataset",
  (IF TRIM(b.usersort2) = '' THEN 'None' ELSE COALESCE(b.usersort2,'None') END IF) AS "Program", 
  a.DxCode,
  b.lastname AS "Last Name", 
  b.firstname AS "First Name", 
  b.id AS "Account ID",
  1 AS "Total"
FROM 
  sos.dx a, sos.rv_patients b
WHERE
  a.dxnum IN (b.dx1,b.dx2,b.dx3,b.dx4)
  AND "Dataset" > 100
  AND b.dischargedate IS NULL
  and a.dxcode in ('296.22','296.23')

UNION

SELECT 
  b.licnum AS "Dataset",
  (IF TRIM(b.usersort2) = '' THEN 'None' ELSE COALESCE(b.usersort2,'None') END IF) AS "Program", 
  a.DxCode,
  '',
  '',
  'COUNT...',
  COUNT(b.id) AS "Total"
FROM 
  sos.dx a, sos.rv_patients b
WHERE
  a.dxnum IN (b.dx1,b.dx2,b.dx3,b.dx4)
  AND "Dataset" > 100
  AND b.dischargedate IS NULL
  and a.dxcode in ('296.22','296.23')
GROUP BY
  ROLLUP("Dataset", "Program", a.dxcode)
ORDER BY
   1,2,3,4,5,6

The queries above all require you to enter specific diagnoses in the WHERE condition. Instead, let’s say that you want to return ANY diagnosis starting with 296 or 305. To do that, we can replace the line:

AND a.dxcode IN (‘296.22′,’296.23’)

with the following:

AND (a.dxcode LIKE ‘296%’ OR a.dxcode LIKE ‘305%’)

Within those parentheses you can add as many OR a.dxcode LIKE ‘…’ as you want. The key is that the LIKE keyword sets up a condition in which you can use percent signs (%) to match any characters – what we call a “wildcard”. Here are some more examples:

LIKE ‘ABC%’   …  matches any character string starting with “ABC”.

LIKE ‘%ABC’   …  matches any character string ending with “ABC”.

LIKE ‘%ABC%’  …  matches any character string containing “ABC” in any position.

A variation of the above queries might be to limit the results to patients who had been seen during some specified date range. To do that we would add another condition in the WHERE clause like this:

AND b.ptnum IN (SELECT ptnum
FROM journal
WHERE trantype = ‘S’ AND trandate BETWEEN ‘2014-01-01’ AND ‘2014-06-30’)

Adding that new condition to the WHERE clause of the above queries would look like:

WHERE
  a.dxnum IN (b.dx1,b.dx2,b.dx3,b.dx4)
  AND "Dataset" > 100
  AND b.dischargedate IS NULL
  AND a.dxcode in ('296.22','296.23')
  AND b.ptnum IN (SELECT ptnum FROM journal 
                  WHERE trantype = 'S' 
                    AND trandate BETWEEN '2014-01-01' AND '2014-06-30')

 

Find Patients with No Recent Activity

I would like to develop one I can use prior to record destruction.  The information I want is: list of client names, last name then first (all active andinactive), date of birth, age (the day of query run), first date seen, last date seen, last transaction, last payment, all providers that rendered service, and patient category (if assigned).  I think this sounds complicated.  Can it be done in one query?  Would first date seen pull from intake date or first transaction or first charge?  Would last date seen pull from last charge, discharged date, or last transaction?

In the query below, “last transaction” is the most recent entry of any kind in the patient’s ledger. If more than one provider rendered services to the patient, the results show a line for each. The “first seen on” and “last seen on” dates are specific to the provider shown on that line, but the “last payment” and “last transaction” are not provider-specific.

This query features a couple of functions that we created here at SOS and added to the library of functions available in SQL Anywhere. AGEINYEARS returns an accurate calculation of age, taking leap years into consideration. LASTCREDITDATE examines the patient’s ledger and returns the date of the last actual payment — that is, it ignores adjustment credits.

The WHERE clause filters the results by data set (licnum), includes both active and inactive patient lists, and returns only those patients with a last transaction date at least 180 days ago. Of course, you can modify each of those values for your specific needs.

Also of interest is that the SELECT list of this query consists mostly of subqueries. Using subqueries in this fashion often results in much better performance, even when the same results can be achieved with additional joins in the main query. As shown here, each subquery must be within a set of parentheses and be followed by “AS <your column name>”.

SELECT DISTINCT
  a.lastname + ', ' + a.firstname AS "Name",
  a.id AS "Account ID",
  a.dob AS "DOB",
  sos.AGEINYEARS(a.dob,today()) AS "Age",
  ( SELECT provcode FROM sos.providers WHERE providernum = b.providernum) AS "Provider",
  ( SELECT MIN(trandate) FROM sos.journal jou JOIN sos.jcharges chg 
    WHERE jou.ptnum = a.ptnum AND chg.providernum = b.providernum AND trantype = 'S' AND amount > 0) AS "First Seen On",
  ( SELECT MAX(trandate) FROM sos.journal jou JOIN sos.jcharges chg 
    WHERE jou.ptnum = a.ptnum AND chg.providernum = b.providernum AND trantype = 'S' AND amount > 0) AS "Last Seen On",
  ( SELECT sos.LASTCREDITDATE(a.ptnum)) AS "Last Payment",
  ( SELECT MAX(trandate) FROM sos.journal WHERE ptnum = a.ptnum) AS "Last Transaction",
  ( SELECT categcode FROM sos.ptcategs WHERE ptcategnum = a.ptcategnum) AS "Pt Category"
FROM 
  sos.patients a
  LEFT OUTER JOIN sos.jcharges b ON a.ptnum = b.ptnum
WHERE
  a.licnum = 101  //specifies the dataset to be examined
  AND a.flag IN(0,1)  //includes both active (0) and inactive (1) patient lists
  AND "Last Transaction" < (TODAY() - 180)  //show only those whose last transaction was 180 days ago
ORDER BY "Name","Account ID","Provider"

Active Patients with Birthdays in a Specified Month

I need a report to give to individual providers with active patients who have a birthday coming up in the next month. It would be helpful for the report to also include patient addresses. The intent is to use the report to mail birthday cards.

This is a pretty basic query. It uses the function MONTH( ) to return only those patients whose birth month matches the month specified in the WHERE clause. You must enter the month as a digit, where January is 1 and December is 12. The output is sorted by day of the month. Add an OUTPUT statement to save it as a CSV file to print onto labels using Word’s mail-merge feature. See http://www.sosoft.com/queries/how2010/ for more details about exporting the query results.

In this example, we are specifying May birthdates  — MONTH(dob) = 5 — and primary provider code of ‘AB’ —  priprvcode = ‘AB’.

SELECT 
  dob,firstname, lastname, addr1, addr2, city, state, zip
FROM
  sos.rv_patients
WHERE
  MONTH(dob) = 5 
  AND flag = 0
  AND priprvcode = 'AB'
ORDER BY 
  DAY(dob)

Fees and Payments for Period, Grouped by Patient Category

For multiple patient categories, we would like totals billed AND collected for a specified date range.

The following query provides the desired totals, but it is important to note that the payment total is a total of any payments received during the period, not just payments for services rendered during the period. In other words, the payments probably include those for previous services and maybe even some prepayments. Also note that we are using SPLIT amounts for both charges and credits, so if you have corruption that makes the sum of the splits on a transaction different than the transaction amount, the total won’t match reports or queries that use the main amount field.

The following is actually TWO similar queries, one for payments and one for fees, with the use of  UNION to combine the results of each query into a single result set. UNIONed queries must not have their own ORDER statement. Instead, at the end you can specify an ORDER BY followed by the number of the column on which you want to sort, as shown below.

SELECT 
  c.categcode AS Category 
  ,'Payments' AS "Type"
  ,SUM(COALESCE(b.crsplamt,0)) AS "Total"
FROM 
  sos.patients a 
  JOIN sos.rv_creditsplits b ON a.ptnum = b.ptnum 
  JOIN sos.ptcategs c ON a.ptcategnum = c.ptcategnum
WHERE 
  a.licnum = 101
  AND b.credtype IN ('CASH','CHECK','CHARGE','OTHER')
  AND b.cre_date BETWEEN '2000-01-01' AND '2014-12-31'
GROUP BY 
  c.categcode

UNION

SELECT 
  c.categcode AS Category 
  ,'Fees' 
  ,SUM(COALESCE(b.chgsplamt,0)) 
FROM 
  sos.patients a 
  JOIN sos.rv_charges b ON a.ptnum = b.ptnum 
  JOIN sos.ptcategs c ON a.ptcategnum = c.ptcategnum
WHERE 
  a.licnum = 101
  AND b.trandate BETWEEN '2000-01-01' AND '2014-12-31'
GROUP BY 
  c.categcode
ORDER BY 
  1,2

Simple Export of Basic Patient Information

I need to export the following fields in Excel or .csv format:

Last name, first name, address (all fields), primary phone, email (optional), DOB, sex, primary provider, diagnosis, primary insurance carrier, insured ID#

 

Instead of going directly to CSV, we recommend exporting in HTML format instead. You can do a quick check on the results by just double-clicking the file (in this example, the results are saved in the file name MYEXPORT.HTML in the SOS folder). You can then open the file in Excel by starting Excel and using File > Open, being careful to select the appropriate file type. In Excel 2013 the type is “All Web Pages”. The results will appear with each data element in its own column and a heading at the top of each column. At that point, you can manipulate the data as you like and save as CSV or any other format supported by Excel.

2014-06-16_17-38-08

Here is the query, including the OUTPUT line that saves the results as an HTML file. Note that the WHERE clause filters the results to include only patients in dataset 101 (licnum = 101), who appear in the Active Patient List (flag = 0), and who have not been discharged (discharge date IS NULL). You can change or remove any of these filters as needed, or add new ones.

SELECT
  a.lastname
  ,a.firstname
  ,a.addr1
  ,a.addr2
  ,a.city
  ,a.state
  ,a.zip
  ,(a.phone1area+'-'+a.phone1) AS phone
  ,a.dob
  ,a.sex
  ,b.provlname+', '+b.provfname AS primary_provider
  ,d.dxcode
  ,c.payorname AS primary_insurance
  ,c.insdid AS insured_id
FROM
  sos.rv_patients a
  JOIN sos.providers b ON a.priprvcode = b.provcode
  JOIN sos.rv_policies c ON a.ptnum = c.ptnum
  JOIN sos.dx d ON a.dx1=d.dxnum
WHERE
  a.licnum = 101
  AND b.licnum = 101
  AND a.flag = 0
  AND a.dischargedate IS NULL
  AND c.inspos = 1
ORDER BY
  a.lastname,a.firstname,a.id
;
OUTPUT TO c:\sos\myexport.html FORMAT html
;