Создание UDF (функция определения пользователя), если она не существует, и пропускать ее, если она существует
Привет и спасибо за это.
Я пытаюсь использовать инструкцию IF EXISTS/IF NOT EXISTS, чтобы проверить, существует ли объект. В принципе, я хочу пропустить его, если он есть, или создать его, если его там нет.
Я пишу код двумя разными способами, но получаю сообщение об ошибке: функция Create должна быть единственной функцией в пакете. Если я помещаю GO между приведенными ниже инструкциями, я получаю другое предупреждение: Неправильный синтаксис рядом с GO.
Где я здесь не так?
IF NOT EXISTS
(select * from Information_schema.Routines where SPECIFIC_SCHEMA='dbo'
AND SPECIFIC_NAME = 'FMT_PHONE_NBR' AND Routine_Type='FUNCTION')
/*CREATE FUNCTION TO FORMAT PHONE NUMBERS*/
CREATE FUNCTION [dbo].[FMT_PHONE_NBR](@phoneNumber VARCHAR(12))
RETURNS VARCHAR(12)
AS
BEGIN
RETURN SUBSTRING(@phoneNumber, 1, 3) + '-' +
SUBSTRING(@phoneNumber, 4, 3) + '-' +
SUBSTRING(@phoneNumber, 7, 4)
END
GO
Или это:
IF NOT EXISTS
(SELECT name FROM sys.objects WHERE name = 'dbo.FMT_PHONE_NBR')
GO
/*CREATE FUNCTION TO FORMAT PHONE NUMBERS*/
CREATE FUNCTION [dbo].[FMT_PHONE_NBR](@phoneNumber VARCHAR(12))
RETURNS VARCHAR(12)
AS
BEGIN
RETURN SUBSTRING(@phoneNumber, 1, 3) + '-' +
SUBSTRING(@phoneNumber, 4, 3) + '-' +
SUBSTRING(@phoneNumber, 7, 4)
END
GO
Спасибо, что проверили это!
Ответы
Ответ 1
Самый простой способ решить это - фактически удалить функцию, если она уже существует, а затем повторно создать ее:
/* If we already exist, get rid of us, and fix our spelling */
IF OBJECT_ID('dbo.FMT_PHONE_NBR') IS NOT NULL
DROP FUNCTION FMT_PHONE_NBR
GO
/*CREATE FUNCTION TO FORMAT PHONE NUMBERS*/
CREATE FUNCTION [dbo].[FMT_PHONE_NBR](@phoneNumber VARCHAR(12))
RETURNS VARCHAR(12)
AS
BEGIN
RETURN SUBSTRING(@phoneNumber, 1, 3) + '-' +
SUBSTRING(@phoneNumber, 4, 3) + '-' +
SUBSTRING(@phoneNumber, 7, 4)
END
GO
Обратите внимание на использование функции "object_id" в приведенном выше. На самом деле это довольно распространенный способ проверить существование объекта, хотя он подвержен определенным ограничениям.
Подробнее об этом можно прочитать здесь: OBJECT_ID
Ответ 2
Как я долго бил головой о эту кирпичную стену, я подниму еще два цента.
Как уже было сказано, да, было бы неплохо добавить его, только если его еще нет, но это невозможно в T-SQL без использования динамического SQL... и обертывания ваших функций, процедур, триггеров, взгляды и, возможно, еще более неясные объекты, поскольку динамические заявления просто слишком непрактичны. (Не просите меня поддерживать исходный код, который может содержать более 4 отдельных апострофов подряд!)
Отбрасывание (если оно существует) и (повторное) создание является жизнеспособным решением. Предположительно, если вы создаете новый код, вы бы захотели создать объект, если он еще не был там, и в противном случае отказаться от существующего/старого кода и заменить его новым. (Если вы случайно замените "новый" код "старым" кодом, у вас есть проблема с управлением версиями, которая является другой и более сложной темой.)
Реальная проблема - потеря информации при отбрасывании старого кода. Какая информация? Тот, который я часто нажимаю, - это права доступа: у кого есть EXECUTE
или, для некоторых функций, SELECT
права на объект? Бросьте и замените, и они исчезли. Ответ на этот вопрос, конечно же, заключается в script правах доступа как части развертывания script. Однако, если у вас есть ситуация, когда разные среды размещения базы данных имеют разные конфигурации (логины, домены, группы и т.д. И т.д.), Вы можете оказаться в ситуации, когда вы не сможете и не можете знать, какие существующие права доступа на данном экземпляре, поэтому, если вы просто снимете и заново создадите его, существующие пользователи больше не смогут получить к нему доступ. (Расширенные свойства и другие биты эзотерики будут одинаково затронуты.)
Первое и лучшее исправление для этого - обеспечить надежную защиту. Настройте роли базы данных, назначьте/сопоставьте соответствующие разрешения для ролей, тогда вам не нужно будет знать, кто в ролях - это будет работа администраторов среды. (Вы все равно должны иметь что-то вроде GRANT EXECUTE on ThisProc to dbo.xxx
в конце вашего script, но это не так сложно.
Если, как и я, вы (а) не наделены полномочиями для развертывания хорошей и надежной модели безопасности и (б) ленивы и, вероятно, не должны проверять конец сохраненных в сотнях строк строк файл процедуры для кода прав доступа, вы можете сделать что-то вроде следующего. (Это задано для хранимых процедур, но подходит для функций и других объектов.)
-- isProcedure
-- IsScalarFunction (Returns single value)
-- IsTableFunction (Declared return table structure, multiple statements)
-- IsInlineFunction (Based on single select statement)
-- IsView
IF objectproperty(object_id('dbo.xxx'), 'isProcedure') is null
BEGIN
-- Procedure (or function) does not exist, create a dummy placeholder
DECLARE @Placeholder varchar(100)
SET @Placeholder = 'CREATE PROCEDURE dbo.xxx AS RETURN 0'
EXEC(@PlaceHolder)
-- Configure access rights
GRANT EXECUTE on dbo.xxx TO StoredProcedureUser
END
GO
ALTER PROCEDURE dbo.xxx
(etc.)
GO
Это будет:
- Сначала проверьте, существует ли процедура. Если это не так, создайте "placholder" и установите для него соответствующие права доступа.
- Затем, независимо от того, существовал ли он до запуска script,
ALTER
и установите его с нужным кодом.
Также существует проблема управления объектами на основе кода (в основном хранимыми процедурами) в схемах, где схемы могут отсутствовать. Я еще не понял, что один из них, и если вам повезет, вы никогда не столкнетесь с подобной ситуацией.
Ответ 3
Сообщение об ошибке в точности верно, что операторы CREATE FUNCTION должны быть первыми в пакете, что означает, что, к сожалению, вы не можете сделать:
IF [condition]
BEGIN
CREATE FUNCTION
...
END
GO
Я обычно делаю в этой ситуации:
IF object_id('dbo.myFunction') IS NOT NULL
BEGIN
DROP FUNCTION dbo.myFunction
END
GO
CREATE FUNCTION dbo.myFunction (
...
)
GO
Обратите внимание, что я обычно использую функцию object_id(), поскольку она проще, легче читать и более надежна, чем EXISTS (SELECT * FROM sys.whatever).
Конечно, это решение будет работать только для вас, если вы в порядке, всегда переписывая любое предыдущее определение функции. Если это не так, сообщите мне.
Ответ 4
На самом деле это работает в 2008 году
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[fn_GetTZDate]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT'))
BEGIN
execute dbo.sp_executesql @statement = N'
CREATE FUNCTION [dbo].[fn_GetTZDate] ()
RETURNS datetime
AS -- WITH ENCRYPTION AS
BEGIN
-- Declare the return variable here
DECLARE @tzadj int, @sysdate datetime
SET @sysdate = getdate()
SET @tzadj = 0
SELECT @tzadj = [tzAdjustment] FROM USysSecurity WHERE [WindowsUserName] = SYSTEM_USER
if @tzadj <> 0
BEGIN
SET @sysdate = dateadd(hh, @tzadj, @sysdate)
END
-- Return the result of the function
RETURN @sysdate
END '
END
GO