1. Главная страница » Компьютеры

Ms sql создание функций

Автор: | 16.12.2019

В языках программирования обычно имеется два типа подпрограмм:

определяемые пользователем функции (UDF).

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

Создание и выполнение определяемых пользователем функций

Определяемые пользователем функции создаются посредством инструкции CREATE FUNCTION, которая имеет следующий синтаксис:

Параметр schema_name определяет имя схемы, которая назначается владельцем создаваемой UDF, а параметр function_name определяет имя этой функции. Параметр @param является входным параметром функции (формальным аргументом), чей тип данных определяется параметром type. Параметры функции — это значения, которые передаются вызывающим объектом определяемой пользователем функции для использования в ней. Параметр default определяет значение по умолчанию для соответствующего параметра функции. (Значением по умолчанию также может быть NULL.)

Предложение RETURNS определяет тип данных значения, возвращаемого UDF. Это может быть почти любой стандартный тип данных, поддерживаемый системой баз данных, включая тип данных TABLE. Единственным типом данных, который нельзя указывать, является тип данных timestamp.

Определяемые пользователем функции могут быть либо скалярными, либо табличными. Скалярные функции возвращают атомарное (скалярное) значение. Это означает, что в предложении RETURNS скалярной функции указывается один из стандартных типов данных. Функция является табличной, если предложение RETURNS возвращает набор строк.

Читайте также:  Geforce gtx 680 zotac

Параметр WITH ENCRYPTION в системном каталоге кодирует информацию, содержащую текст инструкции CREATE FUNCTION. Таким образом, предотвращается несанкционированный просмотр текста, который был использован для создания функции. Данная опция позволяет повысить безопасность системы баз данных.

Альтернативное предложение WITH SCHEMABINDING привязывает UDF к объектам базы данных, к которым эта функция обращается. После этого любая попытка модифицировать объект базы данных, к которому обращается функция, претерпевает неудачу. (Привязка функции к объектам базы данных, к которым она обращается, удаляется только при изменении функции, после чего параметр SCHEMABINDING больше не задан.)

Для того чтобы во время создания функции использовать предложение SCHEMABINDING, объекты базы данных, к которым обращается функция, должны удовлетворять следующим условиям:

все представления и другие UDF, к которым обращается определяемая функция, должны быть привязаны к схеме;

все объекты базы данных (таблицы, представления и UDF) должны быть в той же самой базе данных, что и определяемая функция.

Параметр block определяет блок BEGIN/END, содержащий реализацию функции. Последней инструкцией блока должна быть инструкция RETURN с аргументом. (Значением аргумента является возвращаемое функцией значение.) Внутри блока BEGIN/END разрешаются только следующие инструкции:

инструкции присвоения, такие как SET;

инструкции для управления ходом выполнения, такие как WHILE и IF;

инструкции DECLARE, объявляющие локальные переменные;

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

инструкции INSERT, UPDATE и DELETE, которые изменяют переменные с типом данных TABLE, являющиеся локальными для данной функции.

По умолчанию инструкцию CREATE FUNCTION могут использовать только члены предопределенной роли сервера sysadmin и предопределенной роли базы данных db_owner или db_ddladmin. Но члены этих ролей могут присвоить это право другим пользователям с помощью инструкции GRANT CREATE FUNCTION.

В примере ниже показано создание функции ComputeCosts:

Функция ComputeCosts вычисляет дополнительные расходы, возникающие при увеличении бюджетов проектов. Единственный входной параметр, @percent, определяет процентное значение увеличения бюджетов. В блоке BEGIN/END сначала объявляются две локальные переменные: @addCosts и @sumBudget, а затем с помощью инструкции SELECT переменной @sumBudget присваивается общая сумма всех бюджетов. После этого функция вычисляет общие дополнительные расходы и посредством инструкции RETURN возвращает это значение.

Вызов определяемой пользователем функции

Определенную пользователем функцию можно вызывать с помощью инструкций Transact-SQL, таких как SELECT, INSERT, UPDATE или DELETE. Вызов функции осуществляется, указывая ее имя с парой круглых скобок в конце, в которых можно задать один или несколько аргументов. Аргументы — это значения или выражения, которые передаются входным параметрам, определяемым сразу же после имени функции. При вызове функции, когда для ее параметров не определены значения по умолчанию, для всех этих параметров необходимо предоставить аргументы в том же самом порядке, в каком эти параметры определены в инструкции CREATE FUNCTION.

В примере ниже показан вызов функции ComputeCosts в инструкции SELECT:

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

В инструкциях Transact-SQL имена функций необходимо задавать, используя имена, состоящие из двух частей: schema name и function name, поэтому в примере мы использовали префикс схемы dbo.

Возвращающие табличное значение функции

Как уже упоминалось ранее, функция является возвращающей табличное значение, если ее предложение RETURNS возвращает набор строк. В зависимости от того, каким образом определено тело функции, возвращающие табличное значение функции классифицируются как встраиваемые (inline) и многоинструкционные (multistatement). Если в предложении RETURNS ключевое слово TABLE указывается без сопровождающего списка столбцов, такая функция является встроенной. Инструкция SELECT встраиваемой функции возвращает результирующий набор в виде переменной с типом данных TABLE.

Многоинструкционная возвращающая табличное значение функция содержит имя, определяющее внутреннюю переменную с типом данных TABLE. Этот тип данных указывается ключевым словом TABLE, которое следует за именем переменной. В эту переменную вставляются выбранные строки, и она служит возвращаемым значением функции.

Создание возвращающей табличное значение функции показано в примере ниже:

Функция EmployeesInProject отображает имена всех сотрудников, работающих над определенным проектом, номер которого задается входным параметром @projectNumber. Тогда как функция в общем случае возвращает набор строк, предложение RETURNS в определение данной функции содержит ключевое слово TABLE, указывающее, что функция возвращает табличное значение. (Обратите внимание на то, что в примере блок BEGIN/END необходимо опустить, а предложение RETURN содержит инструкцию SELECT.)

Использование функции Employees_in_Project приведено в примере ниже:

Возвращающие табличное значение функции и инструкция APPLY

Реляционная инструкция APPLY позволяет вызывать возвращающую табличное значение функцию для каждой строки табличного выражения. Эта инструкция задается в предложении FROM соответствующей инструкции SELECT таким же образом, как и инструкция JOIN. Инструкция APPLY может быть объединена с табличной функцией для получения результата, похожего на результирующий набор операции соединения двух таблиц. Существует две формы инструкции APPLY:

Инструкция CROSS APPLY возвращает те строки из внутреннего (левого) табличного выражения, которые совпадают с внешним (правым) табличным выражением. Таким образом, логически, инструкция CROSS APPLY функционирует так же, как и инструкция INNER JOIN.

Инструкция OUTER APPLY возвращает все строки из внутреннего (левого) табличного выражения. (Для тех строк, для которых нет совпадений во внешнем табличном выражении, он содержит значения NULL в столбцах внешнего табличного выражения.) Логически, инструкция OUTER APPLY эквивалентна инструкции LEFT OUTER JOIN.

Применение инструкции APPLY показано в примерах ниже:

Функция GetJob() возвращает набор строк с таблицы Works_on. В примере ниже этот результирующий набор "соединяется" предложением APPLY с содержимым таблицы Employee:

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

В первом запросе примера результирующий набор табличной функции GetJob() "соединяется" с содержимым таблицы Employee посредством инструкции CROSS APPLY. Функция GetJob() играет роль правого ввода, а таблица Employee — левого. Выражение правого ввода вычисляется для каждой строки левого ввода, а полученные строки комбинируются, создавая конечный результат.

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

Возвращающие табличное значение параметры

Во всех версиях сервера, предшествующих SQL Server 2008, задача передачи подпрограмме множественных параметров была сопряжена со значительными сложностями. Для этого сначала нужно было создать временную таблицу, вставить в нее передаваемые значения, и только затем можно было вызывать подпрограмму. Начиная с версии SQL Server 2008, эта задача упрощена, благодаря возможности использования возвращающих табличное значение параметров, посредством которых результирующий набор может быть передан соответствующей подпрограмме.

Использование возвращающего табличное значение параметра показано в примере ниже:

В этом примере сначала определяется табличный тип departmentType. Это означает, что данный тип является типом данных TABLE, вследствие чего он разрешает вставку строк. В процедуре InsertProc объявляется переменная @Moscow с типом данных departmentType. (Предложение READONLY указывает, что содержимое этой таблицы нельзя изменять.) В последующем пакете в эту табличную переменную вставляются данные, после чего процедура запускается на выполнение. В процессе исполнения процедура вставляет строки из табличной переменной во временную таблицу #moscowTable. Вставленное содержимое временной таблицы выглядит следующим образом:

Использование возвращающих табличное значение параметров предоставляет следующие преимущества:

упрощается модель программирования подпрограмм;

уменьшается количество обращений к серверу и получений соответствующих ответов;

таблица результата может иметь произвольное количество строк.

Изменение структуры определяемых пользователями инструкций

Язык Transact-SQL также поддерживает инструкцию ALTER FUNCTION, которая модифицирует структуру определяемых пользователями инструкций (UDF). Эта инструкция обычно используется для удаления привязки функции к схеме. Все параметры инструкции ALTER FUNCTION имеют такое же значение, как и одноименные параметры инструкции CREATE FUNCTION.

Для удаления UDF применяется инструкция DROP FUNCTION. Удалить функцию может только ее владелец или член предопределенной роли db_owner или sysadmin.

Определяемые пользователем функции и среда CLR

В предыдущей статье мы рассмотрели способ создания хранимых процедур из управляемого кода среды CLR на языке C#. Этот подход можно использовать и для определяемых пользователем функций (UDF), с одним только различием, что для сохранения UDF в виде объекта базы данных используется инструкция CREATE FUNCTION, а не CREATE PROCEDURE. Кроме этого, определяемые пользователем функции также применяются в другом контексте, чем хранимые процедуры, поскольку UDF всегда возвращают значение.

В примере ниже показан исходный код определяемых пользователем функций (UDF), реализованный на языке C#:

В исходном коде определяемых пользователем функций в примере вычисляется новый бюджет проекта, увеличивая старый бюджет на определенное количество процентов. Вы можете использовать инструкцию CREATE ASSEMBLY для создания сборки CLR в базе данных, как это было показано ранее. Если вы прорабатывали примеры из предыдущей статьи и уже добавили сборку CLRStoredProcedures в базу данных, то вы можете обновить эту сборку, после ее перекомпиляции с новым классом (CLRStoredProcedures это имя моего проекта классов C#, в котором я добавлял определение хранимых процедур и функций, у вас сборка может называться иначе):

Инструкция CREATE FUNCTION в примере ниже сохраняет метод ComputeBudget в виде объекта базы данных, который в дальнейшем можно использовать в инструкциях для манипулирования данными.

Использование одной из таких инструкций, инструкции SELECT, показано в примере ниже:

Определяемую пользователем функцию можно поместить в разных местах инструкции SELECT. В примерах выше она вызывалась в предложениях WHERE, FROM и в списке выбора оператора SELECT.

ОБЛАСТЬ ПРИМЕНЕНИЯ: SQL Server База данных SQL Azure Azure Synapse Analytics (хранилище данных SQL) Parallel Data Warehouse APPLIES TO: SQL Server Azure SQL Database Azure Synapse Analytics (SQL DW) Parallel Data Warehouse

В этом разделе описывается создание определяемой пользователем функции в SQL Server SQL Server с помощью Transact-SQL Transact-SQL . This topic describes how to create a user-defined function (UDF) in SQL Server SQL Server by using Transact-SQL Transact-SQL .

Перед началом Before You Begin

Ограничения Limitations and restrictions

Определяемые пользователем функции не могут выполнять действия, изменяющие состояние базы данных. User-defined functions cannot be used to perform actions that modify the database state.

Определяемые пользователем функции не могут содержать предложение OUTPUT INTO , целью которого является таблица. User-defined functions cannot contain an OUTPUT INTO clause that has a table as its target.

Определяемые пользователем функции не могут возвращать несколько результирующих наборов. User-defined functions can not return multiple result sets. Используйте хранимую процедуру, если нужно возвращать несколько результирующих наборов. Use a stored procedure if you need to return multiple result sets.

Обработка ошибок в функциях, определяемых пользователем, ограниченна. Error handling is restricted in a user-defined function. UDF не поддерживает тип TRY. CATCH , @ERROR и RAISERROR . A UDF does not support TRY. CATCH , @ERROR or RAISERROR .

Определяемые пользователем функции не могут вызывать хранимую процедуру, но могут вызывать расширенную хранимую процедуру. User-defined functions cannot call a stored procedure, but can call an extended stored procedure.

Определяемые пользователем функции не могут использовать динамический SQL и временные таблицы. User-defined functions cannot make use of dynamic SQL or temp tables. Табличные переменные разрешены к использованию. Table variables are allowed.

Инструкцию SET нельзя использовать в определяемых пользователем функциях. SET statements are not allowed in a user-defined function.

Пустое предложение FOR XML запрещено. The FOR XML clause is not allowed.

Определяемые пользователем функции могут быть вложенными, то есть из одной функции может быть вызвана другая. User-defined functions can be nested; that is, one user-defined function can call another. Уровень вложенности увеличивается на единицу каждый раз, когда начинается выполнение вызванной функции и уменьшается на единицу, когда ее выполнение завершается. The nesting level is incremented when the called function starts execution, and decremented when the called function finishes execution. Вложенность определяемых пользователем функций не может превышать 32 уровней. User-defined functions can be nested up to 32 levels. Превышение максимального уровня вложенности приводит к ошибке выполнения для всей цепочки вызываемых функций. Exceeding the maximum levels of nesting causes the whole calling function chain to fail. Каждый вызов управляемого кода из определяемой пользователем функции Transact-SQL считается одним уровнем вложенности из 32 возможных. Any reference to managed code from a Transact-SQL user-defined function counts as one level against the 32-level nesting limit. Методы, вызываемые из управляемого кода, под это ограничение не подпадают. Methods invoked from within managed code do not count against this limit.

Следующие инструкции компонента Service Broker не могут быть включены в определение пользовательской функции Transact-SQL Transact-SQL : The following Service Broker statements cannot be included in the definition of a Transact-SQL Transact-SQL user-defined function:

BEGIN DIALOG CONVERSATION

GET CONVERSATION GROUP

Permissions Permissions

Требуется разрешение CREATE FUNCTION на базу данных и разрешение ALTER для схемы, в которой создается функция. Requires CREATE FUNCTION permission in the database and ALTER permission on the schema in which the function is being created. Если в функции указан определяемый пользователем тип, требуется разрешение EXECUTE на этот тип. If the function specifies a user-defined type, requires EXECUTE permission on the type.

Скалярные функции Scalar Functions

В следующем примере создается скалярная функция (скалярная UDF) из нескольких инструкций в базе данных AdventureWorks2012 AdventureWorks2012 . The following example creates a multi-statement scalar function (scalar UDF) in the AdventureWorks2012 AdventureWorks2012 database. Функция имеет один входной параметр ProductID и возвращает одно значение — количество указанного товара на складе. The function takes one input value, a ProductID , and returns a single data value, the aggregated quantity of the specified product in inventory.

В следующем примере функция ufnGetInventoryStock используется для получения сведений о количестве товаров с идентификаторами ProductModelID от 75 до 80. The following example uses the ufnGetInventoryStock function to return the current inventory quantity for products that have a ProductModelID between 75 and 80.

Дополнительные сведения см. в разделе CREATE FUNCTION (Transact-SQL). For more information and examples of scalar functions, see CREATE FUNCTION (Transact-SQL).

Функции с табличными значениями Table-Valued Functions

Результатом следующего примера является встроенная функция, возвращающая табличное значение (TVF) , в базе данных AdventureWorks2012 AdventureWorks2012 . The following example creates an inline table-valued function (TVF) in the AdventureWorks2012 AdventureWorks2012 database. Функция имеет один входной параметр — идентификатор клиента (магазина) — и возвращает столбцы ProductID , Name и столбец YTD Total со сведениями о продажах продукта за текущий год. The function takes one input parameter, a customer (store) ID, and returns the columns ProductID , Name , and the aggregate of year-to-date sales as YTD Total for each product sold to the store.

В следующем примере функция вызывается с идентификатором 602. The following example invokes the function and specifies customer ID 602.

Результатом следующего примера является многооператорная встроенная функция, возвращающая табличное значение (MSTVF) , в базе данных AdventureWorks2012 AdventureWorks2012 . The following example creates a multi-statement table-valued function (MSTVF) in the AdventureWorks2012 AdventureWorks2012 database. Функция имеет один входной параметр EmployeeID и возвращает список всех сотрудников, которые напрямую или косвенно отчитываются перед заданным сотрудником. The function takes a single input parameter, an EmployeeID and returns a list of all the employees who report to the specified employee directly or indirectly. Затем функция вызывается с указанием идентификатора сотрудника 109. The function is then invoked specifying employee ID 109.

В следующем примере функция вызывается с идентификатором сотрудника 1. The following example invokes the function and specifies employee ID 1.

Дополнительные сведения и примеры встроенных функций с табличными значениями (встроенные TVF) или многооператорных функций с табличными значениями (MSTVF) см. в разделе CREATE FUNCTION (Transact-SQL). For more information and examples of inline table-valued functions (inline TVFs) and multi-statement table-valued functions (MSTVFs), see CREATE FUNCTION (Transact-SQL).

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

Если определяемая пользователем функция (UDF) создана без применения предложения SCHEMABINDING , то изменения базовых объектов могут повлиять на определение функции и привести к непредвиденным результатам при вызове функции. If a user-defined function (UDF) is not created with the SCHEMABINDING clause, changes that are made to underlying objects can affect the definition of the function and produce unexpected results when it is invoked. Рекомендуется реализовать один из следующих методов, чтобы обеспечить, что функция не устареет из-за изменения ее базовых объектов. We recommend that you implement one of the following methods to ensure that the function does not become outdated because of changes to its underlying objects:

Укажите при создании функции UDF предложение WITH SCHEMABINDING . Specify the WITH SCHEMABINDING clause when you are creating the UDF. Это обеспечит невозможность изменения объектов, на которые ссылается определение функции, если при этом не изменяется сама функция. This ensures that the objects referenced in the function definition cannot be modified unless the function is also modified.

Выполняйте хранимую процедуру sp_refreshsqlmodule после изменения любого объекта, указанного в определении функции UDF. Execute the sp_refreshsqlmodule stored procedure after modifying any object that is specified in the definition of the UDF.

Если вы создаете определяемую пользователем функцию, не имеющую доступа к данным, укажите параметр SCHEMABINDING . If creating a UDF that does not access data, specify the SCHEMABINDING option. Это не позволит оптимизатору запросов создавать ненужные операторы очередей для планов запроса, содержащих такие определяемые пользователем функции. This will prevent the query optimizer from generating unnecessary spool operators for query plans involving these UDFs. Дополнительные сведения об очередях см. в справочнике по логическим и физическим операторам Showplan. For more information on spools, see Showplan Logical and Physical Operators Reference. Дополнительные сведения о создании функций, привязанных к схеме, см. в соответствующем разделе. For more information on creating a schema bound function, see Schema-bound functions.

Присоединение к MSTVF в предложении FROM возможно, но может привести к снижению производительности. Joining to an MSTVF in a FROM clause is possible, but can result in poor performance. SQL Server SQL Server не может использовать все оптимизированные методы для некоторых инструкций, которые можно включить в функцию MSTVF, и в результате план запроса оказывается неоптимальным. is unable to use all the optimized techniques on some statements that can be included in a MSTVF, resulting in a suboptimal query plan. Чтобы получить наилучшую производительность, по возможности задавайте соединения не между функциями, а между базовыми таблицами. To obtain the best possible performance, whenever possible use joins between base tables instead of functions.

Функции MSTVF имеют фиксированное предполагаемое значение кратности 100 начиная с SQL Server 2014 (12.x) SQL Server 2014 (12.x) и 1 в более ранних версиях SQL Server SQL Server . MSTVFs have a fixed cardinality guess of 100 starting with SQL Server 2014 (12.x) SQL Server 2014 (12.x) , and 1 for earlier SQL Server SQL Server versions.
Начиная с SQL Server 2017 (14.x) SQL Server 2017 (14.x) для оптимизации плана выполнения, который использует функции MSTVF, можно использовать выполнение с чередованием, что обеспечивает фактическую кратность вместо приведенной выше эвристики. Starting with SQL Server 2017 (14.x) SQL Server 2017 (14.x) , optimizing an execution plan that uses MSTVFs can leverage interleaved execution, which results in using actual cardinality instead of the above heuristics.
Дополнительные сведения см. в разделе Выполнение с чередованием для функций с табличным значением с несколькими инструкциями. For more information, see Interleaved execution for multi-statement table valued functions.

Параметры ANSI_WARNINGS не годятся для передачи в хранимые процедуры, пользовательские функции и при объявлении и установке переменных в пакетных инструкциях. ANSI_WARNINGS is not honored when you pass parameters in a stored procedure, user-defined function, or when you declare and set variables in a batch statement. Например, если объявить переменную как char(3) , а затем присвоить ей значение длиннее трех символов, данные будут усечены до размера переменной, а инструкция INSERT или UPDATE завершится без ошибок. For example, if a variable is defined as char(3), and then set to a value larger than three characters, the data is truncated to the defined size and the INSERT or UPDATE statement succeeds.

В этом разделе описывается создание определяемой пользователем функции в SQL Server с помощью Transact-SQL.

Ограничения

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

Определяемые пользователем функции не могут содержать предложение OUTPUT INTO, целью которого является таблица.

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

Обработка ошибок в функциях, определяемых пользователем, ограниченна. UDF не поддерживает инструкции TRY…CATCH, @ERROR и RAISERROR.

Определяемые пользователем функции не могут вызывать хранимую процедуру, но могут вызывать расширенную хранимую процедуру.

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

Инструкцию SET нельзя использовать в определяемых пользователем функциях.

Предложение FOR XML не допускается к использованию.

Определяемые пользователем функции могут быть вложенными, то есть из одной функции может быть вызвана другая. Уровень вложенности увеличивается на единицу каждый раз, когда начинается выполнение вызванной функции и уменьшается на единицу, когда ее выполнение завершается. Вложенность определяемых пользователем функций не может превышать 32 уровней. Превышение максимального уровня вложенности приводит к ошибке выполнения для всей цепочки вызываемых функций. Каждый вызов управляемого кода из определяемой пользователем функции Transact-SQL считается одним уровнем вложенности из 32 возможных. Методы, вызываемые из управляемого кода, под это ограничение не подпадают.

Следующие инструкции компонента Service Broker не могут быть включены в определение определяемой пользователем функции Transact-SQL:

BEGIN DIALOG CONVERSATION

GET CONVERSATION GROUP

Разрешения

Требуется разрешение CREATE FUNCTION на базу данных и разрешение ALTER на схему, в которой создается функция. Если в функции указан определяемый пользователем тип, требуется разрешение EXECUTE на этот тип.

В следующем примере создается скалярная функция из нескольких инструкций в базе данных AdventureWorks2012 . Функция имеет один входной параметр ProductID и возвращает одно значение — количество указанного товара на складе.

В следующем примере функция ufnGetInventoryStock используется для получения сведений о количестве товаров с идентификаторами ProductModelID от 75 до 80.

Результатом следующего примера является встроенная функция, создающая табличное значение в базе данных AdventureWorks2012 . Функция имеет один входной параметр — идентификатор клиента (магазина) — и возвращает столбцы ProductID , Name и столбец YTD Total со сведениями о продажах продукта за текущий год.

В следующем примере функция вызывается с идентификатором 602.

В следующем примере создается функция с табличным значением в базе данных AdventureWorks2012 . Функция имеет один входной параметр EmployeeID и возвращает список всех сотрудников, которые напрямую или косвенно отчитываются перед заданным сотрудником. Затем функция вызывается с указанием идентификатора сотрудника 109.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *