- Другие части статьи:
- 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 |
Получаем:
Стало быть, от базы и ее коллейшена вообще ничего не зависит. Почему так? Напрашивается гипотеза, что раз временные таблицы создаются в базе tempdb, а она, как и все системные, имеет всегда и без исключения коллейшен тот же что и сервер — то именно он, «серверный» коллейшен, и «разруливает» ситуацию. Проверим гипотезу выполнением последнего кода на инстансе с регистрозависимым (CS) коллейшеном. Видим строчку
дважды, по разу на каждую из баз. Да, подтверждается — зависимость имен временных таблиц управляется коллейшеном сервера и только им.
Переключаемся на переменные, сначала обычные, в смысле не глобальные (не те, что на самом деле функции). «Прогоним» вот такой тест сначала на сервере с регистро-независимым (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; |
Итог:
Хмм... А тут уже несколько «страньше и страньше», выражаясь языком Алисы из страны чудес. Ну, допустим, для [~DB_CI~] результат ожидаем, регистр проигнорирован ровно как мы «заказывали». Но [~DB_CS~]-то куда «смотрит»?! Где то самое S из названия ее коллейшена? Мы же, во второй части скрипта работаем в контексте именно этой базы! Ну ладно, а что нам скажет сервер с регистрозависимым (CS) коллейшеном на «прогон» того же теста? А вот
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. Либо все три переменные из последнего отрывка указывают на одну и ту же ячейку памяти (если коллейшен сервера безразличен к регистру) и мы получаем предсказуемое
Variable names must be unique within a query batch or stored procedure.
при попытке второй декларации. Либо же (при коллейшене сервера чувствительного к регистру), все три переменные различны и нас вежливо «развернут» уже при попытке присвоить значение переменной, не задекларированной ранее:
Отсюда — мораль: не все логично выглядящие решения верные. Ну а практический вывод тот же что был для временных таблиц: зависимость имен локальных переменных вновь управляется коллейшеном сервера и только им.
Двигаемся дальше — параметры. Снова, не будет ли логично систему управления регистром и их имен сделать единообразно с предыдущими сущностями и вновь отдать полностью на «откуп» коллейшену сервера? Не будет! И вот почему: параметр (любого объекта который в принципе способен их иметь) является полноценным объектом базы. Ровно таким же как таблица, представление, колонка и т.д. Есть даже отдельное представление каталога объектов, а именно — 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~] и
@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) |
выполняется без проблем на обеих базах в обоих серверах. Да, вот так, «круг замкнулся» и мы делаем резюме полностью эквивалентное самому первому, когда мы только начинали рассматривать зарезервированные слова: имена системных встроенных (это важно!) функций никогда и ни при каких обстоятельствах от регистра не зависят.
Так зависит ли язык 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