среда, 12 января 2011 г. - www.msmirnov.ru

Стандарты и правила оформления кода T-SQL

Последнее время меня несколько раз спрашивали относительно стандартов и правил оформления кода на T-SQL , поэтому я решил выложить их в открытый доступ.

Для загрузки Стандарты и правила доступны по следующий ссылке на моем сайте: http://www.msmirnov.ru/public/TSQL_Coding_Standards.doc

Либо с ними можно ознакомиться прямо здесь.




Стандарт оформления кода T-SQL




Цель документа


Документ является соглашением по оформлению и написанию кода на языке T-SQL. В документе приведены основные правила оформления кода и приемы, используемые при написании программ.

Цели документа:

  • предоставить общие правила, позволяющие сохранить единый стиль написания кода, облегчив тем самым его понимание всеми участниками команды;

  • ввести базовые правила написания программ, что позволит повысить предсказуемость выполнения программ, а также избежать ошибок при написании программ новыми участниками команды, не знакомыми с внутренними стандартами разработки.


Стили именования

  • Pascal case первая буква каждого слова в имени идентификатора начинается с верхнего регистра.


Пример: TheCategory;


  • Camel case первая буква первого слова в идентификаторе в нижнем регистре, все первые буквы последующих слов в верхнем.


Пример: theCategory;


  • UpperCase стиль используется только для сокращений, все буквы в имени идентификатора в верхнем регистре.

Пример: ID;



  • Hungarian notation перед именем идентификатора пишется его тип в сокращенной форме.

Пример: strFirstName, iCurrentYear.




Правила именования объектов базы данных

  • Имена баз данных должны быть оформлены в стиле Pascal, нести осмысленное и логическое назначение и отвечать основному назначению баз данных.


  • Имена таблиц должны быть оформлено в стиле Pascal, нести осмысленное и логическое назначение и отвечать основному назначению таблиц. Не допускается использование пробелов или специальных символов в имени таблиц баз данных, а также символов национального алфавита.


  • Имена полей таблиц должно быть оформлено в стиле Pascal, нести осмысленное и логическое назначение и отвечать основному назначению полей. Не допускается использование пробелов или специальных символов в имени поля таблицы, а также символов национального алфавита.
    Первичные ключи в таблицах должны именоваться как ID.

В сложных случаях, когда есть базовая таблица и несколько зависимых, имя зависимой таблицы формируется из имени базовой таблицы и суффикса в конце имени,  для примера таблицы Campaign и CampaignGGL, CampaignMSN  и т.д.

Если таблица используется для хранения объектов определенных C#-классов, то ее имя должно совпадать с именем класса во множественном числе.


  • Имена индексов должны быть оформлены в стиле Camel. Не допускается использование пробелов или специальных символов в имени индекса таблицы, а также символов национального алфавита.

    В имени индекса должны быть перечислены имена всех полей, входящих в  выражение индекса, через символ «_».Если индекс включает в себя более пяти полей, достаточно в имени индекса перечислить первые 4-5 полей, и закончить строкой «_other».



  • Имена кластерных индексов должны начинаться с префикса «idx_».
    Имены некластерных индексов должны начинаться с «ndx_».
    Имена primary key должны начинаться с префикса «pk_».

Пример:

CREATE  CLUSTERED  INDEX [idx_TrackDate] ON [dbo].[EventTracking]([TrackDate])

CREATE
  INDEX [ndx_HitGUID]        ON [dbo].[EventTracking]([HitGUID]) ON [SECONDARY]

CREATE
  INDEX [ndx_SessionID_EventTypeID_TrackDate_VisitorID_Others] ON [dbo].[EventTracking]([SessionID] [EventTypeID], [TrackDate], [VisitorID], [Cost], [Profit])

CREATE
  INDEX [ndx_TrackDate_SessionID] ON [dbo].[EventTracking]([TrackDate], [SessionID])

  • Ограничения (constraints) должны быть поименованы следующим образом:

Foreign key constraints: «Fk» + +

Defaults: «Df» +

Checks: «Ck» +
<table name(Pascal case)> + <column name (Pascal case)>



  • Имена представлений должны быть оформлены в стиле Pascal, нести осмысленное и логическое назначение и отвечать основному его назначению, или же должно содержать в своем имени имя таблицы или имена таблиц, из которых происходит выборка данных.
    Не допускается использование пробелов или специальных символов в имени представления, а также символов национального алфавита. Обязательно наличие префикса «v» в имени представления.

  • Триггеры должны быть проименованы согласно следующим правилам:
    Префикс «Tr» + <TableName> + <TriggerType>.

    Если на одной таблице существует более одного триггера определенного типа, то в конце имени триггера добавляется порядковый номер такого триггера.
Для следующих типов триггеров определены следующие сокращения:

Insert I
Update
U
Delete
D


Например, триггеры на добавление, удаление и обновление для таблицы SomeTable  должны быть поименованы таким образом:

TrSomeTableI,
TrSomeTableD,
TrSomeTableD2,
TrSomeTableU

  • Полное имя объекта с использованием linked_server (четырехкомпонентные имена) формируется следующим образом:
    LINKED_SERVER_NAME.Catalog.schema.ObjectName


При этом имя linked_server-а указывается в upper-case, каталог(база данных) и имя объекта (таблица, хранимая процедура) указывается в стиле Pascal, имя схемы (владелец объекта) указывается в стиле lower-case.


  • Имена хранимых процедур и функций должны быть оформлены в стиле Pascal, нести осмысленное и логическое назначение и отвечать основному его назначению. Не допускается использование пробелов или специальных символов в имени хранимой процедуры, а также символов национального алфавита.


В общем случае имя хранимой процедуры, функции должно состоять из:
<[project name, task name, service name] prefix (camel case)> + «_» + <object name (Pascal case)> + <operation name (Pascal case)>;

  • <[project name, task name, service name] prefix> - наименование проекта (применительно к c# solution) или процесса обработки данных, задачи, сервиса, в работе которой используется данная хранимая процедура или функция. Если это наименование подвергается сокращению, то даже сокращенное наименование должно быть понятным всем разработчикам, а не только разработчику данной процедуры, функции.

    В качестве примера, если это вспомогательная процедура для поддержки логики работы всех остальных, то используется префикс [support], если используется в работе приложения MyApplication то [myApplication] и т.д.

  • <operation name> - название операции, которая применяется к  <object name>. Если под операцией понимается какое-либо изменение конкретной таблицы, тогда допустимы следующие имена <operation name>.
    • Get возврат только одной строки;
    • Select возврат 1 и более строк или 1 и более наборов данных;
    • Insert Вставка в одну или более таблиц. Недопустимо возвращать из такой процедуры набор данных. Return code должен содержать признак 0 или 1 как признак удачного или неудачного выполнения;
    • Update Обновление данных в одной или более таблиц. Недопустимо возвращать из такой процедуры набор данных. Return code должен содержать признак 0 или 1 как признак удачного или неудачного выполнения;
    • Delete Удаление данных из одной или более таблиц. Недопустимо возвращать из такой процедуры набор данных. Return code должен содержать признак 0 или 1 как признак удачного или неудачного выполнения;
    • <другой> - если операция не попала ни в одну из перечисленных;
      Если под операцией понимается название какого-либо подпроцесса, логической операции, относящейся к группе таблиц в этом случае необходимо использовать логическое наименование этого подпроцесса, операции здесь допустимо как существительное (имя операции, например, backup), так и повелительное наклонение глагола (load, upload, remove и пр.).



  • <object name> - это название объекта или группы объектов, над которым будет выполнена <operation name> -  описанная выше операция. Это может быть как имя конкретной таблицы, так и логическое наименование любой сущности, которая имеет свое отражение в базе данных.

Пример:


- если в системе есть процесс
MyProcess, которая делает backup каких-либо данных, то возможно такое наименование хранимой процедуры:
[dbo].[ myProcess_MyDataBackup];


- если тот же самый процесс имеет процедуры для добавления новой записи в справочник
Dictionary, то возможно следующее наименование:
[dbo].[ myProcess_DictionaryInsert];


- если в пределах упомянутого процесса
MyProcess мы хотим логически выделить другой подпроцесс, скажем, работу с какими-либо настройками, то возможно следующее наименование процедуры для операции, например, вставки данных в таблицу SomeTable:

[dbo].[ myProcessSettings_SomeTableInsert];

  • В тех случаях, когда возникает необходимость разнести код и логику хранимой процедуры (а эта необходимость возникает, как правило, в случаях разрастания кода), следует разбить исходную хранимую процедуру на несколько, придерживаясь следующего правила именования вновь создаваемых процедур:

    Имя вновь созданной хранимой процедуры формируется как
<имя вызывающей хранимой процедуры> + «_» + <object name (Pascal case)> <operation name (Pascal case)>;



Оформление t-sql кода при написании user defined functions, stored procedures, triggers и прочих вспомогательных скриптов.



  • Любой код в теле ветвлений, циклов, блоков кода должен быть выделен отступом размером один tab. Размер Tab Size принимается равным 4 пробела. Условие выражений if, while отделяем от данных инструкций символом tab таким образом, чтобы сам t-sql код внутри блоков был выравнен по началу условия ветвления, цикла,



Пример:

begin

      
if        <condition>
      
begin
       
       <        condition body

       
               ...................
       
       >

       
       while        <while_condition>
       
       begin

       
               <        while loop body

       
                       ........................

       
               >
       
       end        

      
end       
end

В любых операциях операнды должны быть отделены от операторов одним пробелом.
На примере операций присваивания, сравнения:


declare        @variable        int
set
        @variable = 1

if
        @variable <> 1
begin

...

end


В выражениях, в функциях, в операциях вызова хранимых процедур, функций, в любых сложных выражениях, все аргументы должны быть отделены от предыдущего значения запятой + <один пробел>, например:


select
       substring(convert(nvarchar(10), getdate(), 21), 1, 4)

  • В запросах и подзапросах алиасы таблиц формируются путем сокращения имен участвующих в запросе таблиц, например, Users -> U и т.д.

  • Все комментарии в коде необходимо оформлять, используя «--» + 1 tab symbol перед началом текста самого комментария. Если комментарий идет перед началом блока, сам текст комментария должен начинаться с той же позиции, что и t-sql код блока т.е. символ начала комментарии «--» должен отставать от позиции начала блока на один tab size, для примера:

--        начало комментария
--
       ...........................
      
if        <condition>

      
begin
       
       <condition body>
      
end

Для более удобного восприятия кода, содержащего большое количество ветвлений, а также блоков кода (begin/end), размер текста которого превышает размеры страницы, рекомендуется оформлять end-statement  комментарием, содержащим или условие if-statement, или условие цикла (while), для примера:

--        начало комментария

--
       ...........................
      
if        <condition>
      
begin
       
       <        condition body
       
               ...................
       
       >
       
       while        <while_condition>
       
       begin

       
               <        while loop body

       
                       ........................

       
               >
       
       end        --        while        <while_condition>
       
      
      
end        --        if        <condition>



  • Ко всем встроенным инструкциям t-sql языка программирования в коде хранимых процедур, триггеров, пользовательских функций должен применяться стиль оформления lower-case. Это относится как к select, insert, update, delete, create и прочим инструкциям, так и к выховам extended stored procedures и встроенным функциям (substring(), getdate() и пр.).



К пользовательским объектам базы данных должен применяться стиль оформления Pascal, т.е. к пользовательским функциям, хранимым процедурам, именам таблиц и пр.

Стиль оформления Pascal должен применяться также и при работе с локальными переменными.


Стиль upper-case должен применяться только в alter-скриптах и только применительно к инструкциям create, select, update, delete, insert.



  • При  создании хранимой процедуры после ее имени с новой строки после объявления ее параметров должна быть указана в комментариях следующая информация:
    - дата создания,
    - кем была создана процедура
    - ее назначение.
    После каждого какого-либо существенного изменения эти изменения должны быть также зафиксированы в истории работы над кодом хранимой процедуры.
    Комментарий о произведенных изменениях оформляется аналогично оформлению комментария о назначении процедуры (Все комментарии оформляются согласно стилю оформления комментариев) .


Пример:
ALTER        PROCEDURE        [dbo].[support_LostDBRecordsProcessor]
      
@Dir                        nvarchar(100)        =        'c:\logs\LostDBRecords',

      
@FileExtension        nvarchar(100)        =        '*.txt',

      
@DateFormat                nvarchar(10)        =        'dmy'

--
       2010.02.07 created by smirnov a
--
       lost db records processor implementation

--
       This procedure must scan ..\Logs\LostDBRecords directory and execute
--
       the bulk insert operation for current
--
       database and after that create .cab archives or create a .log file for a

--
       corresponding  file in case of an error
--
       2010.04.06 modified by smirnov a

--
       sql server version analisys was added. If @version variable equals 2005, we must
--
       remove the "nooutput" parameter from xp_cmdShell
AS
begin
       

Те же самые правила относятся к оформлению user defined functions с той лишь разницей, что комментарии о создании, внесении изменений вносятся после объявления параметров.

История изменений должна логически подниматься вверх по коду таким образом, чтобы последний комментарий о внесенном изменении был всегда сверху над остальными, НО после первого комментария о создании процедуры, функции, триггера. Таким образом, при чтении кода сразу видим назначение процедуры, ее первоначального автора, и информацию о самом последнем изменении.


Пример:
ALTER PROCEDURE         [dbo].[support_SearchDDLChanges]
      
@TableName        nvarchar(100)         = null

--
       2010.01.01        created by <author>
--
       Данная процедура предназначена для выполнения операции сравнения

--
       DDL описаний таблиц из указанной базы данных по сравнению с эталонной

--
       2010.03.01        modified by <changes_author>

--
       bugfix 0001 - скрипт неправильно работал

--
       2010.02.01        modified by <changes_author>

--
       Добавлена функциональность по автоматическому обновлению структуры
--
       указанной базы данных
--
       ....

AS

Если имеет смысл указать в самом t-sql коде, в каком именно месте были произведены изменения, перед новым или измененным выражением стоит поставить комментарий в таком формате, как и в истории изменений, если не требуется - без описания произведенных изменений.


--
       <date> modified by <author>

--
       bugfix <bug number> or additional description

      
insert        into        ....
      


  • Параметры хранимой процедуры, функции должны быть именованы в стиле Pascal и нести осмысленный логический смысл. Объявление  параметра должно начинаться с новой строки с отступом 1 tab size. Если смысл параметра непонятен, после объявления ее типа, значения по умолчанию (если есть) должен быть комментарий о назначении данного параметра. Если комментарий не убирается в пределах окна редактирования по ширине, его необходимо сделать многострочным, применяя следующий стиль оформления:

   @FullDatabasePathLogs        nvarchar(100) = 'dbo',        

       
               --        Полный путь к базе данных (linked_server.catalog.owner),

       
               --        откуда будем брать нормализованные логи для обработки

  
@FullDatabasePathReports        nvarchar(100) = 'MyServer.MyDB.dbo'
       
               --        Полный путь к базе данных (linked_server.catalog.owner),                                --        на котором будут храниться отчеты - MyServer.MyDB.dbo


  • К выражениям select необходимо применять следующий стиль оформления: имя каждого выражения (операции над column), поля должно быть на отдельной строке; каждое следующее выражение, поле должно начинаться с начальной позиции предыдущего; первое выражение, поле должно быть отделено от select statement отступом 1 tab size, как в примере ниже:


select
       column_01,
       
       expression(column_02, operand_01, operand_02, ..),
       
       column_03

Выражения from, join, where, order, group, having должны  быть на одном уровне с выражением select. К перечисленным выражениям инструкции  select  применяем такие же правила, т.е. остальную часть подвыражений отделяем отступом в 1 tab size.

Если условие в where clause, join clause состоит из нескольких логических выражений, то каждое такое логическое выражение должно начинаться с новой строки, причем сам логический оператор (Boolean expressions and, or, not), связывающий эти выражения в условии, должен быть вначале перенесенного выражения.


Условие связывания таблиц в join-ах (on-expression) должно записываться следующим образом: сначала указывается имя поля из той таблицы (или алиаса) , по которой  описывается join filter (собственно, указывается в join on <table_name>), затем  значение самого фильтра.

Все вышеперечисленное можно показать на примере:


select        count(*),
       
       table_01.column_01,

       
       expression(table_01.column_02, operand_01, operand_02, ..),

       
       table_02.column_03
from
       table_01

join
       table_01 on        table_01.column_01 = table_02.column_01        
       
                       and                <boolean expression>       
join
       table_nn on table_nn.column_03 = table_02.column_03

where
       table_01.column_03 not in (.....        )                
       
       and        <where boolean expression 01>   
       
       or        <where boolean expression 02>       
group
       by table_01.column_01,
               expression(table_01.column_02, operand_01, operand_02, ..),
       
       table_02.column_03
having
       count(*) > 10
order
       by table_01.column_01
select
       count(*),
       
       table_01.column_01,
       
       expression(table_01.column_02, operand_01, operand_02, ..),
       
       table_02.column_03
from
       table_01,
       
       table_02,
       
       table_nn ............

  • В выражении insert into список полей в выражении insert указывается одной строкой, если список не убирается в одну строку, переносится так, чтобы список был виден в пределах ширины страницы.

    Into statement должен начинаться с той же позиции, что и список полей из select_list из выражения select.


Select statement оформляется согласно описанным выше правилам.

Пример:
insert        into        <destination_table>        (field_01, field_02, field_03, field_04,...., field_nn)
select
       count(*),
       
       table_01.column_01,
       
       expression(table_01.column_02, operand_01, operand_02, ..),
       
       table_02.column_03,
       
       .....

....................................

select
       count(*),
       
       table_01.column_01,
       
       expression(table_01.column_02, operand_01, operand_02, ..),
       
       table_02.column_03,
       
       .....
into
       <destination_table>      
....................................

  • Для операций update, delete применяются те же правила, что и при оформлении select, insert выражений, т.е. операции присваивания над полями таблицы должны быть отделены от set на 1 tab size, каждая операция set в update-выражении должна начинаться с новой строки и начинаться с той же позиции, что и предыдущая. Операнды в операции присваивания должны быть отделены одним пробелом или tab от символа «=» (смотри рекомендации по отступам).

Сразу же за ключевым словом update или delete должен идти один tab, список обновляемых полей в выражении update должен быть на одном уровне с именем таблицы, так же как и само условие в выражении where. При описании where-условия придерживаемся тех же правил, что и при написании select statement.



Пример:

update
       <SourceTable>
set
               <Column_01> = <value01>,
       
       <Column_02> = <value02>,
       
       <Column_nn> = <valueNN>
where
       <condition_01>
       
       and <condition_02>



delete
       from <SourceTable>
where
       <condition_01>
               and <condition_02>


  • Выражение create table оформляется таким образом, чтобы описание каждого поля начиналось с новой строки. Желательно форматировать список колонок таким образом, чтобы типы данных находились один над другим, т.е. применяем колоночный стиль оформления.

    Пример:
create        table <table_name>        (        Column_01        int        identity (1, 1) primary key,
       
                                       Column_02        nvarchar(100) not null,
       
                                       Column_03        bit null,
       
                                       CreateDate        datetime default (getdate())        )



  • При объявлении курсора select statement должно начинаться с новой строки с той позиции, с которой начинается описание типа переменной курсора. К оформлению select statement применяются все вышеописанные правила.


declare
       columns_list        cursor        local forward_only for
       
                       select        <column_01>,
       
                               <column_02>,
       
                               <column_nn>
       
                       from        <source_table>
       
                       where        <where_condition>

declare
       @Columns_list                cursor       
set
               @Columns_list =        cursor        local forward_only for
       
                               select        <column_01>,
       
                                       <column_02>,
       
                                       <column_nn>
       
                               from        <source_table>
       
                               where        <where_condition>



  • При объявлении в t-sql коде переменных желательно придерживаться табличного стиля оформления, т.е. типы данных переменных должны начинаться с одной определенной позиции, имена переменных аналогично, приблизительно как показано ниже:


declare
       @Variable_01        int,
       
               @Variable_02        nvarchar(100),
       
               @Variable_04        bit
--
       или
      
declare        @Variable_01        int
      
declare        @Variable_02        nvarchar(100)
      
declare        @Variable_04        bit


Общие рекомендации.

Переменные.
Все переменные необходимо объявлять в начале блока, в котором они используются: метода, цикла, ветвления и пр.


Временные таблицы малого объема сохранять в памяти (вместо # - @), кроме тех случаев, когда это накладывает неприемлемые ограничения.
Т.е. вместо create table #temp (ID int)
использовать declare @temp table (ID int)


Требования, обязательные к применению.


  • Необходимо создавать связи для всех таблиц в базе данных.

  • Все таблицы должны быть отражены на соответствующих диаграммах.

  • Поля с допустимым значением NULL допускается использовать только в тех случаях, когда значение NULL несет логическое значение и не может быть заменено другим.

  • При добавлении новых полей допустимость значения NULL определяется в соответствии с предыдущим правилом.

  • Необходимо использовать типы минимально необходимого размера.

  • Для операции select необходимо указывать только те поля, которые требуются в данном запросе.
Пример:
select        ID, Keyword

from        KeywordParams (nolock)

  • Именование таблиц статусов: Имя статуса(с большой буквы)+Statuses.
Пример:
                        UserStatuses


  • Идентификаторы записей во всех таблицах должны называться ID.
Пример:
               в таблице Users поле идентификатора должно называться ID, а не UserID.


  • Идентификаторы в статических таблицах (справочниках) не должны быть помечены как identity.

  • Ссылки на идентификаторы других таблиц именуются: Имя таблицы в единственном числе+ID.

Пример:
                     Если таблица Events ссылается на UserStatuses,
                     то поле внешнего ключа должно называться UserStatusID, а не StatusID.

  • Для всех запросов, где участвуют таблицы, которые не подвергаются изменению, в запросах select необходимо использовать (nolock) для каждой такой таблицы.

Пример:

select
       Keyword, KP.ID
from
       Keywords (nolock),
       
       KeywordParams KP (nolock)
where
       Keywords.ID = KP.KeywordID


  • Для update запросов, которые обрабатывают одну запись необходимо использовать (rowlock, updlock) для каждой таблицы, в которой происходит update.
Пример
update        Keywords with (rowlock, updlock)
set
               Keyword = 'text'
from
       Keywords
where
       Keywords.ID = 123



Мой сайт - www.msmirnov.ru

2 комментария:

  1. Спасибо, именно то что искал!

    ОтветитьУдалить
  2. Не понятен смысл updlock в
    update Keywords with (rowlock, updlock)
    это лишнее, запрос
    update Keywords with (rowlock)
    даст те же блокировки

    ОтветитьУдалить