Создать новую функцию по коду, если она не существует

15

Я хочу создать новую функцию по сценарию в моей базе данных. Код скрипта ниже:

IF Exists(Select * From sys.sysobjects A Where A.name =N'fn_myfunc' and xtype=N'FN') return;

CREATE FUNCTION fn_myfunc ()
returns varchar(10)
AS Begin
...
End

Но когда я выполняю вышеуказанный скрипт, SQL Server возвращает ошибку:

'CREATE FUNCTION' must be the first statement in a query batch.
Мехди Лотфи
источник

Ответы:

16

Обновление от января 2017 г. - SQL Server 2016+ / База данных SQL Azure

SQL Server 2016 и текущая версия базы данных SQL Azure теперь имеют следующий синтаксис для функций, процедур, таблиц, баз данных и т. Д. ( DROP IF EXISTS):

DROP FUNCTION IF EXISTS dbo.fn_myfunc;

А SQL Server 2016 с пакетом обновления 1 (SP1) добавляет еще улучшенную функциональность для модулей (функций, процедур, триггеров, представлений), что означает отсутствие потери разрешений или зависимостей ( CREATE OR ALTER):

CREATE OR ALTER FUNCTION dbo.fn_myfunc ...

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

Но если вы используете ...


Старые версии

Вам нужно сделать то, что делает SQL Server, когда вы пишете сценарий из Management Studio:

IF NOT EXISTS (SELECT 1 FROM sys.objects WHERE type = 'FN' AND name = 'fn_myfunc')
BEGIN
    DECLARE @sql NVARCHAR(MAX);
    SET @sql = N'CREATE FUNCTION ...';
    EXEC sp_executesql @sql;
END

Или вы можете сказать:

BEGIN TRY
    DROP FUNCTION dbo.fn_myfunc;
END TRY
BEGIN CATCH
    PRINT 'Function did not exist.';
END CATCH
GO
CREATE FUNCTION...

Или вы можете просто сказать:

DROP FUNCTION dbo.fn_myfunc;
GO
CREATE FUNCTION...

(Здесь вы получите сообщение об ошибке, если функция еще не существует, но сценарий будет продолжен со следующего GO, так что независимо от того, сработало удаление или нет, функция все равно будет (повторно) создана.)

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

Аарон Бертран
источник
1
так что нет ключевого слова $$ СОЗДАТЬ ИЛИ ЗАМЕНИТЬ $$? Жаль.
zinking
@ Зинкинга нет, но возможно в будущей версии. Пожалуйста, голосуйте / комментируйте: connect.microsoft.com/SQLServer/feedback/details/127219/…
Аарон Бертран
1
@AaronBertrand Хорошая идея проголосовать за это, но ссылка не работает
голубоватая
@ синий да, извините, они удалили элемент где-то три года назад, когда я опубликовал ссылку, и теперь ... Попробуйте connect.microsoft.com/SQLServer/feedback/details/344991/… или connect.microsoft.com/SQLServer/feedback / подробнее / 351217 /…
Аарон Бертран
Это может быть доступно сейчас: blogs.msdn.microsoft.com/sqlserverstorageengine/2016/11/17/…
Бруно
1

Ошибка довольно очевидна. Есть несколько способов исправить это.

  1. Разделяйте сценарий на разные пакеты в Management Studio, используя GOпсевдоключевое слово и DROP/ CREATEobject. (Обратите внимание, что само ключевое слово можно изменить в параметрах Management Studio, но это де-факто параметр, поэтому я предлагаю оставить его в покое).

    Когда вы запускаете сценарий (или выбранную часть сценария), Management Studio разделяет каждый фрагмент сценария между GOs и последовательно отправляет части в SQL Server в виде отдельных пакетов.

  2. Используйте динамический SQL для отправки отдельного пакета из другого пакета.

    Это предпочтительный метод, потому что тогда ваш скрипт не зависит от внешней функциональности для правильного выполнения. Например, если ваше приложение имеет программу обновления базы данных, то, вообще говоря, оно загрузит файл сценария и затем выполнит его на целевом сервере. Либо вам придется добавить логику для разделения пакетов, как это делает Management Studio (примечание: чревато опасностью), либо написать сценарий таким образом, чтобы весь сценарий мог быть успешно выполнен в виде одного пакета.

    Как уже упоминалось в другом ответе, вы можете выполнить тест / CREATEс помощью этого метода (или какой-либо другой комбинации DROP/ CREATEи т. Д.). Что я предпочитаю делать, так это создавать объект-заглушку, если объект не существует, а затем использовать его ALTER <object>для создания или изменения. Этот подход не удаляет зависимости, такие как разрешения или расширенные свойства, и нет необходимости копировать / вставлять подверженную ошибкам логику, чтобы выполнить CREATE/ ALTERв одном выражении.

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

IF NOT EXISTS(SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[<schema>].[<function name>]') AND type IN ('FN', 'FS'))
    EXEC sp_executesql N'CREATE FUNCTION [<schema name>].[<function name>] (@a int) RETURNS int AS BEGIN /* Stub */ RETURN @a END'

EXEC sp_executesql N'
ALTER FUNCTION [<schema name>].[<function name>]
/* ... */
'
Джон Сайгель
источник
Как вариант 1 может работать здесь? Добавление GOфактически гарантирует, что скрипт сломается, так как IFон ни к чему не приведет.
Аарон Бертран
@ Аарон: Я думал о DROP/ CREATEсценарий - отредактировано. Благодарю.
Джон Зигель
1

У вас есть возможность проверить, существует ли объект в databaseи создать, если нет:

IF OBJECT_ID('new_function', 'FN') IS NULL
BEGIN
  EXEC('CREATE FUNCTION new_function() RETURNS INT AS BEGIN RETURN 1 END');
END;
go

ALTER FUNCTION new_function() RETURNS INT AS
BEGIN

...
Sorack
источник