Rewrite MySQL code to retrieve average time - mysql

I don't know if that's a question for SO, if not please delete it. I am using the query below to calculate an average time it takes for a ticket on our service desk to get closed.
I don't have write permissions on the database, so I can't create functions, variables etc.
I strongly believe that there must be a better/nicer, more robust way to calculate that, than my query below, any thoughts?
What I want to avoid, if possible, is to recalculate the count value, which especially with all the where clauses makes the query a bit slow.
SELECT Count(hd_ticket.id) AS 'Tickets #',
ROUND(( Timestampdiff(hour, hd_ticket.created, hd_ticket.time_closed) /
(SELECT Count(hd_ticket.id)
FROM
hd_ticket
LEFT JOIN hd_status
ON hd_status_id = hd_status.id
WHERE
Month(
hd_ticket.time_closed) = 12
AND
Year
(hd_ticket.time_closed) = 2017
AND
hd_status.state LIKE '%close%'
AND
hd_ticket.hd_queue_id IN ( 8 )) )) AS
'AVG Closure Time'
FROM hd_ticket
LEFT JOIN hd_status
ON hd_status_id = hd_status.id
WHERE Month(hd_ticket.time_closed) = 12
AND Year(hd_ticket.time_closed) = 2017
AND hd_status.state LIKE '%close%'
AND hd_ticket.hd_queue_id IN ( 8 )
In a nutshell what the above query does is
SELECT COUNT(TICKETS) as 'Tickets #',
ROUND(TOTAL_TIME_TAKES_TO_CLOSE_TICKETS/COUNT(TICKETS + FILTERS)) as 'AVG Closure Time'
FROM HD_TICKET
SOME FILTERS

I would recommend:
SELECT Count(*) as Num_Tickets_Closed,
AVG( ( Timestampdiff(hour, t.created, t.time_closed) ) as AVG_CLosure_Time
FROM hd_ticket t LEFT JOIN
hd_status s
ON t.hd_status_id = s.id
WHERE t.time_closed >= '2017-12-01' AND
t.time_closed < '2018-01-01' AND
s.state LIKE '%close%' AND
t.hd_queue_id IN ( 8 ) ;
Notes:
First, you can just use AVG(). That greatly simplifies the query.
The date comparisons are made without functions. Although this likely has little impact in your case, it allows the use of indexes.
The names of the columns no longer have special characters, so they don't need to be escaped.
Table aliases make the query easier to write and to read.

Related

MySQL correlated sub-query in SELECT clause

I'm trying to execute a correlated sub-query in the SELECT clause of a MySQL query. How do I use the value of a row in another column inside the WHERE clause of my subquery?
I've been referencing the "Correlated Sub-Query Example" section of this webpage, but for some reason my query is not working the same.
Here is my code:
SELECT Year,
( SELECT COUNT(*)
FROM systems
WHERE SYSTEMTYPE = 'handheld' AND Year = sys.Year
) as handheld,
( SELECT COUNT(*)
FROM systems
WHERE SYSTEMTYPE = 'console' AND Year = sys.Year
) as console,
FROM systems as sys
WHERE Year IS NOT NULL
Basically, I'm trying to create a table that shows how many systems of each type were created for each year. When I run that query in MySQL Workbench it runs until the database connection expires. I can't see how this query is much different than the one on the website I am referencing.
Any help would be greatly appreciated! If it seems there is a better way I could go about this, I am open to those ideas as well. Thank you!
This may be issue of performance of a query. Such subquery needs to be executed for each row, thus it may take long to run. The query can be simplified by using group by:
select year, count(*) from systems
where Year is not null and systemtype = 'handled'
group by Year
UPDATE regarding comment:
what if I want to add more columns for different types of systems other than just the one type?
Use query:
select year,
sum(case when systemtype = 'handled' then 1 else 0 end) handled,
sum(case when systemtype = 'console' then 1 else 0 end) console
from systems
where Year is not null
group by Year
Could be you have a scope issue try use a inner join instead of a subquery for each rows
SELECT sys.Year, t.my_count
FROM systems as sys
INNER JOIN
( SELECT Year, COUNT(*) my_count
FROM systems
WHERE SYSTEMTYPE = 'handheld' AND Year = sys.Year
GROUP BY year
) t on t.year = sys.year
WHERE sys.Year IS NOT NULL
Use Group By statement instead of subqueries. Subqueries will make your query run much slower as more rows are added. Try this query to get number of systems made per type and per year:
SELECT
Year,
SYSTEMTYPE,
COUNT(*) as Total_systems_per_type_per_year
FROM systems
WHERE Year IS NOT NULL
GROUP BY Year, SYSTEMTYPE

How to total "AS" column in mysql query?

I need SUM of AS column "profit"and "purch_price". I tried few things group by stock.id etc but it is not giving me same answer. please let me know how to take their sum.
SELECT
jobc_consumble.Stock_id,
jobc_consumble.issued_qty,
jobc_consumble.total,
p_purch_stock.Price,
p_purch_stock.Price * jobc_consumble.issued_qty AS "purch_price",
jobc_consumble.total -(SELECT purch_price) AS "profit"
FROM
jobc_consumble
INNER JOIN p_purch_stock ON jobc_consumble.stock_id = p_purch_stock.stock_id
WHERE
DATE_FORMAT(
jobc_consumble.issue_time,
'%Y-%m-%d'
) BETWEEN '2018-07-03' AND '2018-07-03'
Output should be like so :
I want sum of last two columns.
sum of PROFIT: 1105
sum of purch_price: 11000
Just repeat the logic in your SELECT clause:
SELECT
j.Stock_id,
j.issued_qty,
j.total,
p.Price,
p.Price * j.issued_qty AS purch_price,
j.total - (p.Price * j.issued_qty) AS profit
FROM jobc_consumble j
INNER JOIN p_purch_stock p
ON j.stock_id = p.stock_id
WHERE
DATE_FORMAT(j.issue_time, '%Y-%m-%d') = '2018-07-03';
For some notes, the only alternative to repeating the purchase price logic would be to wrap your current query as a subquery and reuse the alias. But, that would probably not be too performant. Your DATE_FORMAT expression does not make much sense, because the range is just a single day. If you don't like my version, then let us know what logic you really intended.

SQL query needs optimization

SELECT LM.user_id,LM.users_lineup_id, min( LM.total_score ) AS total_score
FROM vi_lineup_master LM JOIN
vi_contest AS C
ON C.contest_unique_id = LM.contest_unique_id join
(SELECT min( total_score ) as total_score
FROM vi_lineup_master
GROUP BY group_unique_id
) as preq
ON LM.total_score = preq.total_score
WHERE LM.contest_unique_id = 'iledhSBDO' AND
C.league_contest_type = 1
GROUP BY group_unique_id
Above query is to find the loser per group of game, query return accurate result but its not responding with large data. How can I optimize this?
You can try to move your JOINs to subqueries. Also, you should pay attention on your "wrong" GROUP BY usage on the outer query. In Mysql you can group by some columns and select others not specified in the group clause without any aggregation function, but the database can't ensure what data it will return to you. For the sake of consistency of your application, wrap them in an aggregation function.
Check if this one helps:
SELECT
MIN(LM.user_id) AS user_id,
MIN(LM.users_lineup_id) AS users_lineup_id,
MIN(LM.total_score) AS total_score
FROM vi_lineup_master LM
WHERE 1=1
-- check if this "contest_unique_id" is equals
-- to 'iledhSBDO' for a "league_contest_type" valued 1
AND LM.contest_unique_id IN
(
SELECT C.contest_unique_id
FROM vi_contest AS C
WHERE 1=1
AND C.contest_unique_id = 'iledhSBDO'
AND C.league_contest_type = 1
)
-- check if this "total_score" is one of the
-- "min(total_score)" from each "group_unique_id"
AND LM.total_score IN
(
SELECT MIN(total_score)
FROM vi_lineup_master
GROUP BY group_unique_id
)
GROUP BY LM.group_unique_id
;
Also, some pieces of this query may seem redundant, but it's because I did not want to change the filters you wrote, just moved them.
Also, your query logic seems a bit strange to me, based on the tables/columns names and how you wrote it... please, check the comments in my query which reflects what I understood of your implementation.
Hope it helps.

Find average amount of time from Requisition Submitted to Order Created

I have two tables requisition_headers and order_headers. I am interested in finding the average time it takes from the time the requisition is submitted (requisition_headers.submitted_at) and the time the order is created (orders.headers_created_at) where the requisition_headers.status <> 'draft'.
I would like the result to look like:
Avg_Req_To_PO_Cycle_Time = 3.2 Days
I have the following script but it's not working:
SELECT Database() as Customer,
AVG(timestampdiff(requisition_headers.submitted_at,order_headers.created_at)) AS REQ_PO_Cycle_Time
FROM order_headers
LEFT JOIN requisition_headers ON order_headers.requisition_header_id = requisition_headers.id
WHERE requisition_headers.status <> 'draft'
Any Ideas?
--UPDATE--
I changed the query to the following and now get a response of 229491.71 my question is- is that days, hours, minutes, seconds?
SELECT DATABASE() AS CUSTOMER,
AVG(TIME_TO_SEC(TIMEDIFF(order_headers.created_at,requisition_headers.submitted_at))) as Cycle_time
FROM order_headers LEFT JOIN requisition_headers ON order_headers.requisition_header_id = requisition_headers.id
where requisition_headers.status <> 'draft'
Make sure you know what a system function returns when you use it. The TimestampDiff function returns the difference between the two dates in the unit you specify in the first argument. You don't specify that unit so I don't know what you get back. I get a compile error.
In your second attempt, you are using TimeDiff which returns an interval value, then converting the result of Avg to seconds. So if you want the result in fractional days just divide by the number of seconds in a day.
You also use a left join when getting the dates. At first I thought you wanted to get all the requisitions whether the orders had been created or not. But you are joining the tables in the wrong order for that. But, assuming that is your intention, if the order has not yet been created you will be putting NULL as one of the parameters. You will get a NULL as an answer so you get nothing. If you want to use a left join, then you should specify a substitute date for any missing Created dates -- after getting the table in the right order, that is.
Here are two options. One ignores orders that have not yet been created by using a regular inner join. The other includes those but substitutes the current date and time.
By asking for the number of minutes between the dates, the final answer in days is found by dividing by the number of minutes in a day.
SQLFiddle
SELECT Customer,
AVG( timestampdiff( minute, r.submitted_at,
o.created_at)) / (24 * 60 )AS REQ_PO_Cycle_Time
FROM requisition_headers r
JOIN order_headers o
ON o.requisition_header_id = r.id
WHERE r.status <> 'draft'
group by Customer;
SELECT Customer,
AVG( timestampdiff( minute, r.submitted_at,
IfNull( o.created_at, CurDate()))) / (24 * 60 )AS REQ_PO_Cycle_Time
FROM requisition_headers r
LEFT JOIN order_headers o
ON o.requisition_header_id = r.id
WHERE r.status <> 'draft'
group by Customer;

optimize Mysql: get latest status of the sale

In the following query, I show the latest status of the sale (by stage, in this case the number 3). The query is based on a subquery in the status history of the sale:
SELECT v.id_sale,
IFNULL((
SELECT (CASE WHEN IFNULL( vec.description, '' ) = ''
THEN ve.name
ELSE vec.description
END)
FROM t_record veh
INNER JOIN t_state_campaign vec ON vec.id_state_campaign = veh.id_state_campaign
INNER JOIN t_state ve ON ve.id_state = vec.id_state
WHERE veh.id_sale = v.id_sale
AND vec.id_stage = 3
ORDER BY veh.id_record DESC
LIMIT 1
), 'x') sale_state_3
FROM t_sale v
INNER JOIN t_quarters sd ON v.id_quarters = sd.id_quarters
WHERE 1 =1
AND v.flag =1
AND v.id_quarters =4
AND EXISTS (
SELECT '1'
FROM t_record
WHERE id_sale = v.id_sale
LIMIT 1
)
the query delay 0.0057seg and show 1011 records.
Because I have to filter the sales by the name of the state as it would have to repeat the subquery in a where clause, I have decided to change the same query using joins. In this case, I'm using the MAX function to obtain the latest status:
SELECT
v.id_sale,
IFNULL(veh3.State3,'x') AS sale_state_3
FROM t_sale v
INNER JOIN t_quarters sd ON v.id_quarters = sd.id_quarters
LEFT JOIN (
SELECT veh.id_sale,
(CASE WHEN IFNULL(vec.description,'') = ''
THEN ve.name
ELSE vec.description END) AS State3
FROM t_record veh
INNER JOIN (
SELECT id_sale, MAX(id_record) AS max_rating
FROM(
SELECT veh.id_sale, id_record
FROM t_record veh
INNER JOIN t_state_campaign vec ON vec.id_state_campaign = veh.id_state_campaign AND vec.id_stage = 3
) m
GROUP BY id_sale
) x ON x.max_rating = veh.id_record
INNER JOIN t_state_campaign vec ON vec.id_state_campaign = veh.id_state_campaign
INNER JOIN t_state ve ON ve.id_state = vec.id_state
) veh3 ON veh3.id_sale = v.id_sale
WHERE v.flag = 1
AND v.id_quarters = 4
This query shows the same results (1011). But the problem is it takes 0.0753 sec
Reviewing the possibilities I have found the factor that makes the difference in the speed of the query:
AND EXISTS (
SELECT '1'
FROM t_record
WHERE id_sale = v.id_sale
LIMIT 1
)
If I remove this clause, both queries the same time delay... Why it works better? Is there any way to use this clause in the joins? I hope your help.
EDIT
I will show the results of EXPLAIN for each query respectively:
q1:
q2:
Interesting, so that little statement basically determines if there is a match between t_record.id_sale and t_sale.id_sale.
Why is this making your query run faster? Because Where statements applied prior to subSelects in the select statement, so if there is no record to go with the sale, then it doesn't bother processing the subSelect. Which is netting you some time. So that's why it works better.
Is it going to work in your join syntax? I don't really know without having your tables to test against but you can always just apply it to the end and find out. Add the keyword EXPLAIN to the beginning of your query and you will get a plan of execution which will help you optimize things. Probably the best way to get better results in your join syntax is to add some indexes to your tables.
But I ask you, is this even necessary? You have a query returning in <8 hundredths of a second. Unless this query is getting ran thousands of times an hour, this is not really taxing your DB at all and your time is probably better spent making improvements elsewhere in your application.