Почему MySQL 5.7 упорядочен по столбцу временного ранга?

Мне нужно сформировать список сотрудников, ранжированных по процентам закрытия. Я построил следующую хранимую процедуру:

SET @cnt = 0;
SET @percent = 2.0;

SELECT
    CASE
        WHEN stats.close/(stats.open+stats.close) = @percent THEN @cnt
        ELSE (@cnt := @cnt + 1) 
    END rank,
    stats.employee,
    stats.close,
    stats.open,
    (@percent := stats.close/(stats.open+stats.close)) percent
         FROM stats
              WHERE stats.date = CURDATE()
                   ORDER BY percent

Возвращает

| Rank | Employee | Close | Open | Percent|  
|     1|    Smith|       9|     1|    0.90|  
|     2|    Jones|      75|    25|    0.75|  
|     3|      Zed|       1|     9|    0.10|  
|     3|    Adams|      10|    90|    0.10|

Этот запрос работает по своему прямому назначению, но после просмотра запроса он выглядит так, как будто не должен возвращаться правильно. Вот почему я так думаю:

MySQL обрабатывает SELECT до обработки ORDER BY. Таким образом, я предположил бы, что MySQL назначит ранги в любом порядке, который он решил выпустить из базы данных, а затем отсортирует результирующий набор. Я ожидаю, что это будет выглядеть так:

| Rank | Employee | Close | Open | Percent|  
|     3|    Smith|       9|     1|    0.90|  
|     2|    Jones|      75|    25|    0.75|  
|     4|      Zed|       1|     9|    0.10|  
|     1|    Adams|      10|    90|    0.10|

Почему это не так?

1 ответ

Решение

Использование @variables в любом порядке не является строго говоря надежным, но это обычное "хакерство" для имитации оконных функций, которых в MySQL все еще не хватает (планируется для v8.x, т.е. все еще в предварительной версии).

Этот "взлом" основан на том, что ORDER BY обрабатывается вместе с SELECT (не как 2 отдельных шага), они более интегрированы, чем вы ожидали. например

SELECT * 
FROM   (SELECT CASE 
                 WHEN stats.close / ( stats.open + stats.close ) = @percent THEN 
                 @cnt 
                 ELSE ( @cnt := @cnt + 1 ) 
               end                                                        rank, 
               stats.employee, 
               stats.close, 
               stats.open, 
               ( @percent := stats.close / ( stats.open + stats.close ) ) 
               percent 
        FROM   stats 
        CROSS JOIN (@percent := 0 x, @cnt :=0 y) vars
        WHERE  stats.date = Curdate() 
        ORDER  BY percent ASC) d 
ORDER  BY percent DESC 

Внутренний порядок percent ASC установит ранги, начиная с наименьшего значения процента, затем внешний порядок будет ставить наибольший процент первым.

Реальная проблема заключается в том, что последовательность выражений слева направо (или сверху вниз, как видно выше) не гарантируется. Таким образом, возможно, что сравнение @percent для вычисления @cnt выполняется не так, как показано в написанном sql. На практике это работает "большую часть времени", но не может. (Так что возьмите на себя оконные функции!!)

Другие вопросы по тегам