Identify open cases for each week during a year - sql

I am trying to produce a report which identifies client cases which were open during each week of a year. Currently I have the following SQL which returns all clients with an indicator on whether their case was open during week 1 of our calendar. A client has two aspects which identifies if their case is open - their MOV_START_DATE and their ESU_START DATE should be greater than end date of the period, and their MOV_END_DATE/ESU_START DATE should be either null or greater than the start date of the period.
The below code works, but I thought I could just copy the left join WK1 and rename it WK2 to return information for week 2 but I'm getting an error relating to ambiguously named columns. Additionally, I'm guessing that having 52 (one for each week) left joins on a report isn't particularly advisable, so again I'm wondering if there is a better way of achieving this?
SELECT
A.ESU_PER_GRO_ID,
A.ESU_ID,
A.STATUS,
B.MOV_ID,
B.MOV_START_DATE,
B.MOV_END_DATE,
A.ESU_START_DATE,
A.ESU_END_DATE,
LS.CLS_DESC,
nvl2(wk1.PRD_PERIOD_NUM,'Y','N') as "Week1"
FROM
A
LEFT JOIN B ON B.MOV_PER_GRO_ID = A.ESU_PER_GRO_ID
LEFT JOIN LS ON LS.CLS_CODE = A.STATUS
LEFT JOIN O_PERIODS WK1 ON B.MOV_START_DATE < WK1.PRD_END_DATE
AND (B.MOV_END_DATE IS NULL OR B.MOV_END_DATE > WK1.PRD_START_DATE)
AND A.ESU_START_DATE < WK1.PRD_END_DATE
AND (A.ESU_END_DATE IS NULL OR A.ESU_END_DATE > WK1.PRD_START_DATE)
AND PRD_CAL_ID = 'E1190' AND WK1.PRD_PERIOD_NUM = 1 AND WK1.PRD_YEAR = 2012
WHERE
B.MOV_START_DATE Is Not Null
AND A.STATUS <> ('X')
Hopefully I have provided enough information, but if not, I am happy to answer questions. Thanks!
Sample Data (Produced by above query)
P ID ESU_ID STATUS MOV_ID M_START M_END DESC Week1
1 ESU1 New 1M 01/01/2012 Boo Y
2 ESU2 New 2M 01/03/2012 Boo N
Desired output (Week1 - Week 52)
P ID ESU_ID STATUS MOV_ID M_START M_END DESC Week1 Week2
1 ESU1 New 1M 01/01/2012 Boo Y Y
2 ESU2 New 2M 01/03/2012 Boo N N

I suspect that the reason creating a WK2 join like WK1 didn't work was that the column PRD_CAL_ID didn't have a table alias on it. However, as you guessed, 52 joins is probably not going to perform very well. Try the following:
SELECT A.ESU_PER_GRO_ID,
A.ESU_ID,
A.STATUS,
B.MOV_ID,
B.MOV_START_DATE,
B.MOV_END_DATE,
A.ESU_START_DATE,
A.ESU_END_DATE,
LS.CLS_DESC,
'Week' || TRIM(TO_CHAR(pd.PRD_PERIOD_NUM)) WEEK_DESC
FROM A
LEFT JOIN B
ON B.MOV_PER_GRO_ID = A.ESU_PER_GRO_ID
LEFT JOIN LS
ON LS.CLS_CODE = A.STATUS
LEFT JOIN O_PERIODS pd
ON B.MOV_START_DATE < pd.PRD_END_DATE AND
(B.MOV_END_DATE IS NULL OR
B.MOV_END_DATE > pd.PRD_START_DATE) AND
A.ESU_START_DATE < pd.PRD_END_DATE AND
(A.ESU_END_DATE IS NULL OR
A.ESU_END_DATE > pd.PRD_START_DATE)
WHERE B.MOV_START_DATE Is Not Null AND
A.STATUS <> ('X') AND
pd.PRD_CAL_ID = 'E1190' AND
pd.PRD_YEAR = 2012
ORDER BY WEEK_DESC
This produces slightly different results than your original query, having a WEEK_DESC instead of trying to create 52 different columns, one for each week, but I think it will perform better.
Share and enjoy.

Related

How can I replace the LAST() function in MS Access with proper ordering on a rather large table?

I have an MS Access database with the two tables, Asset and Transaction. The schema looks like this:
Table ASSET
Key Date1 AType FieldB FieldC ...
A 2023.01.01 T1
B 2022.01.01 T1
C 2023.01.01 T2
.
.
TABLE TRANSACTION
Date2 Key TType1 TType2 TType3 FieldOfInterest ...
2022.05.31 A 1 1 1 10
2022.08.31 A 1 1 1 40
2022.08.31 A 1 2 1 41
2022.09.31 A 1 1 1 30
2022.07.31 A 1 1 1 30
2022.06.31 A 1 1 1 20
2022.10.31 A 1 1 1 45
2022.12.31 A 2 1 1 50
2022.11.31 A 1 2 1 47
2022.05.23 B 2 1 1 30
2022.05.01 B 1 1 1 10
2022.05.12 B 1 2 1 20
.
.
.
The ASSET table has a PK (Key).
The TRANSACTION table has a composite key that is (Key, Date2, Type1, Type2, Type3).
Given the above tables let's see an example:
Input1 = 2022.04.01
Input2 = 2022.08.31
Desired result:
Key FieldOfInterest
A 41
because if the Transactions in scope was to be ordered by Date2, TType1, TType2, TType3 all ascending then the record having FieldOfInterest = 41 would be the last one.
Note that Asset B is not in scope due to Asset.Date1 < Input1, neither is Asset C because AType != T1. Ultimately I am curious about the SUM(FieldOfInterest) of all the last transactions belonging to an Asset that is in scope determined by the input variables.
The following query has so far provided the right results but after upgrading to a newer MS Access version, the LAST() operation is no longer reliably returning the row which is the latest addition to the Transaction table.
I have several input values but the most important ones are two dates, lets call them InputDate1 and
InputDate2.
This is how it worked so far:
SELECT Asset.AType, Last(FieldOfInterest) AS CurrentValue ,Asset.Key
FROM Transaction
INNER JOIN Asset ON Transaction.Key = Asset.Key
WHERE Transaction.Date2 <= InputDate2 And Asset.Date1 >= InputDate1
GROUP BY Asset.Key, Asset.AType
HAVING Asset.AType='T1'
It is known that the grouped records are not guaranteed to be in any order. Obviously it is a mistake to rely on the order of the records of the group by operation will always keep the original table order but lets just ignore this for now.
I have been struggling to come up with the right way to do the following:
join the Asset and Transaction tables on Asset.Key = Transaction.Key
filter by Asset.Date1 >= InputDate1 AND Transaction.Date2 <= InputDate2
then I need to select one record for all Transaction.Key where Date2 and TType1 and TType2 and TType3 has the highest value. (this represents the actual last record for given Key)
As far as I know there is no way to order records within a group by clause which is unfortunate.
I have tried Ranking, but the Transactions table is large (800k rows) and the performance was very slow, I need something faster than this. The following are an example of three saved queries that I wrote and chained together but the performance is very disappointing probably due to the ranking step.
-- Saved query step_1
SELECT Asset.*, Transaction.*
FROM Transaction
INNER JOIN Asset ON Transaction.Key = Asset.Key
WHERE Transaction.Date2 <= 44926
AND Asset.Date1 >= 44562
AND Asset.aType = 'T1'
-- Saved query step_2
SELECT tr.FieldOfInterest, (SELECT Count(*) FROM
(SELECT tr2.Transaction.Key, tr2.Date2, tr2.Transaction.tType1, tr2.tType2, tr2.tType3 FROM step_1 AS tr2) AS tr1
WHERE (tr1.Date2 > tr.Date2 OR
(tr1.Date2 = tr.Date2 AND tr1.tType1 > tr.Transaction.tType1) OR
(tr1.Date2 = tr.Date2 AND tr1.tType1 = tr.Transaction.tType1 AND tr1.tType2 > tr.tType2) OR
(tr1.Date2 = tr.Date2 AND tr1.tType1 = tr.Transaction.tType1 AND tr1.tType2 = tr.tType2 AND tr1.tType3 > tr.tType3))
AND tr1.Key = tr.Transaction.Key)+1 AS Rank
FROM step_1 AS tr
-- Saved query step_3
SELECT SUM(FieldOfInterest) FROM step_2
WHERE Rank = 1
I hope I am being clear enough so that I can get some useful recommendations. I've been stuck with this for weeks now and really don't know what to do about it. I am open for any suggestions.
Reading the following specification
then I need to select one record for all Transaction.Key where Date2 and TType1 and TType2 and TType3 has the highest value. (this represents the actual last record for given Key)
Consider a simple aggregation for step 2 to retrieve the max values then in step 3 join all fields to first query.
Step 1 (rewritten to avoid name collision and too many columns)
SELECT a.[Key] AS Asset_Key, a.Date1, a.AType,
t.[Key] AS Transaction_Key, t.Date2,
t.TType1, t.TType2, t.TType3, t.FieldOfInterest
FROM Transaction t
INNER JOIN Asset a ON a.[Key] = a.[Key]
WHERE t.Date2 <= 44926
AND a.Date1 >= 44562
AND a.AType = 'T1'
Step 2
SELECT Transaction_Key,
MAX(Date2) AS Max_Date2,
MAX(TType1) AS TType1,
MAX(TType2) AS TType2,
MAX(TType3) AS TType3
FROM step_1
GROUP Transaction_Key
Step 3
SELECT s1.*
FROM step_1 s1
INNER JOIN step_2 s2
ON s1.Transaction_Key = s2.Transaction_Key
AND s1.Date2 = s2.Max_Date2
AND s1.TType1 = s2.Max_TType1
AND s1.TType2 = s2.Max_TType2
AND s1.TType3 = s2.Max_TType3

How do you join a table with a different WHERE condition after you already used a join

Hi i have 2 tables employees and medical leaves related through the employee ID, basically i want to make a result set where there is one column that filters by month and year, and another column that filters by year only
EMPLOYEES MEDICAL
|employee|ID| |ID|DateOfLeave|
A 1 1 2019/1/3
B 2 1 2019/4/15
C 3 2 2019/5/16
D 4
select employees.employee,Employees.ID,count(medical.dateofleave) as
NumberofLeaves
from employees
left outer join Medical on employees.emp = MedBillInfo.emp
and month(medbillinfo.date) in(1) and year(medbillinfo.date) in (2019)
group by Employees.employee,employees.ID
RESULT SET
|Employee|ID|NumberOfLeaves|YearlyLeaves|--i want to join this column
A 1 1 2
B 2 0 1
C 3 0 0
D 4 0 0
But i have no idea how to write inside the current sql statement to join a yearly leaves column to my current result set which is only employee,id and numberofleaves
I think you want conditional aggregation:
select e.employee, e.ID,
count(*) as num_leaves,
sum(case when month(m.date) = 1 then 1 else 0 end) as num_leaves_in_month_1
from employees e left join
Medical m
on e.emp = m.emp
where m.date >= '2019-01-01' and m.date < '2020-01-01'
group by e.employee, e.ID;
Notes:
This removes the where clause which seems to refer to a non-existent table alias.
The date arithmetic uses direct comparisons rather than functions.
This introduces table aliases so the question is easier to write and to read.
Your question probably needs to be corrected as the group by condition does not match with select columns. But based on what you asked, I think you need to use truncate date function in order to group the leaves by year. For SQL Server, there is YEAR(date) function which returns the year of the given date. This date would be MEDICAL.DateOfLeave in your case.

DB2 SQL - Difference (minus) of CASE WHEN

I am trying to do the difference between 2 case when in a DB2 environment as follow:
select DISTINCT
TLORDER.BILL_NUMBER,
TLORDER.XCHARGES,
TLORDER.CHARGES,
TLORDER.DISTANCE,
TLORDER.CREATED_TIME,
TLORDER.DESTCITY,
(CASE WHEN ODRSTAT.STATUS = '5ARRCONS' THEN MAX(ORDSTAT.CHANGED)
END -
CASE WHEN ODRSTAT.STATUS = 'PICKD' THEN MIN(ODRSTAT.CHANGED)
END) AS DETENTION
FROM ODRSTAT
LEFT JOIN TLORDER ON ODRSTAT.ORDER_ID = TLORDER.DETAIL_LINE_ID
I tried a few variation of this concept using the various online ressources and found many answers for sums but not for difference using the same columns.
The goal is to substract the oldest date (in col. CHANGED) if status in Col. Status is 'pickd' from the newest date (in the same col CHANGED) if the status in Col. STATUS is '5arrcons'
Consider the following dataset:
Key ORDER_ID STATUS CHANGED
1 10 5ARRCONS 12/10/2017
2 10 OTHER 12/10/2017
3 10 PICKD 12/5/2017
4 10 OTHER 12/3/2017
5 10 PICKD 12/1/2017
In this case the wanted result from the CASE statement would be
MAX = 12/10/2017
Min = 12/1/2017
so (12/10/2017 - 12/1/2017) equals 9
9 is what I would want in what is returned
Any and all help will be appreciated.
thank you for your time
It is hard to tell exactly you want want. But, this may be what you want:
SELECT o.BILL_NUMBER, o.XCHARGES, o.CHARGES, o.DISTANCE, o.CREATED_TIME, o.DESTCITY,
(MAX(CASE WHEN os.STATUS = '5ARRCONS' THEN os.CHANGED END) -
MIN(CASE WHEN os.STATUS = 'PICKD' THEN os.CHANGED END)
) AS DETENTION
FROM ODRSTAT os JOIN
TLORDER o
ON os.ORDER_ID = o.DETAIL_LINE_ID
GROUP BY o.BILL_NUMBER, o.XCHARGES, o.CHARGES, o.DISTANCE, o.CREATED_TIME, o.DESTCITY;

how to perform date calculations from different tables?

Please forgive me if this is a basic question, I'm a beginner in SQL and need some help performing date calculations from 2 tables in SQL.
I have two tables (patient and chd) they look like this:
Patient:
ID|Age|date |Alive
--------------------------
1 50 01/09/2013 Y
2 52 11/05/2015 N
3 19 20/07/2016 N
CHD:
ID|Age|indexdate
--------------------
1 50 01/08/2012
2 52 11/11/2013
3 19 10/07/2015
The patient table contains about 500,000 records from 2010-2016 and the CHD table contains about 350,000 records from 2012-2013. What I want to do is see how many CHD patients have died from 2012-2016, and if they have died has 12months passed?
I'm not sure how to do this but I know a join is needed on the ID and we set the where condition with alive as NOT 'Y'
The final output should look like this based on the sample above:
ID|Age|indexdate| deathdate
---------------------------
2 52 11/11/2013 11/05/2015
3 19 10/07/2016 20/07/2016
Any questions let me know!
EDIT: just to make it clear, patients can appear multiple times in the patient table until they die.
Thanks
Let me assume that this query gets the date of death from the patient table:
select p.id, min(p.date) as deathdate
from patient p
where p.Alive = 'N'
group by p.id;
Then, you can get what you want with a join:
select count(*)
from chd c join
(select p.id, min(p.date) as deathdate
from patient p
where p.Alive = 'N'
group by p.id
) pd
on c.id = pd.id;
You can then address your questions with a where clause in the outer query. For instance:
where deathdate >= current_date - interval '1 year'

Adding in missing dates from results in SQL

I have a database that currently looks like this
Date | valid_entry | profile
1/6/2015 1 | 1
3/6/2015 2 | 1
3/6/2015 2 | 2
5/6/2015 4 | 4
I am trying to grab the dates but i need to make a query to display also for dates that does not exist in the list, such as 2/6/2015.
This is a sample of what i need it to be:
Date | valid_entry
1/6/2015 1
2/6/2015 0
3/6/2015 2
3/6/2015 2
4/6/2015 0
5/6/2015 4
My query:
select date, count(valid_entry)
from database
where profile = 1
group by 1;
This query will only display the dates that exist in there. Is there a way in query that I can populate the results with dates that does not exist in there?
You can generate a list of all dates that are between the start and end date from your source table using generate_series(). These dates can then be used in an outer join to sum the values for all dates.
with all_dates (date) as (
select dt::date
from generate_series( (select min(date) from some_table), (select max(date) from some_table), interval '1' day) as x(dt)
)
select ad.date, sum(coalesce(st.valid_entry,0))
from all_dates ad
left join some_table st on ad.date = st.date
group by ad.date, st.profile
order by ad.date;
some_table is your table with the sample data you have provided.
Based on your sample output, you also seem to want group by date and profile, otherwise there can't be two rows with 2015-06-03. You also don't seem to want where profile = 1 because that as well wouldn't generate two rows with 2015-06-03 as shown in your sample output.
SQLFiddle example: http://sqlfiddle.com/#!15/b0b2a/2
Unrelated, but: I hope that the column names are only made up. date is a horrible name for a column. For one because it is also a keyword, but more importantly it does not document what this date is for. A start date? An end date? A due date? A modification date?
You have to use a calendar table for this purpose. In this case you can create an in-line table with the tables required, then LEFT JOIN your table to it:
select "date", count(valid_entry)
from (
SELECT '2015-06-01' AS d UNION ALL '2015-06-02' UNION ALL '2015-06-03' UNION ALL
'2015-06-04' UNION ALL '2015-06-05' UNION ALL '2015-06-06') AS t
left join database AS db on t.d = db."date" and db.profile = 1
group by t.d;
Note: Predicate profile = 1 should be applied in the ON clause of the LEFT JOIN operation. If it is placed in the WHERE clause instead then LEFT JOIN essentially becomes an INNER JOIN.