BenHuman
BenHuman

Reputation: 175

Optimise ORDER BY in MYSQL Query

I have the following query which uses order by with limit. It takes 2 min 25 sec to pull around 16k data.I have done PROPER INDEXING too but still PERFORMS SLOW. Same time is taken when only LIMIT 20 is applied too. When ORDER BY is removed, query fetches same data in 17 secs.All the tables are in latin1 charset.Please suggest any possible solutions.

SELECT 
a.customer,
a.division AS division,
a.noitaziraa_id AS noitaziraaId,
DATE_FORMAT(a.request_date, '%m/%d/%Y') AS RequestDate,
a.request_date AS RequestDateSort,
DATE_FORMAT(noita.date_of_birth, '%m/%d/%Y') AS dob,
noita.date_of_birth AS dobSort,
IF(
a.noita_type = 'Noita Stay',
a.length_of_stay,
NULL
) AS requestedDays,
IF(
a.noita_type = 'Noita Stay',
CONCAT_WS(
  ',',
  a.facility_provider_city,
  a.facility_provider_state
),
''
) AS facilityCityState,
IF(
a.noita_type = 'Noita Stay',
IFNULL(
  DATE_FORMAT(aips.admission_date, '%m/%d/%Y'),
  ''
),
''
) AS admitDate,
IF(
a.noita_type = 'Noita Stay',
aips.admission_date,
''
) AS admitDateSort,
IF(
a.noita_type = 'Noita Stay',
IFNULL(
  DATE_FORMAT(
    aipsd.discharge_date,
    '%m/%d/%Y'
  ),
  ''
),
''
 ) AS dischargeDate,
  IF(
a.noita_type = 'Noita Stay',
aipsd.discharge_date,
''
 ) AS dischargeDateSort,
  IF(
a.noita_type = 'Noita Stay',
IFNULL(dl1.`description`, ''),
''
 ) AS dischargeDisposition,
 a.gender,
 a.age,
  a.relationship AS relationship,
  noita.groupid,
  a.request_type AS requestType,
  a.prog_status AS programStatus,
 dl.description AS billingDetails,
 a.referred_to_npi AS NPI,
 a.program AS program,
 CASE
  WHEN a.status = 'OPEN' 
  THEN DATEDIFF(NOW(), a.auth_request_date) 
  ELSE 0 
  END AS 'daysSinceRequest',
  a.first_name AS firstName,
 a.last_name AS lastName,
dl2.description AS levelOfUrgency,
 a.member_id AS memberId,
a.created_full_name AS createdFullName,
CONCAT_WS(
',',
COALESCE(a.assigned_to, NULL),
COALESCE(
  a.auth_review_assigned_user_name,
  NULL
),
COALESCE(
  a.auth_con_review_assigned_user_name,
  NULL
),
COALESCE(a.assigned_queue, NULL),
COALESCE(
  a.auth_review_assigned_queue_name,
  NULL
 ),
 COALESCE(
  a.auth_con_review_assigned_queue_name,
  NULL
 )
 ) AS assignedTo,
a.status,
DATE_FORMAT(a.opened_date, '%m/%d/%Y') AS openDate,
 a.opened_date AS openDateSort,
 DATE_FORMAT(a.closed_date, '%m/%d/%Y') AS closedDate,
 a.closed_date AS closedDateSort,
 a.noita_type AS authType,
 a.facility_provider AS facilityProvider,
 a.length_of_stay AS lengthOfStay,
 DATE_FORMAT(a.requested_from, '%m/%d/%Y') AS authFromDate,
 a.requested_from AS authFromDateSort,
 DATE_FORMAT(a.requested_through, '%m/%d/%Y') AS authToDate,
 a.requested_through AS authToDateSort,
 a.pended,
  a.diagnosis AS diagnosis,
 a.diagnosis_desc AS diagDesc,
 a.auth,
a.denied,
a.excluded,
a.admit_type AS admitType,
a.service_type AS serviceType,
a.proc,
a.proc_desc AS procDesc,
a.plan 
FROM
main_table a 
INNER JOIN noitaciary noita 
ON noita.id = a.noitaciary_id 
INNER JOIN usermanagement.`user` usr 
ON a.created_by = usr.id 
AND 
CASE
  WHEN CONCAT(usr.firstname, ' ', usr.lastname) IN ('a', 'b *', 'c', 
   'd', 'd', 'f') 
  THEN 1 = 1 
  ELSE (
    COALESCE(usr.`employer`, '') NOT IN ('r', 's')
  ) 
  END 
    LEFT JOIN noitaziraa_ips AS aips 
    ON aips.noitaziraa_id = a.auth_id 
  LEFT JOIN db1.`noitaziraa_history` ah 
   ON ah.noitaziraa_id = a.noitaziraa_id 
 LEFT JOIN noitaziraa_ips_discharge AS aipsd 
  ON aipsd.noitaziraa_ips_id = aips.id 
 LEFT JOIN noitaziraa_phr AS aphr 
  ON aphr.noitaziraa_id = a.auth_id 
  LEFT JOIN noitaziraa_sp AS asp 
  ON asp.noitaziraa_id = a.auth_id 
  LEFT JOIN noitaziraa_decisions AS auth_dec 
 ON a.auth_id = auth_dec.noitaziraa_id 
 LEFT JOIN mytable AS aa 
 ON a.noitaziraa_id = aa.noitaziraa_id 
LEFT JOIN db1.dw_lookup dl 
 ON auth_dec.details = dl.code 
LEFT JOIN db1.`dw_lookup` dl1 
ON dl1.`code` = aipsd.`discharge_diposition` 
 AND dl1.`data_type` = 'dataTypeName' 
 LEFT JOIN db1.dw_lookup dl2 
 ON aa.level_of_urgency = dl2.code 
 AND dl2.data_type = 'dataTypeName1' 
LEFT JOIN 
    (SELECT 
     * 
   FROM
  (SELECT 
    hh.noitaziraa_id,
    hh.`status` 
  FROM
    db1.`noitaziraa_history` hh,
    main_table a 
  WHERE hh.noitaziraa_id = a.noitaziraa_id 
    AND hh.client = 'certainValue' 
    AND DATE(hh.last_updated) < '2017-12-01 00:00:00' 
  GROUP BY hh.`last_updated` 
  ORDER BY hh.last_updated DESC) tmp 
GROUP BY noitaziraa_id) AS tps 
ON tps.noitaziraa_id = a.noitaziraa_id 
  WHERE a.customer LIKE 'certainValue%' 
   AND a.status <> 'VOID' 

  AND DATE(auth_dec.requested_through) >= '2017-12-01 00:00:00' 
AND DATE(auth_dec.requested_through) <= '2017-12-05 00:00:00' 
AND DATE(a.opened_date) <= '2017-12-05 00:00:00' 
 AND (
 (
    DATE(ah.last_updated) BETWEEN '2017-12-01 00:00:00' 
  AND '2017-12-05 00:00:00' 
   AND ah.status IN (
    'OPEN',
    'CLOSED',
    'REOPENED',
    'CANCELED'
     )
    ) || (
    tps.noitaziraa_id = a.noitaziraa_id 
     AND tps.status IN (
    'OPEN',
    'CLOSED',
    'REOPENED',
    'CANCELED'
  )
   )
  ) 
  GROUP BY a.auth_id 
  ORDER BY groupid ASC 
  LIMIT 0, 20 

noitaziraa_history table contains large number of rows and it must be left joined to meet my requirements which is taking lot of time.

Using EXPLAIN gives the following: enter image description here

Upvotes: 0

Views: 141

Answers (2)

RamaKrishna
RamaKrishna

Reputation: 76

As per the question, you have applied proper indexing too. I suppose you are correct on that. Then, Please avoid the use of LEFT JOIN with the noitaziraa_history table as it is also mentioned by @Rick James. If it is possible, make sure to load the data in this table so that all the noitaziraa_id in your primary table be in history table too.Now, you can apply INNER JOIN instead of LEFT JOIN you are using and see the result. Also, refactor everything that seems inappropriate or of no use at present following Rick James' suggestion. I am sure INNER JOIN will reduce the time taken by LEFT JOIN if you have multiple rows of data for a same row in your primary table. One thing more, please apply any conditions filter if you can during the JOIN with the nnoitaziraa_history table like you have done in the subquery below:

    INNER JOIN db1.`noitaziraa_history` ah 
    ON ah.noitaziraa_id = a.noitaziraa_id AND  hh.client = 'certainValue' AND  DATE(hh.last_updated) <  '2017-12-01 00:00:00'

Please update if it works for you :) Thanks!

Upvotes: 1

Rick James
Rick James

Reputation: 142298

This needs to be tackled incrementally.

        SELECT  *
            FROM  
            (
                SELECT  hh.noitaziraa_id, hh.`status`
                    FROM  db1.`noitaziraa_history` hh, main_table a
                    WHERE  hh.noitaziraa_id = a.noitaziraa_id
                      AND  hh.client = 'certainValue'
                      AND  DATE(hh.last_updated) <  '2017-12-01 00:00:00'
                    GROUP BY  hh.`last_updated`
                    ORDER BY  hh.last_updated DESC
            ) tmp
            GROUP BY  noitaziraa_id

The inner ORDER BY will be ignored; get rid of it. Then ask whether two levels of GROUP BY really makes sense.

AND  DATE(hh.last_updated) <  '2017-12-01 00:00:00'

Change that to

AND hh.last_updated < '2017-12-01'

Reason: Hiding a potentially indexed column in a function (DATE) makes it so that the index cannot be used.

Then add this composite index to hh:

INDEX(client, noitaziraa_id, last_updated, status) 

Meanwhile, you may have a serious bug: Why do you have main_table a specified both in this subquery and in the outer area? Was that a mistake?

      AND DATE(auth_dec.requested_through) >= '2017-12-01 00:00:00'
      AND DATE(auth_dec.requested_through) <= '2017-12-05 00:00:00'

-->

      AND auth_dec.requested_through >= '2017-12-01'
      AND auth_dec.requested_through  < '2017-12-01' + INTERVAL 5 DAY

These are not used, so get rid of them. This may require you to invest more effort in the code that constructs the query. (Or was it written by hand?)

    LEFT JOIN  noitaziraa_phr AS aphr  ON aphr.noitaziraa_id = a.auth_id
    LEFT JOIN  noitaziraa_sp AS asp  ON asp.noitaziraa_id = a.auth_id

LEFT JOIN -- Don't use it unless you need it. You do not need some of them -- as can be discovered by referencing auth_dec in the WHERE clause.

dl, dl1, dl2 -- These are on the end of the chain of LEFT JOINs. Remove them, and remove the references to the columns in them. Then add an extra SELECT layer outside to reach into them after doing the ORDER BY and LIMIT. This will decrease the number of references into them from "lots" to only 20.

The EXPLAIN shows a table caseload; the query does not have such. Please fix.

And fix the AND AND typo.

I quit for now.

Upvotes: 4

Related Questions