Header RSS-подписка на обновления сайта eMail-подписка на обновления сайта

Регистрозависим ли язык T-SQL? Часть 2/2.


366bef3a

  • Другие части статьи:
  • 1
  • 2
  • вперед »
Идентификаторы переменных

Наконец — переменные, параметры процедур/функций и временные таблицы. Как ни удивительно, но данный раздел потребует от нас самой сложной «инфраструктуры» для опытов: оба сервера и обе базы, т.е. нам нужны все четыре возможных (по параметру «чувствительность к регистру») сочетания «коллейшен сервера»+«коллейшен базы». Начнем с конца — с таблиц. Запустим вот такой тест на сервере с регистро-независимым (CI) коллейшеном:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
USE master;
GO
IF DB_ID (N'~DB_CS~') IS NOT NULL DROP DATABASE [~DB_CS~];
IF DB_ID (N'~DB_CI~') IS NOT NULL DROP DATABASE [~DB_CI~];
GO
CREATE DATABASE [~DB_CS~] COLLATE SQL_Czech_Cp1250_CS_AS
GO
CREATE DATABASE [~DB_CI~] COLLATE SQL_Czech_Cp1250_CI_AS
GO

USE [~DB_CI~]
go
CREATE TABLE #justTemp (Col1 int)
go
DROP TABLE #justtemp
go
USE [~DB_CS~]
go
CREATE TABLE #justTemp (Col1 int)
go
DROP TABLE #justtemp

Получаем:

Command(s) completed successfully.

Стало быть, от базы и ее коллейшена вообще ничего не зависит. Почему так? Напрашивается гипотеза, что раз временные таблицы создаются в базе tempdb, а она, как и все системные, имеет всегда и без исключения коллейшен тот же что и сервер — то именно он, «серверный» коллейшен, и «разруливает» ситуацию. Проверим гипотезу выполнением последнего кода на инстансе с регистрозависимым (CS) коллейшеном. Видим строчку

Cannot drop the table '#justtemp', because it does not exist or you do not have permission.

дважды, по разу на каждую из баз. Да, подтверждается — зависимость имен временных таблиц управляется коллейшеном сервера и только им.

Переключаемся на переменные, сначала обычные, в смысле не глобальные (не те, что на самом деле функции). «Прогоним» вот такой тест сначала на сервере с регистро-независимым (CI) коллейшеном (далее, для экономии места, автор предполагает что базы данных [~DB_CS~]/[~DB_CI~] с соответствующими коллейшенами у вас уже созданы):

1
2
3
4
5
6
7
8
9
USE [~DB_CI~]
go
DECLARE @abc int;
SET @aBC=2;
go
USE [~DB_CS~]
go
DECLARE @abc int;
SET @aBC=2;

Итог:

Command(s) completed successfully.

Хмм... А тут уже несколько «страньше и страньше», выражаясь языком Алисы из страны чудес. Ну, допустим, для [~DB_CI~] результат ожидаем, регистр проигнорирован ровно как мы «заказывали». Но [~DB_CS~]-то куда «смотрит»?! Где то самое S из названия ее коллейшена? Мы же, во второй части скрипта работаем в контексте именно этой базы! Ну ладно, а что нам скажет сервер с регистрозависимым (CS) коллейшеном на «прогон» того же теста? А вот

Must declare the scalar variable "@aBC".
Must declare the scalar variable "@aBC".

что! Т.е. нам как бы намекают, что работа с переменными идет в контексте конкретной базы, а «рулит» всем снова коллейшен сервера? Да, сделано именно так! Причем не только для переменных, но и для имен меток команды GOTO (используемой, прямо скажем, не часто, но упомянем и ее). Создатели и программисты движка сервера обосновывают свой выбор тем, что переменная может быть декларирована в контексте одной базы, затем может произойти переключение контекстов, а лишь затем переменная будет использована в этом втором контексте. Если, говорят они, привязать регистро-чувствительность имени переменной к коллейшену базы, то переменная может то существовать, то «пропадать» в рамках одного пакета (batch). И вы знаете — с ними трудно спорить! Допустим, на секунду, выбран вариант «чувствительность имени переменной определяется коллейшеном той базы в контексте которой она декларируется». На самом деле — хорошее правило, логичное. НО!

1
2
3
4
5
6
USE [~DB_CI~]
DECLARE @aBC int; --OK
USE [~DB_CS~]
DECLARE @abc int; --тоже OK, регистр отличается, а база регистрозависимая
USE [~DB_CI~]
SET @Abc=5; --и?? куда именно 5-ку помещать? Если отбросить регистр (а база диктует именно это) у нас два варианта!

Ухватываете корень проблемы? При правиле же принятом на вооружение проблем ровно 0. Либо все три переменные из последнего отрывка указывают на одну и ту же ячейку памяти (если коллейшен сервера безразличен к регистру) и мы получаем предсказуемое

The variable name '@abc' has already been declared.
Variable names must be unique within a query batch or stored procedure.

при попытке второй декларации. Либо же (при коллейшене сервера чувствительного к регистру), все три переменные различны и нас вежливо «развернут» уже при попытке присвоить значение переменной, не задекларированной ранее:

Must declare the scalar variable "@Abc".

Отсюда — мораль: не все логично выглядящие решения верные. Ну а практический вывод тот же что был для временных таблиц: зависимость имен локальных переменных вновь управляется коллейшеном сервера и только им.

Двигаемся дальше — параметры. Снова, не будет ли логично систему управления регистром и их имен сделать единообразно с предыдущими сущностями и вновь отдать полностью на «откуп» коллейшену сервера? Не будет! И вот почему: параметр (любого объекта который в принципе способен их иметь) является полноценным объектом базы. Ровно таким же как таблица, представление, колонка и т.д. Есть даже отдельное представление каталога объектов, а именно — sys.parameters, которое занимается исключительно ними. Поэтому если принять последний принцип единообразия с прочими идентификаторами нарушится не менее стройное единообразие, правило для которого мы вывели ранее: регистрозависимость имен всех объектов уровня базы зависит от коллейшена той же базы. А параметры — это именно объект базы. Проверим?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
USE [~DB_CI~]
go
CREATE PROCEDURE mySP @StartPARAM [int] AS SELECT 1 AS num
GO
EXEC mySP @startparam=0
GO
CREATE FUNCTION myFN (@StartPARAM [int]) RETURNS int AS BEGIN RETURN 1 END
GO
DECLARE @m int
EXEC @m=myFN @startparam=2

USE [~DB_CS~]
go
CREATE PROCEDURE mySP @StartPARAM [int] AS SELECT 1 AS num
GO
EXEC mySP @startparam=0
GO
CREATE FUNCTION myFN (@StartPARAM [int]) RETURNS int AS BEGIN RETURN 1 END
GO
DECLARE @m int
EXEC @m=myFN @startparam=2

Получаем успешное выполнение для базы первой, [~DB_CI~] и

Procedure or function 'mySP' expects parameter '@StartPARAM', which was not supplied.
@startparam is not a parameter for procedure myFN.

для базы [~DB_CS~] вне зависимости от того на каком из двух серверов выполняется код. Итого: зависимость имен параметров любых программных объектов управляется коллейшеном базы и только им.

Наконец — системные функции, иногда «по инерции» называемые глобальными переменными. Ну — давайте рассуждать логично. :) Раз системные, но функции — значит это объекты базы. Какой базы? В данном случае имя ее не важно (хоть оно и Resource), важно именно прилагательное системная. Что это значит? Это значит, что коллейшен такой базы будет всегда совпадать с коллейшеном всего сервера. А значит и регистрозависимость имен объектов исследуемой в настоящий момент группы будет определяться коллейшеном системной базы (формально), а равно коллейшеном всего сервера (фактически). Логично? Да! А верно? Нет! :) Потому что имена всех системных функций (даже более правильно сказать — всех встроенных функций, не важно начинается их имя с «собачек» или нет)... вновь полностью «отвязаны» от регистра! Иными словами, код:

1
2
3
4
5
SELECT @@versION
SELECT @@idenTITY
SELECT @@ErrOr
SELECT host_NaMe()
SELECT StatS_daTE(1, 1)

выполняется без проблем на обеих базах в обоих серверах. Да, вот так, «круг замкнулся» и мы делаем резюме полностью эквивалентное самому первому, когда мы только начинали рассматривать зарезервированные слова: имена системных встроенных (это важно!) функций никогда и ни при каких обстоятельствах от регистра не зависят.

Читатели могут впасть в заблуждение, что раз «системные функции независимы как и зарезервированные слова», то и вообще все системные объекты будут вести себя подобным образом. Автор спешит их предотвратить от таких выводов — не будут! Системные представления (пример — sys.objects), системные хранимые процедуры (пример — sp_helprole), системные не встроенные (снова важно!) функции (пример — sys.fn_builtin_permissions) — все они (точнее их имена, конечно) будут зависеть от коллейшена... какого? Нет, не сервера — не угадали. :) И не системных баз, что суть одно и тоже. А от коллейшена той пользовательской базы из контекста которой происходит обращение к одному из таких объектов! Но это, как говорится, «совсем другая история». Надо и читателю оставить простор и стимул к исследованиям. ;)
Так зависит ли язык T-SQL от регистра?

И что же мы имеем «в сухом остатке»? Каков финальный вывод и заключение? Прав ли тот штамп с которого начиналась заметка? «It's», как говорится, «dependent on». Если считать что T-SQL есть всего лишь набор ключевых слов (аналог сферического коня) — штамп прав, никакой зависимости нет и в помине. Если же учесть, что тот же SELECT в отрыве от названия источника содержащего извлекаемые данные просто никому не нужен — штамп заблуждается, а местами (зависит от конкретного коллейшена) просто врет. А с практической точки зрения? Должны ли мы при написании «чистового» кода заботится о «сохранении» регистра или можно слегка облегчить себе жизнь? Вот вам несколько рекомендаций a.k.a. hints:

  • правило первое и самое важное: будьте последовательны! Выберите для себя (или своей команды) единый стиль кода и «бейте» постоянно в эту точку. Например, если согласно избранному стилю таблица называется OurVeryDearVIPCustomer, то вы не задумываясь пишете именно так (со всеми регистрами) тотально везде. В коде ее создания, в коде работы с нею (извлечение данных и т.д.), в комментариях к коду, в e-mail с вопросами к коллегам... Везде! Это должно происходить на автомате и никаких «внутренних диалогов» не порождать. Вашими лучшими друзьями в реализации этого пункта будут различные «подсказчики по коду» (известные под собирательным именем IntelliSense), особенно те из них что обладают большим количеством опций;
  • если правило первое соблюдено, то правило второе вам не нужно, но все же зафиксирую его: считайте T-SQL языком регистрозависимым, точка. Если хотите — сделайте исключение для ключевых слов. Но самое правильное, примите решение: все ключевые слова пишутся БОЛЬШИМИ (малыми?) буквами и после этого считайте регистрозависимыми и их тоже;
  • если вы написали код и он точно работает на одном сервере — не факт что он заработает на другом, «подобном» сервере. Помните, что коллейшен сервера влияет на очень многое, и на ваш T-SQL код в частности. Примечание: при соблюдении пунктов 1 и 2 данный пункт не актуален (разумеется автор не говорит что в этом случае такой переход будет полностью безболезненным, но, по крайней мере, вы уберете «грабли» хотя бы с той стороны и в том смысле что мы с вами обсуждаем, а это уже кое-что);
  • если база была перенесена с одного сервера на другой даже методом полного бэкапа — не факт что код успешно работавший с нею на старом сервере заработает на новом. Да, вы правильно догадались: другой коллейшен сервера со всеми вытекающими. Примечание: при соблюдении пунктов 1 и 2 данный пункт так же не актуален;

Автор благодарит читателей своего блога за внимание и время уделенное ими данной заметке и желает им «чистого» T-SQL кода и оптимальных запросов к базе, удачи всем и — до новых встреч!

  • Другие части статьи:
  • 1
  • 2
  • вперед »