Get the rest of the row in a max group by - sql

I'm trying to acquire the most recently passed training someone has taken. To do this, I have a view that works great
CREATE OR REPLACE FORCE VIEW MYAPP.most_recent_training (
employee_id, course_id, date_taken
) AS SELECT
who.employee_id,
course.course_id,
MAX(sess.end_date) date_taken
FROM employee_session_join esj
JOIN training_session sess on sess.session_id = esj.session_id
JOIN course_version vers on vers.version_id = sess.version_id
JOIN course course on course.course_id = vers.course_id
JOIN employee who on who.employee_id = esj.employee_id
WHERE esj.active_flag = 'Y'
AND sess.active_flag = 'Y'
AND course.active_flag = 'Y'
AND who.active_flag = 'Y'
AND esj.approval_status = 5 -- successfully passed
GROUP BY who.employee_id, course.course_id
Okay, so my query works excellent. Here's my problem - I also need the expiry date so I know when they go out of compliance. This is stored as a number of months on the version. But I can't add vers.valid_for_months because it complains ORA-00979: not a GROUP BY expression.
I just want to get whatever the rest of that row is. How can I do this?

I would think this would solve your problem:
SELECT who.employee_id, course.course_id,
MAX(add_months(sess.end_date, vers.valid_for_months))
That gets the latest end date. If you want the end date for the last session, use row_number():
SELECT employee_id, course_id, end_date
FROM (SELECT who.employee_id, course.course_id, sess.end_date,
row_number() over (partition by who.employee_id, course.course_id
order by sess.end_date
) as seqnum
FROM employee_session_join esj
JOIN training_session sess on sess.session_id = esj.session_id
JOIN course_version vers on vers.version_id = sess.version_id
JOIN course course on course.course_id = vers.course_id
JOIN employee who on who.employee_id = esj.employee_id
WHERE esj.active_flag = 'Y'
AND sess.active_flag = 'Y'
AND course.active_flag = 'Y'
AND who.active_flag = 'Y'
AND esj.approval_status = 5 -- successfully passed
) e
WHERE seqnum = 1;

Related

SQL remove duplicates from results

select DAC.LocationCode, DAC.Description, ReqApp.Rank, App.Approver as UserName,
CASE WHEN app.Approver = app.AlternateApprover THEN ''
ELSE AltApp.AlternateApprover END As AltApprover,
ISNULL(CONVERT(Varchar,AltApp.FromDate,101),'')AS FromDate,
ISNULL(CONVERT(Varchar,AltApp.ToDate,101),'')AS ToDate
from tblAPAlternateApprovers App
INNER JOIN tblAPAlternateApprovers AltApp
ON App.ID = AltApp.ID
INNER JOIN tblAPReqLocations DAC
ON App.tblAPReqLocationsID = DAC.ID
INNER JOIN tblAPReqApprover ReqApp
ON App.Approver = ReqApp.Approver AND
App.tblAPReqLocationsID = ReqApp.LocationID
ORDER BY DAC.LocationCode ASC, ReqApp.Rank asc
Output
When SQL Adds an 'alternate approver' (for purchase orders), it creates an additional record for the actual approver. So, trying to find a way to show only 1 record for those approvers that also have alternates. i.e. 'jlhayes' has 2 records. One with an alternate and one without. For these records, I want to only see the ones that have an alternate.Thank you for your help. I've spend a couple hours and out of ideas.
You can wrap AltApprover case statement in max(AltApprover) and group by DAC.LocationCode, DAC.Description, ReqApp.Rank, App.Approver and do similarly for FromDate and ToDate:
select DAC.LocationCode, DAC.Description, ReqApp.Rank, App.Approver as UserName,
max(CASE WHEN app.Approver = app.AlternateApprover THEN ''
ELSE AltApp.AlternateApprover END) As AltApprover,
max(ISNULL(CONVERT(Varchar,AltApp.FromDate,101),'')) AS FromDate,
max(ISNULL(CONVERT(Varchar,AltApp.ToDate,101),'')) AS ToDate
from tblAPAlternateApprovers App
INNER JOIN tblAPAlternateApprovers AltApp
ON App.ID = AltApp.ID
INNER JOIN tblAPReqLocations DAC
ON App.tblAPReqLocationsID = DAC.ID
INNER JOIN tblAPReqApprover ReqApp
ON App.Approver = ReqApp.Approver AND
App.tblAPReqLocationsID = ReqApp.LocationID
GROUP BY DAC.LocationCode, DAC.Description, ReqApp.Rank, App.Approver
ORDER BY DAC.LocationCode ASC, ReqApp.Rank asc

Should a subquery on a join use tables from an outer query in the where clause?

I need to add a subquery to a join, because one payment can have more than one allotment, so I only need to account for the first match (where rownum = 1).
However, I'm not sure if adding pmt from the outer query to the subquery on the allotment join is best.
Should I be doing this differently in the event of performance hits, etc.. ?
SELECT
pmt.payment_uid,
alt.allotment_uid,
FROM
payment pmt
/* HERE: is the reference to pmt.pay_key and pmt.client_id
incorrect in the below subquery? */
INNER JOIN allotment alc ON alt.allotment_uid = (
SELECT
allotment_uid
FROM
allotment
WHERE
pay_key = pmt.pay_key
AND
pay_code = 'xyz'
AND
deleted = 'N'
AND
client_id = pmt.client_id
AND
ROWNUM = 1
)
WHERE
AND
pmt.deleted = 'N'
AND
pmt.date_paid >= TO_DATE('2017-07-01')
AND
pmt.date_paid < TO_DATE('2017-10-01') + 1;
It's difficult to identify the performance issue in your query without seeing an explain plan output. You query does seem to do an additional SELECT on the allotment for every record from the main query.
Here is a version which doesn't use correlated sub query. Obviously I haven't been able to test it. It does a simple join in and then filters all records except one of the allotments. Hope this helps.
WITH v_payment
AS
(
SELECT
pmt.payment_uid,
alt.allotment_uid,
ROW_NUMBER () OVER(PARTITION BY allotment_id) r_num
FROM
payment pmt JOIN allotment alt
ON (pmt.pay_key = alt.pay_key AND
pmt.client_id = alt.client_id)
WHERE pmt.deleted = 'N' AND
pmt.date_paid >= TO_DATE('2017-07-01') AND
pmt.date_paid < TO_DATE('2017-10-01') + 1 AND
alt.pay_code = 'xyz' AND
alt.deleted = 'N'
)
SELECT payment_uid,
allotment_uid
FROM v_payment
WHERE r_num = 1;
Let's know how this performs!
You can phrase the query that way. I would be more likely to do:
SELECT . . .
FROM payment p INNER JOIN
(SELECT a.*,
ROW_NUMBER() OVER (PARTITION BY pay_key, client_id
ORDER BY allotment_uid
) as seqnum
FROM allotment a
WHERE pay_code = 'xyz' AND deleted = 'N'
) a
ON a.pay_key = p.pay_key AND a.client_id = p.client_id AND
seqnum = 1
WHERE p.deleted = 'N' AND
p.date_paid >= DATE '2017-07-01' AND
p.date_paid < (DATE '2017-10-01') + 1;

First event per patients

The attached code is supposed to return the first ORDER_PROC.ORDER_INST for each patient. I'm getting multiple records in some cases.
Any suggestions on a better approach?
Thanks
Steve
SELECT DISTINCT
ORDER_PROC.PAT_ENC_CSN_ID as ordercsn, Min(ORDER_PROC.ORDER_INST) as
CodeStatus_Datetime, CLARITY_SER.PROV_NAME as CodeStatus_OrderProvider
FROM
ORDER_PROC with(nolock) , ORDER_METRICS with(nolock) , CLARITY_SER
with(nolock)
WHERE
ORDER_PROC.ORDER_PROC_ID = ORDER_METRICS.ORDER_ID AND
ORDER_METRICS.ORDERING_PROV_ID = CLARITY_SER.PROV_ID AND
--ORDER_PROC.REASON_FOR_CANC_C IS NULL AND
(ORDER_PROC.PROC_CODE = 'COD1' OR
ORDER_PROC.PROC_CODE = 'COD2' OR
ORDER_PROC.PROC_CODE = 'COD3'
)
GROUP by
ORDER_PROC.PAT_ENC_CSN_ID, ORDER_PROC.ORDER_INST,CLARITY_SER.PROV_NAME
Use ROW_NUMBER() to create partition for each patient
SELECT *
FROM (
SELECT OP.PAT_ENC_CSN_ID as ordercsn,
OP.ORDER_INST,
CodeStatus_Datetime,
CS.PROV_NAME as CodeStatus_OrderProvider,
ROW_NUMBER() OVER (PARTITION BY OP.PAT_ENC_CSN_ID
ORDER BY OP.ORDER_INST) as rn
FROM ORDER_PROC OP
JOIN ORDER_METRICS OM
OP.ORDER_PROC_ID = OM.ORDER_ID
JOIN CLARITY_SER CS
OM.ORDERING_PROV_ID = CS.PROV_ID
WHERE
OP.PROC_CODE IN ('COD1','COD2','COD3')
) as T
WHERE rn = 1

Distinct, count, group by query madness

I am trying to return a count of tests taken per term. I can get the count to return, but I can't get it grouped by term.
I've tried everything and the closest I get is grouping by term but then my count only = 1, which isn't right.
Here is what I have now. It just returns a count, how do I group it by term_id?
SELECT COUNT(*)
FROM (SELECT DISTINCT ON(student_id, test_event_id, terf.term_id) student_id
FROM report.test_event_result_fact terf
JOIN report.growth_measurement_window gw on gw.term_id = terf.term_id
JOIN report.term t on t.term_id = terf.term_id
JOIN report.test tt on tt.test_id = terf.test_id
WHERE terf.partner_id = 98
AND growth_event_yn = 't'
AND gw.test_window_complete_yn = 't'
AND gw.growth_window_type = 'DISTRICT'
AND tt.test_type_description = 'SURVEY_WITH_GOALS') as TestEvents
Without knowing more about your setup, that's my best bet:
select term_id, count(*) AS count_per_term
from (
select Distinct on (student_id, test_event_id, terf.term_id)
terf.term_id, student_id
from report.test_event_result_fact terf
join report.growth_measurement_window gw using (term_id)
join report.term t using (term_id)
join report.test tt using (term_id)
where terf.partner_id = 98
and growth_event_yn = 't'
and gw.test_window_complete_yn = 't'
and gw.growth_window_type = 'DISTRICT'
and tt.test_type_description = 'SURVEY_WITH_GOALS') as TestEvents
group by 1;

SQL SP runs Slow

Hi
I am using an SP which takes 7 minutes in a server which has 7336 recrds
and 6seconds in another server which has 3500 records.
Can anybody help me to know why is it happening?
Thanks,
-Divya
THE SP:
SELECT WORKSHEET_ID
FROM PERSON PER
INNER JOIN PERSON EMPLEE
ON EMPLEE.PERSON_ID = PER.PERSON_ID
AND
dbo.FN_CHECKRPTSECURITY(EMPLEE.PERSON_ID, #p_SEC_ACCOUNT_ID) > 0
LEFT JOIN SEARCH_ASSIGNMENT_VW PERSON_ASGN
ON PERSON_ASGN.ASSIGNMENT_ID =
dbo.FN_GETRPTASSIGNMENTID(EMPLEE.PERSON_ID)
LEFT JOIN LOOKUP EMPLEE_ASGN_STAT
ON EMPLEE_ASGN_STAT.TYPE_ = 'ASSIGNMMENT_STATUS_CODE'
AND EMPLEE_ASGN_STAT.CODE = PERSON_ASGN.ASGN_STAT_CODE
INNER JOIN
(SELECT w1.ASSIGNMENT_ID, w1.WORKSHEET_ID, w1.EFFECTIVE_DATE, w1.APPROVED_BY, w3.CREATED_BY
FROM WORKSHEET_PAYROLL_VW w1
INNER JOIN WORKSHEET w3
ON w3.WORKSHEET_ID = w1.WORKSHEET_ID
WHERE w1.EFFECTIVE_DATE = CASE
WHEN #p_MOST_RECENT_ONLY = 'Y'
THEN (SELECT MAX(w2.EFFECTIVE_DATE)
FROM WORKSHEET_PAYROLL_VW w2
WHERE w1.ASSIGNMENT_ID = w2.ASSIGNMENT_ID
AND (ISNULL(#p_WKS_EFFECTIVE_DATE,w2.EFFECTIVE_DATE) =w2.EFFECTIVE_DATE))
ELSE ISNULL(#p_WKS_EFFECTIVE_DATE,w1.EFFECTIVE_DATE)
END
)
PERSON_WKS
ON PERSON_WKS.ASSIGNMENT_ID = dbo.FN_GETRPTASSIGNMENTID(EMPLEE.PERSON_ID)
INNER JOIN
(SELECT ASSIGNMENT_ID, VALUE
FROM ASSIGNMENT_HISTORY AH
WHERE FIELD_NAME ='HOME PAYROLL GROUP'
AND EFFECTIVE_DATE = (SELECT MAX(EFFECTIVE_DATE)
FROM ASSIGNMENT_HISTORY
WHERE ASSIGNMENT_ID = AH.ASSIGNMENT_ID
AND EFFECTIVE_DATE <=getDate()
AND FIELD_NAME = 'HOME PAYROLL GROUP')
)HOME_PAYROLL
ON HOME_PAYROLL.ASSIGNMENT_ID = dbo.FN_GETRPTASSIGNMENTID(EMPLEE.PERSON_ID)
WHERE
(#p_SELECTED_PERSON_ONLY = 'N' OR EMPLEE.PERSON_ID = #p_PERSON_ID)
AND
(#p_ASGN_STAT_CODE IS NULL OR PERSON_ASGN.ASGN_STAT_CODE = SUBSTRING(#p_ASGN_STAT_CODE,1,1)
OR PERSON_ASGN.ASGN_STAT_CODE = SUBSTRING(#p_ASGN_STAT_CODE,2,1))
AND
(#p_POLICY_ID IS NULL OR PERSON_ASGN.PROGRAM_CODE = #p_POLICY_ID)
AND
(#p_HOME_COUNTRY_ID IS NULL OR PERSON_ASGN.HOMECOUNTRYID=#p_HOME_COUNTRY_ID)
AND
(#p_HOME_CITY_ID IS NULL OR PERSON_ASGN.HOMECITYID=#p_HOME_CITY_ID )
AND
(#p_HOME_COMPANY_ID IS NULL OR PERSON_ASGN.HOMEBUSINESSID=#p_HOME_COMPANY_ID )
AND
(#p_HOME_DIVISION_ID IS NULL OR PERSON_ASGN.HOMECOMPONENTID=#p_HOME_DIVISION_ID )
AND
(#p_HOST_COUNTRY_ID IS NULL OR PERSON_ASGN.HOSTCOUNTRYID=#p_HOST_COUNTRY_ID )
AND
(#p_HOST_CITY_ID IS NULL OR PERSON_ASGN.HOSTCITYID=#p_HOST_CITY_ID )
AND
(#p_HOST_COMPANY_ID IS NULL OR PERSON_ASGN.HOSTBUSINESSID=#p_HOST_COMPANY_ID )
AND
(#p_HOST_DIVISION_ID IS NULL OR PERSON_ASGN.HOSTCOMPONENTID=#p_HOST_DIVISION_ID )
AND
(#p_CREATED_BY IS NULL OR PERSON_WKS.CREATED_BY=#p_CREATED_BY )
AND
(#p_APPROVED_BY IS NULL OR PERSON_WKS.APPROVED_BY=#p_APPROVED_BY )
AND
(#p_payroll_code IS NULL OR HOME_PAYROLL.VALUE=#p_payroll_code )
ORDER BY PER.LAST_NAME ASC,
PER.FIRST_NAME ASC,
PERSON_WKS.EFFECTIVE_DATE DESC
The Function in the 5th line is the one which is running slow. rest of the part is running in 4secs
The FUNCTION:
BEGIN
DECLARE
#v_ASGN_COUNT INT,
#v_RESULT INT
SELECT #v_ASGN_COUNT = COUNT(ASSIGNMENT_ID) --to find out if this employee has any assignment
FROM ASSIGNMENT
WHERE EXPATRIATE_PERSON_ID = #p_PERSON_ID AND
ASGN_STAT_CODE IN ('PD','A','I')
IF(#v_ASGN_COUNT > 0) --yes assignment, check against SECURITY_ASSIGNMENT_VW
BEGIN
SELECT #v_RESULT = COUNT(ASSIGNMENT_ID)
FROM SECURITY_ASSIGNMENT_VW
WHERE SEC_ACCOUNT_ID = #p_SEC_ACCOUNT_ID AND
ASSIGNMENT_ID IN (SELECT ASSIGNMENT_ID
FROM ASSIGNMENT
WHERE EXPATRIATE_PERSON_ID = #p_PERSON_ID AND
ASGN_STAT_CODE IN ('PD','A','I'))
END
ELSE --no assignment, so check against SECURITY_PERSON_VW
BEGIN
SELECT #v_RESULT = COUNT(PERSON_ID)
FROM SECURITY_PERSON_VW
WHERE SEC_ACCOUNT_ID = #p_SEC_ACCOUNT_ID AND
PERSON_ID = #p_PERSON_ID
END
RETURN #v_RESULT
END
Do the schemas match exactly... in particular check for missing indexes.
Well to begin with you have scalar functions which will run significantly slower as the number of records increase becasue they process row-by-agonizing-row. Not only that you've used the functions in joins which is a horrible practice if you need performance. You have a bunch of OR conditions which tend to slowness. And while it is too hard to actually read the code you posted (please try to format and only use all caps for keywords), I would suspect that some of those conditions are not sargable.
To know what is actually happening check the Execution plan (SQL Server) or Explain Plan (mySQL and others I think) or the equivalent feature in your database. Likely you wil find table scans which of course are going to get significantly slower as the number of records increases.
You may also have a problem with parameter sniffing. Please google to see how to fix that.
One improvement would be to make sure that dbo.FN_GETRPTSSIGNMENTID only gets executed once.
Currently, it gets executed three times.
You can replace two of those calls by joining to the field of the (one) remaing call.
Something like
SELECT WORKSHEET_ID
FROM PERSON PER
INNER JOIN PERSON EMPLEE ON EMPLEE.PERSON_ID = PER.PERSON_ID AND dbo.FN_CHECKRPTSECURITY(EMPLEE.PERSON_ID, #p_SEC_ACCOUNT_ID) > 0
INNER JOIN (
SELECT w1.ASSIGNMENT_ID
, w1.WORKSHEET_ID
, w1.EFFECTIVE_DATE
, w1.APPROVED_BY
, w3.CREATED_BY
FROM WORKSHEET_PAYROLL_VW w1
INNER JOIN WORKSHEET w3 ON w3.WORKSHEET_ID = w1.WORKSHEET_ID
WHERE w1.EFFECTIVE_DATE =
CASE WHEN #p_MOST_RECENT_ONLY = 'Y'
THEN (
SELECT MAX(w2.EFFECTIVE_DATE)
FROM WORKSHEET_PAYROLL_VW w2
WHERE w1.ASSIGNMENT_ID = w2.ASSIGNMENT_ID AND (ISNULL(#p_WKS_EFFECTIVE_DATE,w2.EFFECTIVE_DATE) = w2.EFFECTIVE_DATE)
)
ELSE ISNULL(#p_WKS_EFFECTIVE_DATE,w1.EFFECTIVE_DATE)
END
) PERSON_WKS ON PERSON_WKS.ASSIGNMENT_ID = dbo.FN_GETRPTASSIGNMENTID(EMPLEE.PERSON_ID)
INNER JOIN (
SELECT ASSIGNMENT_ID
, VALUE
FROM ASSIGNMENT_HISTORY AH
WHERE FIELD_NAME ='HOME PAYROLL GROUP'
AND EFFECTIVE_DATE = (
SELECT MAX(EFFECTIVE_DATE)
FROM ASSIGNMENT_HISTORY
WHERE ASSIGNMENT_ID = AH.ASSIGNMENT_ID
AND EFFECTIVE_DATE <=getDate()
AND FIELD_NAME = 'HOME PAYROLL GROUP'
)
LEFT JOIN SEARCH_ASSIGNMENT_VW PERSON_ASGN ON PERSON_ASGN.ASSIGNMENT_ID = PERSON_WKS.ASSIGNMENT_ID
LEFT JOIN LOOKUP EMPLEE_ASGN_STAT ON EMPLEE_ASGN_STAT.TYPE_ = 'ASSIGNMMENT_STATUS_CODE' AND EMPLEE_ASGN_STAT.CODE = PERSON_ASGN.ASGN_STAT_CODE
) HOME_PAYROLL ON HOME_PAYROLL.ASSIGNMENT_ID = PERSON_WKS.ASSIGNMENT_ID
WHERE (#p_SELECTED_PERSON_ONLY = 'N' OR EMPLEE.PERSON_ID = #p_PERSON_ID)
AND (#p_ASGN_STAT_CODE IS NULL OR PERSON_ASGN.ASGN_STAT_CODE = SUBSTRING(#p_ASGN_STAT_CODE,1,1) OR PERSON_ASGN.ASGN_STAT_CODE = SUBSTRING(#p_ASGN_STAT_CODE,2,1))
AND (#p_POLICY_ID IS NULL OR PERSON_ASGN.PROGRAM_CODE = #p_POLICY_ID)
AND (#p_HOME_COUNTRY_ID IS NULL OR PERSON_ASGN.HOMECOUNTRYID=#p_HOME_COUNTRY_ID)
AND (#p_HOME_CITY_ID IS NULL OR PERSON_ASGN.HOMECITYID=#p_HOME_CITY_ID )
AND (#p_HOME_COMPANY_ID IS NULL OR PERSON_ASGN.HOMEBUSINESSID=#p_HOME_COMPANY_ID )
AND (#p_HOME_DIVISION_ID IS NULL OR PERSON_ASGN.HOMECOMPONENTID=#p_HOME_DIVISION_ID )
AND (#p_HOST_COUNTRY_ID IS NULL OR PERSON_ASGN.HOSTCOUNTRYID=#p_HOST_COUNTRY_ID )
AND (#p_HOST_CITY_ID IS NULL OR PERSON_ASGN.HOSTCITYID=#p_HOST_CITY_ID )
AND (#p_HOST_COMPANY_ID IS NULL OR PERSON_ASGN.HOSTBUSINESSID=#p_HOST_COMPANY_ID )
AND (#p_HOST_DIVISION_ID IS NULL OR PERSON_ASGN.HOSTCOMPONENTID=#p_HOST_DIVISION_ID )
AND (#p_CREATED_BY IS NULL OR PERSON_WKS.CREATED_BY=#p_CREATED_BY )
AND (#p_APPROVED_BY IS NULL OR PERSON_WKS.APPROVED_BY=#p_APPROVED_BY )
AND (#p_payroll_code IS NULL OR HOME_PAYROLL.VALUE=#p_payroll_code )
ORDER BY
PER.LAST_NAME ASC
, PER.FIRST_NAME ASC
, PERSON_WKS.EFFECTIVE_DATE DESC