среда, 11 июля 2012 г.

Унарный минус

Вопрос

Начнем с небольшого вопроса. Что вернет такой запрос?
select count(distinct [i]) [count]
from (
        select -30.0 / 5.0 / 3.0 / 2.0 / 1.0 [i]
  union select  30.0 /-5.0 / 3.0 / 2.0 / 1.0
  union select  30.0 / 5.0 /-3.0 / 2.0 / 1.0
  union select  30.0 / 5.0 / 3.0 /-2.0 / 1.0
  union select  30.0 / 5.0 / 3.0 / 2.0 /-1.0
) [Data]



Было бы неплохо, если бы он возвращал 1, но на самом деле ответом будет 3.

Копнем чуть глубже

Давайте посмотрим какие данные нам возвращаются
      select 1 [index], -30.0 / 5.0 / 3.0 / 2.0 / 1.0 [i]
union select 2,          30.0 /-5.0 / 3.0 / 2.0 / 1.0
union select 3,          30.0 / 5.0 /-3.0 / 2.0 / 1.0
union select 4,          30.0 / 5.0 / 3.0 /-2.0 / 1.0
union select 5,          30.0 / 5.0 / 3.0 / 2.0 /-1.0











Согласитесь, довольно неожиданный результат.
Давайте разберемся, что же такое происходит в строках 2 и 3 и почему строки 1,4 и 5 возвращают верные значения.

Согласно статье из msdn Приоритет операторов (Transact-SQL) следующий
  1. ~ (побитовое НЕ) 
  2. * (умножение), / (деление), % (остаток от деления) 
  3. + (положительное), - (отрицательное), + (сложение), (+ объединение), - (вычитание), & (побитовое И), ^ (побитовое исключающее ИЛИ), | (побитовое ИЛИ) 
  4. =, >, <, >=, <=, <>, !=, !>, !< (операторы сравнения) 
  5. NOT 
  6. AND 
  7. ALL, ANY, BETWEEN, IN, LIKE, OR, SOME 
  8. = (присваивание)
Обратите внимание на пункты 2 и 3. В отличии от многих других платформ и того, чему меня учили в школе, в T-SQL унарная операция "-" имеет меньший приоритет, чем умножение и деление.
Таким образом, исходный код можно представить в виде

      select 1 [index], -(30.0 / 5.0 / 3.0 / 2.0 / 1.0) [i]
union select 2,          30.0 /-(5.0 / 3.0 / 2.0 / 1.0)
union select 3,          30.0 / 5.0 /-(3.0 / 2.0 / 1.0)
union select 4,          30.0 / 5.0 / 3.0 /-(2.0 / 1.0)
union select 5,          30.0 / 5.0 / 3.0 / 2.0 /-(1.0)

Теперь видно, что
  1. В строке № 1 сначала происходит деление, и лишь затем к результату применяется унарный минус.  
  2. Строка № 4 возвращает верный ответ, только из-за удачно подобранных делителей.
  3. И лишь  строка № 5 вычисляется так, как мы ожидаем.

Вывод

Такие проблемы встречаются не часто, но если о таком поведении SQL Server'a не подозреваешь, то отладка может занять довольно много времени. Решением проблемы может быть использование скобок для принудительного повышения приоритета операции.

1 комментарий: