How to get top 5 history rows of each sql job? - sql

I am trying to get top 5 (latest) of each sql job.
I tried to query it like this
SELECT Pr1.job_ID,MSDB.dbo.Agent_datetime(Pr1.run_date, Pr1.run_time) LastRun, COUNT(*) num
FROM MSDB.dbo.sysjobhistory Pr1
JOIN MSDB.dbo.sysjobhistory Pr2
ON MSDB.dbo.Agent_datetime(Pr1.run_date, Pr1.run_time) = MSDB.dbo.Agent_datetime(Pr2.run_date, Pr2.run_time)
AND Pr1.job_ID = Pr2.job_ID
GROUP BY Pr1.job_ID, MSDB.dbo.Agent_datetime(Pr1.run_date, Pr1.run_time)
HAVING COUNT (*) <= 5
ORDER BY job_ID, MSDB.dbo.Agent_datetime(Pr1.run_date, Pr1.run_time)
But this query runs forever. I am using sql-server.
i also tried with a recommendation from below.
WITH CTE AS
(
SELECT jo.name,Pr1.job_ID, Pr1.run_date, Pr1.run_time,pr1.run_status,
Count(*) Over (Partition By Pr1.job_ID,Pr1.run_date,Pr1.run_time) As num,
Row_Number() Over (Partition By Pr1.job_ID,Pr1.run_date,Pr1.run_time
Order By pr1.run_date desc--, pr1.run_time desc
) As Rn
FROM MSDB.dbo.sysjobhistory Pr1
join MSDB.dbo.sysjobs jo on jo.job_id=pr1.job_id
JOIN MSDB.dbo.sysjobhistory Pr2
ON Pr1.job_ID = Pr2.job_ID --and pr1.run_status=pr2.run_status
and pr1.run_date >=pr2.run_date and pr1.run_time >=pr2.run_time
)
SELECT name,job_ID, run_date, run_time,run_status
FROM CTE
WHERE num <= 3 AND Rn = 1

Use row_number():
select pr.*
from (select pr.*,
row_number() over (partition by pr.job_id
order by pr.run_date desc, pr.run_time desc
) as seqnum
from MSDB.dbo.sysjobhistory pr
) pr
where seqnum <= 5;

It's running forever because you use the scalar valued function Agent_datetime. If you use a query that doesn't need those functions the query optimizer can do it's job and use indexes.
You can simplify your task with window functions and a common-table-expression(CTE). Following groups by the column job_ID and returns the latest record of each group according to the run_date and run_time columns:
WITH CTE AS
(
SELECT Pr1.job_ID, Pr1.run_date, Pr1.run_time,
Count(*) Over (Partition By Pr1.job_ID) As num,
Row_Number() Over (Partition By Pr1.job_ID
Order By Pr1.run_date DESC, Pr1.run_time DESC) As Rn
FROM MSDB.dbo.sysjobhistory
)
SELECT job_ID, run_date, run_time
FROM CTE
WHERE num <= 3 AND Rn = 1
Note that i've removed your pointless join condition and the self-join:
ON MSDB.dbo.Agent_datetime(Pr1.run_date, Pr1.run_time)
= MSDB.dbo.Agent_datetime(Pr1.run_date, Pr1.run_time)

Related

SQL query to get first and last of a sequence

With the following query ...
select aa.trip_id, aa.arrival_time, aa.departure_time, aa.stop_sequence, aa.stop_id, bb.stop_name
from OeBB_Stop_Times aa
left join OeBB_Stops bb on aa.stop_id = bb.stop_id
I get the following table:
Now I want the first and last line/value from the column stop_sequence referring to column trip_id, so the result should be:
How can I do that?
Thanks
You can do a sub-query to get the min and max and join against that data.
Like this:
select aa.trip_id, aa.arrival_time, aa.departure_time, aa.stop_sequence, aa.stop_id, bb.stop_name
from OeBB_Stop_Times aa
join (
SELECT trip_id, max(stop_sequence) as max_stop, min(stop_sequence) as min_stop
FROM OeBB_Stop_Times
GROUP BY trip_di
) sub on aa.trip_id = sub.trip_id AND (aa.stop_sequence = sub.max_stop or aa.stop_sequence = sub.min_stop)
left join OeBB_Stops bb on aa.stop_id = bb.stop_id
You can use the ROW_NUMBER() window function twice to filter out rows, as in:
select *
from (
select *,
row_number() over(partition by trip_id order by arrival_time) as rn,
row_number() over(partition by trip_id order by arrival_time desc) as rnr
from OeBB_Stop_Times
) x
where rn = 1 or rnr = 1
order by trip_id, arrival_time
You can use row_number():
select s.*
from (select st.trip_id, st.arrival_time, st.departure_time,
st.stop_sequence, st.stop_id, s.stop_name,
row_number() over (partition by st.trip_id order by st.stop_sequence) as seqnum_asc,
row_number() over (partition by st.trip_id order by st.stop_sequence desc) as seqnum_desc
from OeBB_Stop_Times st left join
OeBB_Stops s
on st.stop_id = s.stop_id
) s
where 1 in (seqnum_asc, seqnum_desc);
Note that I fixed the table aliases so they are meaningful rather than arbitrary letters.
Actually, if the stop_sequence is guaranteed to start at 1, this is a bit simpler:
select s.*
from (select st.trip_id, st.arrival_time, st.departure_time,
st.stop_sequence, st.stop_id, s.stop_name,
max(stop_sequence) over (partition by st.trip_id) as max_stop_sequence
from OeBB_Stop_Times st left join
OeBB_Stops s
on st.stop_id = s.stop_id
) s
where stop_sequence in (1, max_stop_sequence);

how to use rank/join and where together

I have used multiple inner joins in my code, and I provided rank, but now I want to select a particular rank. So how to use rank in a where statement?
Here is my code, but now please help me to proceed further:
select [YEAR],
[IDManufacturer],
sum([TotalPrice]),
rank() over (order by sum(totalprice) desc) as sales_rank
from [dbo].[DIM_DATE]
join [dbo].[FACT_TRANSACTIONS]
on [dbo].[FACT_TRANSACTIONS].Date = [dbo].[DIM_DATE].DATE
join [dbo].[DIM_MODEL]
on [dbo].[DIM_MODEL].IDModel=[dbo].[FACT_TRANSACTIONS].IDModel
where [YEAR] in (2009,2010)
group by IDManufacturer,[year]
order by sum([TotalPrice]) desc
Now I want to select only rank 3 and 4. How to do that?
You could either do sub-query or CTE, i would suggest try with 2 methods and look at execution plan pick which performs better:
Sub Query
SELECT * FROM
(select [YEAR],
[IDManufacturer],
sum([TotalPrice]) TotalPrice,
rank() over (order by sum(totalprice) desc) as sales_rank
from [dbo].[DIM_DATE]
join [dbo].[FACT_TRANSACTIONS]
on [dbo].[FACT_TRANSACTIONS].Date = [dbo].[DIM_DATE].DATE
join [dbo].[DIM_MODEL]
on [dbo].[DIM_MODEL].IDModel=[dbo].[FACT_TRANSACTIONS].IDModel
where [YEAR] in (2009,2010)
group by IDManufacturer,[year]
) as SQ
Where sales_rank = 3 or sales_rank = 4
go
Common Table Expression
; with CTE as
(select [YEAR],
[IDManufacturer],
sum([TotalPrice]) TotalPrice,
rank() over (order by sum(totalprice) desc) as sales_rank
from [dbo].[DIM_DATE]
join [dbo].[FACT_TRANSACTIONS]
on [dbo].[FACT_TRANSACTIONS].Date = [dbo].[DIM_DATE].DATE
join [dbo].[DIM_MODEL]
on [dbo].[DIM_MODEL].IDModel=[dbo].[FACT_TRANSACTIONS].IDModel
where [YEAR] in (2009,2010)
group by IDManufacturer,[year]
)
SELECT * FROM CTE WHERE sales_rank = 3 or sales_rank = 4
If you want only rank 3 and 4 then try this:
select * from (
select [YEAR],
[IDManufacturer],
sum([TotalPrice]),
rank() over (order by sum(totalprice) desc) as sales_rank
from [dbo].[DIM_DATE]
join [dbo].[FACT_TRANSACTIONS]
on [dbo].[FACT_TRANSACTIONS].Date = [dbo].[DIM_DATE].DATE
join [dbo].[DIM_MODEL]
on [dbo].[DIM_MODEL].IDModel=[dbo].[FACT_TRANSACTIONS].IDModel
where [YEAR] in (2009,2010)
group by IDManufacturer,[year]
order by sum([TotalPrice]) desc
) t where sales_rank in (3,4)
If you only want the 3rd and 4th values -- and assuming no ties -- then use offset/fetch:
offset 2 rows fetch first 2 rows only
The offset 2 is because offset starts counting at 0 rather than 1.

Select most recent status for each ID and department code

I have the following table:
I want to get the most recent status for each dept_code that a CL_ID has. So the desired output would be this:
I have tried the following but this give me just the most recent status for each client and not each of their dept_codes.
SELECT *
FROM [CIMSHR6_MERGED].[dbo].[C3CLSTAT] C
INNER JOIN
(SELECT CLIENT_NUMBER, MAX(STATUS_DATE) AS SDATE
FROM [CIMSHR6_MERGED].[dbo].[C3CLSTAT]
GROUP BY CLIENT_NUMBER) X
ON X.CLIENT_NUMBER = C.CLIENT_NUMBER
AND X.SDATE = C.STATUS_DATE
ORDER BY C.CLIENT_NUMBER
Any help would be much appreciated. Thanks.
A convenient method that works in SQL Server is:
select top (1) cl.*
from [CIMSHR6_MERGED].[dbo].[C3CLSTAT] cl
order by row_number() over (partition by cl_id, dept_code order by status_date desc);
A method that is efficient with the right indexes in almost any database is:
select cl.*
from [CIMSHR6_MERGED].[dbo].[C3CLSTAT] cl
where cl.status_date = (select max(cl2.status_date)
from [CIMSHR6_MERGED].[dbo].[C3CLSTAT] cl2
where cl2.cl_id = cl.cl_id and cl2.dept_code = cl.dept_code
);
The right index is on (cl_id, dept_code, status_date).
I would also use ROW_NUMBER, but with a subquery:
SELECT CL_ID, Status_date, Status, Dept_code
FROM
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY CL_ID, Dept_code ORDER BY Status_date DESC) rn
FROM CIMSHR6_MERGED].[dbo].[C3CLSTAT]
) t
WHERE rn = 1;
1) Firstly group everything on Dept_Code,CL_ID and assign rank for each row with in the group in descending order.
2) Select all the rows with rnk=1 which would display your desired result.
SELECT Z.CL_ID,
Z.Status_Date,
Z.Status,
Z.Dept_Code
FROM
(
SELECT *,
RANK() OVER( PARTITION BY Dept_Code,CL_ID, ORDER BY Status_Date DESC ) AS rnk
FROM [CIMSHR6_MERGED].[dbo].[C3CLSTAT]
) Z
WHERE Z.rnk = 1;
This would work for almost all databases
select * from c3clstat c
where exists
(select 1 from c3clstat c1
where c1.cl_id=c.cl_id
and c1.dept_code=c.dept_code
group by cl_id,dept_code
having c.status_date=max(c1.status_date)
)

Limit per some field

Suppose we have such an sql query, with joined data from another table.
SELECT
pr.num, pr.resort_id, p.src_mask
FROM
rutraveler.rt_photo_resort AS pr
JOIN rutraveler.rt_photo AS p ON pr.photo_id = p.id
WHERE pr.resort_id = '612' AND p.src_mask is not null
ORDER BY num
LIMIT 30
So far we have to do several queries for several resort_id.
How to change the query so that we have only one query (WHERE resort_id in (612, 333, 111) with result no more than 30 items per each resort_id?
Use ROW_NUMBER to count the rows per resort_id.
SELECT resort_id, num, resort_id, src_mask
FROM
(
SELECT
pr.resort_id, pr.num, pr.resort_id, p.src_mask,
ROW_NUMBER() OVER (PARTITION BY pr.resort_id ORDER BY num) AS rn
FROM
rutraveler.rt_photo_resort AS pr
JOIN rutraveler.rt_photo AS p ON pr.photo_id = p.id
WHERE resort_id in (612, 333, 111) AND p.src_mask is not null
) data
WHERE rn <= 30
ORDER BY resort_id, num;
you can use CTE with ROW_NUMBER() and PARTITION BY
WITH Results_CTE AS
(
SELECT
pr.num, pr.resort_id, p.src_mask,ROW_NUMBER() over ( PARTITION BY pr.resort_id ORDER BY num) As Row_number
FROM
rutraveler.rt_photo_resort AS pr
JOIN rutraveler.rt_photo AS p ON pr.photo_id = p.id
WHERE pr.resort_id IN (A,B,C) AND p.src_mask is not null
)
SELECT *
FROM Results_CTE
WHERE Row_number <= 30

Using Oracle rank() in a left join

When I run this subquery by itself:
select *
from (select rpt_nbr, iteration, seq_nbr, emp_id_key, rank() over
(partition by emp_id_key order by iteration, seq_nbr) rk
from SJTCAPP.LAB_RPT_SPEC_EMP where rpt_nbr = 1572413)
where rk = rownum
I get a good result it with only 1 emp_id_key returned for each iteration and seq_nbr, even though there can be multiple emp_id_key s assigned. So that is good, however, when I add that to the rest of my query:
select * from
SJTCAPP.LAB_RPT r
left join SJTCAPP.LAB_RPT_SPEC s on s.rpt_nbr = r.rpt_nbr
left join (select * from
(select rpt_nbr, iteration, seq_nbr, emp_id_key, rank() over (partition by emp_id_key order
by iteration, seq_nbr) rk from SJTCAPP.LAB_RPT_SPEC_EMP ) where rk = rownum)
se on se.rpt_nbr = s.rpt_nbr and se.seq_nbr = s.seq_nbr and se.iteration = s.iteration
left join sjtcapp.employee tech on tech.emp_id_key = se.emp_id_key
I get a NULL value for the tech.emp_id_key join
Update:
select * from (select rpt_nbr, iteration, seq_nbr, emp_id_key, rank() over (partition by emp_id_key order by iteration, seq_nbr ) rk from SJTCAPP.LAB_RPT_SPEC_EMP where rpt_nbr = 1572413) where rk = rownum and rpt_nbr = 1572413
The above query also gives 'good' results.
RPT_NBR ITERATION SEQ_NBR EMP_ID_KEY RK
1572413 1 1 44746 1
1572413 1 2 44746 2
Before I had more of a straightforward join here and received a correct query with individual technician names. Only issue is that if more than one were assigned it caused a duplicate, which is why I added the rank subquery.
If constrain iteration and seq_nbr is unique you can use exists instead rank
SELECT
rpt_nbr,
iteration,
seq_nbr,
emp_id_key
FROM
SJTCAPP.LAB_RPT_SPEC_EMP emp
WHERE
NOT EXISTS
(
SELECT
*
FROM
SJTCAPP.LAB_RPT_SPEC_EMP emp2
WHERE
emp2.emp_id_key = emp.emp_id_key AND
emp2.iteration < emp.iteration AND
emp2.seq_nbr < emp.seq_nbr
)
I ended up going back to using a GROUP BY
left join (
select min(rpt_nbr) as rpt_nbr, min(iteration) as iteration, min(seq_nbr) as seq_nbr, min(emp_id_key) as emp_id_key from LAB_RPT_SPEC_EMP group by rpt_nbr, iteration, seq_nbr
) se
on se.rpt_nbr = s.rpt_nbr and se.seq_nbr = s.seq_nbr and se.iteration = s.iteration