О неявном преобразовании типов в SQL Server
Помимо типов данных в реляционной теории вводится фундаментальное понятие домена, как множества допустимых значений, которое может иметь атрибут. Можно сказать, что домен представляет собой пару {базовый тип данных, предикат}. При этом значение принадлежит домену только в том случае, если оно имеет соответствующий тип и предикат, вычисленный на этом значении, есть ИСТИНА. Атрибуты (столбцы таблицы) определяются на домене, то есть помимо контроля типов СУБД при каждом изменении данных должна проверять также значение предиката. Изменение будет отклонено, если сохраняемое значение не удовлетворяет предикату домена.
Домен играет еще одну важную роль, а именно, сравниваться могут только значения, принадлежащие одному домену. Рассмотрим в качестве примера таблицу PC, а именно, столбцы speed (тактовая частота процессора) и hd (объем жесткого диска). Оба эти столбца имеют тип integer (или smallint). Однако это совершенно разные характеристики. Достаточно сказать, что в предметной области для них используются разные единицы измерения — герцы и байты соответственно. Так вот, если мы определим эти столбцы на разных доменах, то сравнение значения одного столбца со значением другого станет недопустимым. Причем контролироваться это будет СУБД. По аналогии с категорной и ссылочной целостностью такой контроль можно было бы назвать доменной целостностью, если бы этот термин не был занят в SQL Server под проверку ограничения CHECK, наложенного на столбцы таблицы. А так определенная «доменная целостность» никак не ограничивает сравнения.
Не лишним будет напомнить о важности поддержания целостности на стороне СУБД. Ограничения целостности, как правило, моделируют ограничения, реально существующие в предметной области. Поскольку эти ограничения не зависят от приложений, естественно их проверять (и писать) в том месте, которое является общим для всех приложений, а именно, на стороне СУБД. Это помимо прочего:
- избавляет приложения от необходимости встраивать (и дублировать!) в них необходимые проверки;
- дает более высокий уровень безопасности; ограничения, встроенные в приложения, легко обойти — достаточно обратиться к базе данных, минуя приложение;
- если ограничения предметной области изменятся, то соответствующие программные изменения нужно будет сделать в одном месте, а не во всех приложениях, работающих с базой данных.
Возвращаясь к доменам, уместно заметить, что и стандарт языка SQL-92 не вкладывает в понятие домена смысла «сравнимости». То, что реализовано стандартом, не более чем возможность один раз записать ограничения, а затем неоднократно применять их при определении спецификаций столбцов, то есть дает возможность избежать дублирования кодов.
В цепочке «Теория > Стандарт > Реализация» последовательно теряется строгость реляционной теории, в результате чего мы не можем вполне прозрачно взаимодействовать с реляционными СУБД разных производителей. Здесь мы хотим показать небольшой пример того, как следует обращаться с типами в SQL Server.
Итак, реально мы имеем то, что сравниваться могут значения одного типа. Для преобразования типов стандарт предлагает функцию CAST. То есть в общем случае мы должны преобразовать сравниваемые значения к одному типу, а затем уже выполнять операцию сравнения (или присвоения). Что же произойдет, если мы переменной (или столбцу) одного типа просто присвоим значение другого типа? Рассмотрим простой пример кода на T-SQL (в примере используется SQL Server 2000):
DECLARE @vc VARCHAR(10), @mn MONEY, @ft FLOAT;
SELECT @vc = '499.99';
PRINT @vc;
SELECT @ft = @vc;
PRINT @ft;
Здесь мы описывает три переменные соответственно строкового типа (VARCHAR), денежного типа (MONEY) и числа с плавающей точкой (FLOAT). Далее строковой переменной присваиваем константу соответствующего типа, а затем присваиваем переменной типа FLOAT значение строковой переменной. В результате получаем два одинаковых результата — 499.99 (оператор PRINT осуществляет вывод сообщения на консоль). То, что произошло, называется неявным преобразованием типов, то есть строковое значение — ‘499.99’ было автоматически приведено к типу FLOAT и присвоено переменной @ft.
Добавим в конец кода еще пару строк:
SELECT @mn = @vc;
PRINT @mn;
В результате получим два похожих сообщения об ошибке:
SELECT @mn = CAST(@vc AS MONEY);
PRINT @mn;
От первого сообщения об ошибке мы избавились. Напрашивается вывод о том, что второе сообщение дает оператор PRINT. Попробуем заглянуть в справку. В документации на оператор PRINT говорится, что переменная, которая может использоваться в этом операторе, должна быть любого допустимого строкового типа (CHAR или VARCHAR) или же должна неявно приводиться к этому типу.
Перепишем последнюю строку:
PRINT CAST(@mn AS VARCHAR);
Все работает. Главный вывод, который мы отсюда извлекаем, заключается в том, что не все типы (даже если мы не видим особых причин тому) допускают неявное преобразование.
В частности, не выполняется неявное приведение типа MONEY к типам CHAR, VARCHAR, NCHAR, NVARCHAR и наоборот.
Замечание
Следует отметить, что неявное преобразование к типу MONEY уже поддерживается в SQL Server в версиях 2005 и выше.
А теперь о причине, которая заставила нас написать так много слов. Из-за нашей неряшливости оказалось, что в основной и проверочной базе на сайте некоторые эквивалентные столбцы имели разные типы данных. Например, поле price в таблице РС имело тип FLOAT в одной базе и тип MONEY — в другой. В течение очень долгого времени это никак не влияло на работу сайта sql-ex.ru, но вдруг почти одновременно два наших участника решили использовать неявное преобразование типов в своих запросах с уже известным результатом.
Мы решили не ограничиваться извинениями, а написать этот опус об особенностях реализации, надеясь, что это принесет больше пользы. Что же касается типов, то замеченное расхождение уже приведено в соответствие.
#Преобразование Типов#Типы Данных#SQL-92#CHECK#Домен#Varchar#MONEY#Convert#CAST#SQL Server 2000#Print