Перейти к содержанию

Хранимые процедуры SQL

Рекомендации

При разработке и использовании хранимых процедур учитывайте следующие моменты:

  • В запросах select к таблицам рекомендуется указывать параметр with(nolock). Это позволяет избежать блокировок таблиц во время чтения данных и сохранить производительность системы.

  • Псевдонимы (alias) колонок, возвращаемых функциями SQL, не должны совпадать с именами существующих колонок таблиц БД.

  • Для проверки прав доступа пользователя к задачам в запросе может использоваться JOIN с таблицей UserTaskPermissions, в которой хранятся права на задачи, полученные пользователями всеми возможными в "Первой Форме" способами.

Основные таблицы БД "Первая Форма", которые используются при составлении запросов, и особенности обращения к ДП различных типов описаны здесь.

Примеры хранимых процедур для иерархий приведены в разделе с описанием иерархий.

Вызов веб-сервисов с помощью SQL

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

Для работы веб-сервисов "Первой Формы" необходимо выдать права роли, в которую входит служебный пользователь D10TaskUser.

GRANT EXECUTE ON [sys.sp_OASetProperty] TO {D10TaskUserRole}
GRANT EXECUTE ON [sys.sp_OAMethod] TO {D10TaskUserRole}
GRANT EXECUTE ON [sys.sp_OAGetErrorInfo] TO {D10TaskUserRole}
GRANT EXECUTE ON [sys.sp_OADestroy] TO {D10TaskUserRole}
GRANT EXECUTE ON [sys.sp_OAStop] TO {D10TaskUserRole}
GRANT EXECUTE ON [sys.sp_OACreate] TO {D10TaskUserRole}
GRANT EXECUTE ON [sys.sp_OAGetProperty] TO {D10TaskUserRole}

Также надо выдать права на создание экземпляров объектов OLE-автоматизации в пакетах Transact-SQL.

Процедура

Параметры и краткое описание

tc/_ws/_addAcceptant

Добавление подписчика в текущую запрошенную динамическую подпись.

Параметры:

  • @TaskSignatureId (int) — номер запрошенной подписи, обязателен,

  • @InitUserID (int) — ID пользователя который добавил акцептанта, обязателен,

  • @UserID (int) — ID пользователя, которого добавляют в акцептанты, обязателен ,

  • @WriteComment(bit) — нужно ли писать комментарий, необязателен,

  • @SendEmail(bit) — отправлять ли письмо, необязателен.

Пример кода:

DECLARE @id int
select @id = ID from TaskSignatures
where signatureId = 5 and taskId = @taskId and SignatureStateID = 1
EXEC dbo.tc_ws_addAcceptant
        @TaskSignatureID=@id,
        @InitUserID=2,
        @WriteComment=0,
        @SendEmail=0,
        @UserID=123

tc_ws_addComment

Добавление комментария в задачу.

Параметры:

  • @UserID (int) — ID пользователя, от имени которого генерируется комментарий,

  • @TaskID (int) — ID задачи, в которой пишут комментарий, обязателен,

  • @Comment nvarchar(8000) — текст комментария, обязателен.

Пример кода:

declare @Comment varchar(1000)
set @Comment = 'привет, мир!'
exec dbo.tc_ws_addComment @UserID = 123, @TaskID = @taskID, @Comment = @Comment

tc_ws_addNewPerformer

Назначение нового исполнителя задачи.

Параметры:

  • @UserID (int) — ID пользователя, является исполнителем,

  • @TaskID (int) --- ID задачи, в которой назначается новый исполнитель,

  • @SessionUserID (int) — ID пользователя, от имени которого выполняется действие, обязателен.

Пример кода:

EXEC tc_ws_addNewPerformer
     @TaskID = @TaskID,
     @UserID = 123,
     @SessionUserID = 1

tc_ws_AddNewSignatureToTask

Запрос подписи в задачу.

Параметры:

  • @TaskID (int) — ID задачи, в которой запрашивают подпись, обязателен,

  • @SignatureID (int) — ID подписи, которую запрашивают, обязателен,

  • @UserID (int) — ID пользователя, который запрашивает подпись, обязателен,

  • @IsDynamic (bit) — динамическая ли подпись, необязателен,

  • @Reason (varchar(max)) — причина запроса подписи, необязателен.

Пример кода:

exec dbo.tc_ws_AddNewSignatureToTask
  @taskid = @taskid,
  @signatureid = 3,
  @userid = 123 ,
  @isDynamic = 1,
  @reason ='нужно ли это делать?'

tc_ws_addSubscriber

Добавление пользователя в подписчики задачи.

Параметры:

  • @UserID (int) — ID пользователя, которого надо подписать к задаче, обязателен,

  • @TaskID (int) — ID задачи, к которой надо подписать, обязателен,

  • @IsSubscribeChildTask (bit) — подписывание к дочерним задачам, необязателен,

  • @SubscribeType (int) — тип подписки. Значения:

o1 — Добавление вручную

o2 — Акцептант

o3 — Скопирован из родительской

o4 — Консультант\Исполнитель\Заказчик\Автоподписка

o5 — Не определено

o6 — Прочее\Автоматизация

o7 — Участник подписанной группы

  • @SessionUserId (int) — ID пользователя, от имени которого выполняется действие, обязателен.

Пример кода:

exec dbo.tc_ws_addSubscriber
   @userid =  123,
   @taskid = @taskid,
   @SessionUserId = 1

tc_ws_addSubscriberMailed

Добавление пользователя в подписчики с уведомлением по почте (на данный момент на почту уведомления не отправляются, фактически работает как dbo.tc_ws_addSubscriber).

Параметры:

  • @UserID (int) — ID пользователя, которого надо подписать к задаче, обязателен,

  • @TaskID (int) — ID задачи, к которой надо подписать, обязателен,

  • @IsSubscribeChildTask (bit) — подписывание к дочерним задачам, необязателен,

  • @ActingUserID --- ID пользователя, который подписывает, обязателен.

Пример кода:

exec dbo.tc_ws_addSubscriberMailed
   @userid =  123,
   @taskid = @taskid,
   @IsSubscribeChildTask = 0,
   @actinguserid = 1

tc_ws_AddUserComment

Отправление комментарий конкретному пользователю.

Параметры:

  • @TaskID (int) — ID задачи, в которой надо написать комментарий, обязателен,

  • @CommentRecipients (nvarchar(4000)) — список через точку с запятой логин (на английском) получателей, обязателен,

  • @UserID (int) — ID пользователя отправителя, обязателен,

  • @Text (nvarchar(8000)) — текст комментария, обязателен,

  • @NeedSubscribe (bit) — нужно ли подписываться на задачу, необязателен

tc_ws_AddUserCommentWithSms

Отправление пользователю комментарий через смс (требуется подключение к системе Clickatell).

Параметры:

  • @TaskID (int) — ID задачи, в которой надо написать комментарий, обязателен,

  • @CommentRecipients (nvarchar(8000)) — список через точку с запятой логин (на английском) получателей, обязателен,

  • @UserID (int) — ID пользователя отправителя, обязателен,

  • @Text (nvarchar(8000)) — текст комментария , обязателен,

  • @NeedSubscribe (bit) — нужно ли подписываться на задачу, необязателен

tc_ws_changeTaskStep

Совершение перехода в задаче.

Параметры:

  • @StepID (int) — номер перехода который надо сделать, обязателен,

  • @Userid (int) — ID пользователя, который делает переход, обязателен,

  • @Taskid (int) — ID задачи, в которой надо сделать переход, обязателен

  • @InitiatorUserId (int) — ID пользователя-инициатора перехода,

tc_ws_DeletePerformerFromTask

Удаление исполнителя из задачи.

Параметры:

  • @Taskid (int) — ID задачи, в которой надо удалить исполнителя, обязателен,

  • @Userid (int) — ID пользователя, которого удаляют из исполнителей, обязателен,

  • @SessionUserId (int) — ID пользователя, от имени которого выполняется действие, обязателен.

tc_ws_DeleteRequestedSignature

Удаление запрошенной подписи.

Параметры:

  • @UserID (int) — ID пользователя, который удаляет запрошенную подпись, обязателен,

  • @TaskSignatureID (int) — номер запрошенной подписи, обязателен

tc_ws_DeleteTask

Удаление задачи.

Параметры:

  • @UserID (int) — ID пользователя, который удаляет задачу, обязателен,

  • @TaskID (int) — ID задачи, которая удаляется, обязателен

tc_ws_LinkOrgUnitWithGroup

Установление связи между орг. единицей и группой пользователей.

Параметры:

  • @OrgUnitID (int) — ID орг. единицы, обязателен,

  • @GroupID (int) — ID группы, обязателен

tc_ws_moveTask

Перемещение задачи в категорию.

Параметры:

  • @UserID (int) — ID пользователя, от имени которого будет сделано перемещение, обязателен,

  • @TaskID (int) — ID перемещаемой задачи, обязателен,

  • @SubcatID (int) — ID категории, в которую будет перемещена задача, обязателен

tc_ws_newTask

Создание новой задачи.

Параметры:

  • @UserID (int) — ID пользователя, который будет являться заказчиком, обязателен,

  • @Task (nvarchar(8000)) — текст задачи, обязателен,

  • @OrderedTime (datetime) — срок задачи, обязателен (в зависимости от настройки категории),

  • @Category (int) — ID категории, в которую будет поставлена задача, обязателен,

  • @Comment (nvarchar(8000)) — первый комментарий к задаче, не обязателен,

  • @ExtParamStr (nvarchar(8000)) — служебная строка для заполнения доп. параметров, описание см. ниже, обязателен (в зависимости от настройки обязательных ДП при постановке в категории),

  • @PerfID (int) — ID исполнителя при постановке, обязателен (в зависимости от настройки категории),

  • @PriorityID (int) — ID приоритета задачи, не обязателен,

  • @D3TaskID — не используется,

  • @ParentID (int) — ID родительской задачи, не обязателен,

  • @UserNick (nvarchar(20))- ник пользователя, ставящего задачу, не обязателен (может использоваться вместо @UserID),

  • @Remind (bit) — напоминание о приближении срока задачи, не обязателен,

  • @PropagateSubscribers (bit) — подписать подписчиков родительской задачи, не обязателен,

  • @PropagateFiles (boolean) — перенести файлы в дочернюю задачу,

  • @TaskStartTime (datetime) — постановка даты начала работ, не обязателен

ℹ️ ID созданной задачи можно получить так:

EXECUTE @newtaskid = [dbo].[tc_ws_NewTask]

tc_ws_PeriodicExecution

Явное выполнение периодических заданий из очереди

tc_ws_updateExtParamsInTasks

Обновление дополнительных параметров в задаче.

Параметры:

  • @TaskID (int) — id задачи, обязателен,

  • @Str (nvarchar(8000)) — служебная строка для заполнения доп. параметров, описание см. ниже, обязателен,

  • @SessionUserID (int) — id пользователя, от имени которого выполняется действие, обязателен,

  • @Comment (bit) — если равен 1, то пишется системный комментарий об изменении ДП, если 0 — не пишется; по умолчанию равен 1; не обязателен

tc_ws_updateOrderedTime

Обновление срока задачи.

Параметры:

  • @TaskID (int) — ID задачи, обязателен,

  • @OrderedTime (datetime) — срок задачи, обязателен

tc_ws_updateTaskDescription

Обновление текста задачи.

Параметры:

  • @TaskID (int) — ID задачи, обязателен,

  • @Description (nvarchar(max)) — новый текст, обязателен

Служебная строка

@ExtParamStr или @Str

Заполняет/изменяет значение ДП.

Например, для одного ДП строка имеет вид:

'#nномер#vзначение::'

где номер — ID ДП, а значение — значение, которое должно быть записано в ДП.

Для двух ДП (и более — аналогично):

'#nномер1#vзначение1::#nномер2#vзначение2::'

ℹ️ Все значения должны быть приведены к типу varchar

Пример кода:

Пусть в задаче 15678 от имени пользователя "Диспетчер задач" нужно обновить ДП с ID=167 (тип "Текст"), записать в него значение 'Иван Иванович', а в ДП с ID=168 (тип "ДатаВремя") записать текущую дату.

Строку можно задекларировать следующим образом:

set @str = '#n167#vИван Иванович::#n168#v'+ cast (getdate() as varchar) + '::'

ID пользователя "Диспетчер задач" получим следующим образом:

select @UserID = SystemRobotID from Settings where CustomerID = 1

Наконец, сделаем вызов процедуры:

exec tc_ws_UpdateExtParamsInTasks
   @TaskID = 15678,
   @Str = @str,
   @SessionUserID = @userid

ℹ️ Обратите внимание:

  • чтобы сохранить символы '::' внутри значения ДП -- поставьте перед ними обратный слеш '\::'
  • чтобы поместить слеш в конце значения ДП -- продублируйте его '\::'

Пример работы с таблицей:

Пусть в ДП "Таблица" с ID=999 колонки 11 и 33 заполняются, а колонка 22 остается пустой.

declare @str as varchar (1000) = '+[{11:{"First":"Текст1111"},22:{"First":""},33:{"First":"Текст3333"}}]'
set @str = '#n999#v' + @str

Вызов опубликованного смарт-пакета

Если вы работаете с системой на уровне БД, меняяя данные в таблицах напрямую через SQL-запросы, периодически вы можете сталкиваться с тем, что внесенные в БД изменения применяются не сразу. Чтобы обойти это, можно использовать механизм публикаций.

Чтобы сделать http-запрос к опубликованному смарт-пакету, можно использовать [следующий скрипт](../.

Declare @Object as Int;
Declare @ResponseText as Varchar(8000);
Exec sp_OACreate 'MSXML2.ServerXMLHTTP', @Object OUT;
Exec sp_OAMethod @Object,
  'open',
  NULL,
  'get',
  'ссылка',
  'false'
Exec sp_OAMethod @Object, 'setRequestHeader', null, 'Content-Type', 'application/json'
Exec sp_OAMethod @Object, 'send'
Exec sp_OAMethod @Object, 'responseText', @ResponseText OUTPUT
Select  @ResponseText
Exec sp_OADestroy @Object

где ссылка — это Url для обращения к опубликованному смарт-пакету, с возможностью задать входящие параметры. Общий вид ссылки:

https://адрес_приложения/app/v1.2/api/publications/action/название_пакета?param1=value1&param2=value2...

Вызов задания

Некоторые хранимые процедуры нужно вызывать регулярно по расписанию. Часто для этого используются смарт-расписания. Но если процедура обрабатывает большие объемы данных (например, при интеграции с внешними системами), то смарт-расписание будет прерываться по тайм-ауту. Чтобы избежать этого, такие хранимые процедуры можно вызывать как задания (джобы).

Пример:

exec msdb.dbo.sp_start_job 'название_хранимой_процедуры'

Получение входящих параметров

Часто набор входящих параметров передается в формате XML (например, при использовании фильтров в иерархиях, виджетах, планировщиках и т.п.). Для обработки таких входящих параметров на MS SQL удобно использовать процедуру crm_repGetParams, поставляемую вместе с "Первой Формой".

Имя параметра передается как атрибут Name. Для периода времени передаются две даты — From и To, для остальных параметров передается значение Value.

Получение значений входящих параметров из формата XML с помощью процедуры crm_repGetParams может выглядеть следующим образом:

ALTER PROCEDURE [...]
(
 @XmlParam xml = null,
 @DrillDownField varchar(max) = null,
 @DrillDownParams varchar(max) = null,
 @UserID int = null
)

AS
BEGIN
 -- объявление параметров и вспомогательной таблицы
 declare @StartDate date = null, @EndDate date = null, @Filial int = null
 declare @Params table ([Name] varchar(max), [From] varchar(max), [To] varchar(max), [Value] varchar(max))

 -- запись значений параметров во вспомогательную таблицу
 insert into @Params ([Name], [From], [To], [Value])
               exec crm_repGetParams @XML = @XmlParam

 -- получение значений параметров
 select @StartDate = cast([From] as varchar(max)), @EndDate = cast([To] as varchar(max))
                 from @Params where Name = 'Period'

 select @Filial = cast([Value] as varchar(max))
         from @Params where Name = 'Filial'
 ...
END

Для PG процедура crm_repGetParams не используется. Пример передачи параметра для PG:

DECLARE
d1 DATE := NULL;
d2 DATE := NULL;
BEGIN
SELECT a.from_val, a.to_val into d1, d2 FROM dbo.ssl_get_filter_param_by_name(xmlparam::xml, 'period') as a;

Проверка прав доступа

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

Использование функции dbo.fn_CheckUserTaskPermissions

[Пример](../ вывода списка задач, которые доступны пользователю из определенной или любой категории.

declare @UserID int = 2950, @SubcatID int = 128;

-- доступные пользователю задачи из указанной категории
select t.TaskID from dbo.Tasks t with (nolock)
cross apply dbo.fn_CheckUserTaskPermissions (t.TaskID, @UserID, @SubcatID) chtp
where t.SubcatID = 128

-- доступные пользователю задачи из любой категории
select top 1000 t.TaskID from dbo.Tasks t with (nolock)
cross apply dbo.fn_CheckUserTaskPermissions (t.TaskID, @UserID, t.SubcatID) chtp
where t.IsClosed = 0

Использование функции dbo.fn_UserTaskPermissions()

[Пример](../ быстрого вывода списка задач, которые доступны пользователю из определенной категории.

select TaskID from dbo.fn_UserTaskPermissions (@UserID, @SubcatID)

Вместо объединения UserTaskPermissions и таблицы Tasks эффективнее использовать функцию проверки прав пользователя dbo.fn_UserTaskPermissions(). Она имеет два аргумента: @UserID (ID пользователя) и @SubcatID (ID категории). Функция возвращает все записи указанной категории, доступные указанному пользователю, и содержит все поля, которые есть в таблице Tasks.

Пример:

select  *
from   dbo.fn_UserTaskPermissions(@UserID, @SubcatID) utp
where   utp.StateId in (1, 2) and
      utp.PlannedEnd < getdate()

Объединение результата dbo.fn_UserTaskPermissions() с Tasks

Поскольку все поля можно брать из результата функции, в большинстве случаев нет необходимости дополнительно связывать в запросе результат dbo.fn_UserTaskPermissions() с таблицей Tasks — это создает лишнюю нагрузку на процессор.

Если же в вашем запросе невозможно обойтись без связи с таблицей Tasks, а простой join с dbo.fn_UserTaskPermissions() начинает заметно тормозить даже на небольшой выборке, попробуйте следующие варианты:

1. Вместо прямого join с dbo.ваша_таблица по TaskID используйте [конструкцию](../.

...
from   dbo.ваша_таблица
    cross apply
    (
        select top 1 *
        from   dbo.fn_UserTaskPermissions(@UserID, @SubcatID) utp
        where   utp.TaskID = dbo.ваша_таблица.TaskID
    ) utp
...

Это заставит сервер применить проверку прав после отбора по dbo.ваша_таблица.

2. Добавьте в самый конец оператора option (force order).

Использование dbo.fn_UserTaskPermissions() для нескольких пользователей и/или категорий

Если вам нужно получить задачи, доступные N пользователям (например, с учетом заместителей), и/или задачи из M категорий, функцию dbo.fn_UserTaskPermissions() необходимо вызывать N*M раз.

Пример 3:

Два пользователя с ID 111 и 222, две категории с ID 333 и 444.

select  *
from    (
          select * from dbo.fn_UserTaskPermissions(111, 333)
          union
          select * from dbo.fn_UserTaskPermissions(111, 444)
          union
          select * from dbo.fn_UserTaskPermissions(222, 333)
          union
          select * from dbo.fn_UserTaskPermissions(222, 444)
       ) utp
where   utp.StateId in (1, 2) and
      utp.PlannedEnd < getdate()

В данном примере используется union (а не union all) для исключения дублирующихся записей.

Пример 4 (применим если не используются поля из таблицы Tasks):

Два пользователя с ID 111 и 222, две категории с ID 333 и 444.

select distinct TagID
from   dbo.TaskTags tt
where   tt.TaskID in
       (
          select utp.TaskID from dbo.fn_UserTaskPermissions(111, 333) utp
          union all
          select utp.TaskID from dbo.fn_UserTaskPermissions(111, 444) utp
          union all
          select utp.TaskID from dbo.fn_UserTaskPermissions(222, 333) utp
          union all
          select utp.TaskID from dbo.fn_UserTaskPermissions(222, 444) utp
       )

В данном примере предпочтительнее использовать union all (а не union) из-за особенностей оптимизатора.

Использование dbo.fn_UserTaskPermissions() для неопределённого количества категорий

Если количество категорий заранее не известно, тогда применение функции dbo.fn_UserTaskPermissions() становится не настолько эффективным, но все же возможным. В этом случае рекомендуется провести эксперимент: в качестве второго параметра передать null и проанализировать снижение производительности. Если снижение существенно, стоит отказаться от использования функции dbo.fn_UserTaskPermissions() и использовать join с представлением UserTaskPermissions.

Доступные настройки

События, на которые можно назначить выполнение хранимых процедур в интерфейсе системы:

  • При постановке задачи: Категории и процессы — Категории — Настройки — опция Процедура при постановке (SQL) (Устаревшее, не используется)

  • При сохранении задачи: Категории и процессы — Категории — Настройки — опция Процедура при сохранении (Устаревшее, не используется)

  • При переходе по маршруту: Категории и процессы — Категории — Маршрут — Настройки перехода — Настройки — опция Процедура при переходе (SQL)

  • При подписании на переходе: Категории и процессы — Категории — Маршрут — Настройки перехода — Подписи на переходе — опция Процедура

  • При завершении обработки писем: Категории и процессы — Категории — Почта — Настройки перехода — Настройки почтового ящика — опция Процедура при завершении обработки (SQL)

  • При изменении статуса: Категории и процессы — Статусы — Настройки статуса — опция Выполняется встроенная процедура

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

Диагностика работы СУБД

С помощью процедуры sp_Blitz можно проанализировать производительность работы СУБД и выявить "узкие места".

Каждый вызов настраивает результат проверки через параметр @OutputType или фильтрует выводимые данные по уровню важности с помощью параметров @IgnorePrioritiesBelow и @IgnorePrioritiesAbove.

Примеры вызовов:

  • EXEC sp_Blitz;

Запускает полную диагностику и выводит список всех найденных проблем, предупреждений и рекомендаций в виде таблицы.

  • EXEC sp_Blitz @IgnorePrioritiesAbove = 50;

Показывает только критические проблемы с высоким приоритетом (до 50 включительно), скрывая менее важные сообщения.

  • EXEC sp_Blitz @IgnorePrioritiesBelow = 200;

Фильтрует вывод, оставляя только информационные сообщения и рекомендации с приоритетом от 200 и выше.

  • EXEC sp_Blitz @IgnorePrioritiesBelow = 50, @IgnorePrioritiesAbove = 150;

Выводит проблемы в определённом диапазоне приоритетов (от 50 до 150), исключая как очень критические, так и чисто информационные записи.

  • EXEC sp_Blitz @OutputType = 'COUNT';

Не выводит детальный список, а возвращает только общее количество найденных проблем.

  • EXEC sp_Blitz @OutputType = 'XML';

Формирует результат проверки в структурированном XML-формате.

  • EXEC sp_Blitz @OutputType = 'MARKDOWN';

Генерирует отчет в формате Markdown.

  • EXEC sp_Blitz @OutputType = 'CSV';

Возвращает данные в формате CSV (значения, разделённые запятыми).

  • EXEC sp_Blitz @OutputType = 'SCHEMA';

Возвращает не сами данные диагностики, а метаинформацию: схему (структуру) выходной таблицы с именами и типами столбцов.

Полезные ссылки

Администрирование хранимых процедур

Структура БД

Обращение к различным типам ДП в SQL запросах

Описание отдельных таблиц и значений