Проблема производительности запроса с запросом внутри условия where - PullRequest
1 голос
/ 26 марта 2012

У меня следующий довольно сложный запрос sql, который имеет ужасную производительность, «конечно» из-за внутреннего запроса в предложении where.В некоторых случаях это занимает более минуты.Кто-нибудь знает, как переписать этот запрос для повышения производительности?
Запрос:

SELECT DISTINCT t.id as taskId, t.name as taskName, 
  t.startdate as taskStartDate, t.enddate as taskEndDate, 
  t.proj_id as taskProjectId 
FROM PROJECT p, EMPL_PROJ ep, TASK t, TIMERECORD tr 
WHERE 
  ep.empl_id = ? AND 
  ep.proj_id = p.id AND 
  ep.proj_id = t.proj_id AND 
  ((p.startdate IS NULL AND p.enddate IS NULL) OR 
   (p.startdate IS NULL AND p.enddate >= ?) OR 
   (p.enddate IS NULL AND p.startdate <= ? + INTERVAL 6 DAY) OR 
   (p.startdate <= ? + INTERVAL 6 DAY AND p.enddate >= ?) ) AND 
  ((t.startdate IS NULL AND t.enddate IS NULL) OR 
   (t.startdate IS NULL AND t.enddate >= ?) OR 
   (t.enddate IS NULL AND t.startdate <= ? + INTERVAL 6 DAY) OR 
  (t.startdate <= ? + INTERVAL 6 DAY AND t.enddate >= ?)) AND 
  (
   (ep.empl_id = tr.empl_id AND 
    ep.proj_id = tr.proj_id AND 
    t.id = tr.task_id AND tr.day <= ? + INTERVAL 7 DAY AND 
    tr.day >= ? + INTERVAL -14 DAY
   ) OR 
   (
    (SELECT count(*) 
     FROM TIMERECORD tr2 
     WHERE 
     tr2.empl_id=ep.empl_id AND 
     tr2.proj_id=p.id AND tr2.day <= ? + INTERVAL 7 DAY AND 
     tr2.day >= ? + INTERVAL -14 DAY) <= 0
    )
  ) 

Я использую сервер MySQL 5.1.40.

Редактировать (2): С комментариями и ответами я пришел кэтот запрос, который выполняется менее чем за секунду (неплохо из-за почти минуты!)

SELECT DISTINCT t.id as taskId, t.name as taskName, 
  t.startdate as taskStartDate, t.enddate as taskEndDate, 
  t.proj_id as taskProjectId 
FROM (PROJECT p INNER JOIN EMPL_PROJ ep ON  ep.proj_id = p.id)  
  INNER JOIN TASK t ON p.id=t.proj_id 
  INNER JOIN TIMERECORD tr ON tr.empl_id=ep.empl_id AND tr.proj_id=ep.proj_id 
    AND tr.task_id=t.id
WHERE 
  ep.empl_id = ? AND 
  ((p.startdate IS NULL AND p.enddate IS NULL) OR 
   (p.startdate IS NULL AND p.enddate >= ?) OR 
   (p.enddate IS NULL AND p.startdate <= ? + INTERVAL 6 DAY) OR 
   (p.startdate <= ? + INTERVAL 6 DAY AND p.enddate >= ?) ) AND 
  ((t.startdate IS NULL AND t.enddate IS NULL) OR 
   (t.startdate IS NULL AND t.enddate >= ?) OR 
   (t.enddate IS NULL AND t.startdate <= ? + INTERVAL 6 DAY) OR 
   (t.startdate <= ? + INTERVAL 6 DAY AND t.enddate >= ?)) AND 
  (
   (
    tr.day <= ? + INTERVAL 7 DAY AND 
    tr.day >= ? + INTERVAL -14 DAY
   ) OR 
   (
    NOT EXISTS(SELECT *
      FROM TIMERECORD tr2 INNER JOIN EMPL_PROJ ON tr2.empl_id=EMPL_PROJ.empl_id 
        INNER JOIN PROJECT ON PROJECT.id=tr2.proj_id
      WHERE 
       tr2.day BETWEEN ? + INTERVAL -14 DAY AND ? + INTERVAL 7 DAY)
   )
  ) 
  ORDER BY p.id, t.id

Самым большим вкладом был ответ, предлагающий подход NOT EXISTS (который я пометил как правильный) и комментарий, несмешайте explicit и implicit JOIN.

Спасибо всем!

Ответы [ 2 ]

2 голосов
/ 26 марта 2012

Вы используете COUNT (*), когда вам нужно только NOT EXISTS ...

(
(SELECT count(*) 
 FROM TIMERECORD tr2 
 WHERE 
 tr2.empl_id=ep.empl_id AND 
 tr2.proj_id=p.id AND tr2.day <= ? + INTERVAL 7 DAY AND 
 tr2.day >= ? + INTERVAL -14 DAY) <= 0
)

Заменить на

(
NOT EXISTS(SELECT * 
 FROM TIMERECORD tr2 
 WHERE 
 tr2.empl_id=ep.empl_id AND 
 tr2.proj_id=p.id AND tr2.day <= ? + INTERVAL 7 DAY AND 
 tr2.day >= ? + INTERVAL -14 DAY)
)

Теперь, если TIMERECORD существует, эта частьусловия where будет замыкать на ЛОЖЬ (НЕ ИСТИНА) без необходимости считать каждый TIMERECORD.

0 голосов
/ 26 марта 2012

Избавьтесь от подзапроса.

(1).Вычислите подзапрос отдельно со столбцами empl_id, proj_id, count (*) для всех empl_ids & proj_ids, где день находится в требуемом диапазоне.Это простая группа по запросу.

select empl_id,proj_id,count(*) as ct from TIMERECORD 
where day between (? + INTERVAL -14 DAY) and (? + INTERVAL 7 DAY)
group by empl_id,proj_id;

Вызовите этот набор результатов B

(2).Вычислите оставшийся запрос, как вы делаете сейчас.Назовите этот набор результатов A

(3).Выполните левое внешнее соединение B, используя столбцы empl_id, proj_id, которые являются общими в A & B

, затем в предложении where вы можете проверить значение в столбце B.ct, оно будет нулевым для всех комбинаций empl_id, proj_idдля которого не было найдено ни одной записи в таблице TIMERECORD для данного временного диапазона.

На самом деле вам даже не нужно считать (*), так как вас не беспокоит фактическое количество.Но позвольте мне не усложнять это больше, чем необходимо.

...