четверг, 23 февраля 2017 г.

Преобразование типов

Преобразование типов

В реализациях языка SQL может быть выполнено неявное преобразование типов. Так, например, в T-SQL при сравнении или комбинировании значений типов smallint и int, данные типа smallint неявно преобразуются к типу int. Подробно о явном и неявном преобразовании типов в MS SQL Server можно прочитать в BOL.
Пример. Вывести среднюю цену ПК-блокнотов с предваряющим текстом "средняя цена = ".
Попытка выполнить запрос
SELECT 'Средняя цена = ' + AVG(price) FROM laptop;

приведет к сообщению об ошибке

Implicit conversion from data type varchar to money is not allowed. Use the CONVERT function to run this query.

Это сообщение означает, что система не может выполнить неявное преобразование типа varchar к типу money. В подобных ситуациях может помочь явное преобразование типов. При этом, как указано в сообщении об ошибке, можно воспользоваться функцией CONVERT. Однако эта функция не стандартизована, поэтому в целях переносимости рекомендуется использовать стандартное выражение CAST. С него и начнем.
Если переписать наш запрос в виде
SELECT 'Средняя цена = ' + CAST(AVG(price) AS CHAR(15)) FROM laptop;

в результате получим то, что требовалось:
Средняя цена = 1410.44

Мы использовали выражение явного преобразования типов CAST для приведения среднего значения цены к строковому представлению. Синтаксис выражения CAST очень простой:CAST(<выражение> AS <тип данных>)
При этом следует иметь в виду, во-первых, что не любые преобразования типов возможны (стандарт содержит таблицу допустимых преобразований типов данных). Во-вторых, результат функции CAST для значения выражения, равного NULL, тоже будет NULL.
Рассмотрим еще один пример: определить средний год спуска на воду кораблей из таблицы Ships. Запрос
SELECT AVG(launched) FROM ships;

даст результат 1926. В принципе все правильно, т.к. мы получили в результате то, что просили - ГОД. Однако среднее арифметическое будет составлять примерно 1926,2381. Тут следует отметить, что агрегатные функции (за исключением функции COUNT, которая всегда возвращает целое число) наследуют тип данных обрабатываемых значений. Поскольку поле launched - целочисленное, мы и получили среднее значение с отброшенной дробной частью (заметьте - не округленное).
А если нас интересует результат с заданной точностью, скажем, до двух десятичных знаков? Применение выражения CAST к среднему значению ничего не даст по указанной выше причине. Действительно,
SELECT CAST(AVG(launched) AS NUMERIC(6,2)) FROM ships;

вернет значение 1926.00. Следовательно, CAST нужно применить к аргументу агрегатной функции:
SELECT AVG(CAST(launched AS NUMERIC(6,2))) FROM ships;

Результат - 1926.238095. Опять не то. Причина состоит в том, что при вычислении среднего значения было выполнено неявное преобразование типа. Сделаем еще один шаг:
SELECT CAST(AVG(CAST(launched AS NUMERIC(6,2))) AS NUMERIC(6,2)) FROM ships;

В результате получим то, что нужно - 1926.24. Однако это решение выглядит очень громоздко. Заставим неявное преобразование типа поработать на нас:
SELECT CAST(AVG(launched*1.0) AS NUMERIC(6,2)) FROM ships;
Т.е. мы использовали неявное преобразование целочисленного аргумента к точному числовому типу (EXACT NUMERIC), умножив его на вещественную единицу, после чего применили явное приведения типа результата агрегатной функции.
Аналогичные преобразования типа можно выполнить с помощью функции CONVERT:
SELECT CONVERT(NUMERIC(6,2),AVG(launched*1.0)) FROM ships;
Функция CONVERT имеет следующий синтаксис:
CONVERT (<тип данных>[(<длина>)], <выражение> [, <стиль>])
Основное отличие функции CONVERT от функции CAST состоит в том, что первая позволяет форматировать данные (например, темпоральные данные типа datetime) при преобразовании их к символьному типу и указывать формат при обратном преобразовании. Разные целочисленные значения необязательного аргумента стиль соответствуют определенным форматам. Рассмотрим следующий пример
SELECT CONVERT(char(25),CONVERT(datetime,'20030722'));

Здесь мы преобразуем строковое представление даты к типу datetime, после чего выполняем обратное преобразование, чтобы продемонстрировать результат форматирования. Поскольку значение аргумента стиль не задано, используется значение по умолчанию (0 или 100). В результате получим
Jul 22 2003 12:00AM

Ниже приведены некоторые другие значения аргумента стиль и результат, полученный на приведенном выше примере. Заметим, что значения стиль большие 100 приводят к четырехзначному отображению года.
стильформат
107/22/03
1103/07/22
322/07/03
1212003-07-22 00:00:00.000

Перечень всех возможных значений аргумента стиль можно посмотреть в BOL.

Оператор CASE

Пусть требуется вывести список всех моделей ПК с указанием их цены. При этом если модель отсутствует в продаже (нет в таблице РС), то вместо цены вывести текст: "Нет в наличии".
Список всех моделей ПК с ценами можно получить с помощью запроса:
SELECT DISTINCT product.model, price FROM product LEFT JOIN pc c
   ON product.model=c.model
   WHERE product.type='pc';

В результирующем наборе отсутствующая цена будет заменена NULL-значением:
modelprice
1121850
1232350
1232400
1232600
1233600
1233950
1233980
1260350
2111NULL
2112NULL

Чтобы заменить NULL-значения нужным текстом, можно воспользоваться оператором CASE:
SELECT DISTINCT product.model,
   CASE WHEN price IS NULL THEN 'Нет в наличии' ELSE CAST(price AS CHAR(20)) END price
   FROM product LEFT JOIN pc c ON product.model=c.model
   WHERE product.type='pc'

Оператор CASE в зависимости от указанных условий возвращает одно из множества возможных значений. В нашем примере условием является проверка на NULL. Если это условие выполняется, то возвращается текст "Нет в наличии", в противном случае (ELSE) возвращается значение цены. Здесь есть один принципиальный момент. Поскольку результатом оператора SELECT всегда является таблица, то все значения любого столбца должны иметь один и тот же тип данных (с учетом неявного приведения типов). Поэтому мы не можем наряду с ценой (числовой тип) выводить символьную константу. Вот почему к полю price применяется преобразование типов, чтобы привести его значения к символьному представлению. В результате получим
modelprice
1121850
1232350
1232400
1232600
1233600
1233950
1233980
1260350
2111Нет в наличии
2112Нет в наличии
Оператор CASE может быть использован в одной из двух синтаксических форм записи:
1-я форма
CASE <проверяемое выражение>
   WHEN <сравниваемое выражение 1>
   THEN <возвращаемое значение 1>
   …
   WHEN <сравниваемое выражение N>
   THEN <возвращаемое значение N>
[ELSE <возвращаемое значение>]
END

2-я форма
CASE
   WHEN <предикат 1>
   THEN <возвращаемое значение 1>
   …
   WHEN <предикат N>
   THEN <возвращаемое значение N>
[ELSE <возвращаемое значение>]
END
Все предложения WHEN должны иметь одинаковую синтаксическую форму, т.е. нельзя смешивать первую и вторую формы. При использовании первой синтаксической формы условие WHEN удовлетворяется, как только значение проверяемого выражения станет равным значению выражения, указанного в предложении WHEN. При использовании второй синтаксической формы условие WHEN удовлетворяется, как только предикат принимает значение TRUE. При удовлетворении условия оператор CASE возвращает значение, указанное в соответствующем предложении THEN. Если ни одно из условий WHEN не выполнилось, то будет использовано значение, указанное в предложении ELSE. При отсутствии ELSE, будет возвращено NULL-значение. Если удовлетворены несколько условий, то будет возвращено значение предложения THEN первого из них.
В приведенном выше примере была использована вторая форма оператора CASE.
Заметим, что для проверки на NULL стандарт предлагает более короткую форму оператора - COALESCE. Этот оператор имеет произвольное число параметров и возвращает значение первого, отличного от NULL. Для двух параметров оператор COALESCE(A, B) эквивалентен следующему оператору CASE:
CASE WHEN A IS NOT NULL THEN A ELSE B END
Решение рассмотренного выше примера при использовании оператора COALESCE можно переписать следующим образом:
SELECT DISTINCT product.model,
   COALESCE(CAST(price as CHAR(20)),'Нет в наличии') price
   FROM product LEFT JOIN pc c ON product.model=c.model
   WHERE product.type='pc';


Использование первой синтаксической формы оператора CASE можно продемонстрировать на следующем примере: Вывести все имеющиеся модели ПК с указанием цены. Отметить самые дорогие и самые дешевые модели.
SELECT DISTINCT model, price,
   CASE price WHEN (SELECT MAX(price) FROM pc) THEN 'Самый дорогой'
   WHEN (SELECT MIN(price) FROM pc) THEN 'Самый дешевый'
   ELSE 'Средняя цена' END comment
   FROM pc ORDER BY price;


В результате выполнения запроса получим
modelpricecomment
1232350Самый дешевый
1260350Самый дешевый
1232400Средняя цена
1233400Средняя цена
1233600Средняя цена
1121850Средняя цена
1233950Средняя цена
1233980Самый дорогой
Рекомендуемые упражнения: 3132475354.