TL;DR
Here is the op's use-case (dbo.sp_insertoralter
procedure is given as a factory just after Below):
EXEC dbo.sp_insertoralter '[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', 0, 'IsScalarFunction'
print [dbo].[FMT_PHONE_NBR] ('987654132456') -- 987-654-1324
Warning: Please do keep in mind that it's not intended to be a general-purpose factory, as user rights may not apply same everywhere, and there are tons of possible sql injections.
Original answer
Starting from @PhilipKelley's answer, if you wish to use a schema other than 'dbo', see Below. No temp var are required to make EXEC('sql code')
instructions working by themselves.
For the sake of the @obj_lookup
reuse, you may uncomment the last EXEC('PRINT...')
part.
Important note: Everything that is stringified will not have spellcheck enabled. It's up to you to be sure that names (schema and function) are accurate, and any db rework might break names/schemas without spellcheck notice.
I strongly recommend to read the full @PhilipKelley's answer, especially for the "Don't ask me to support source code that might contain more than 4 single apostrophes in a row!" part.
Reminder: This is not a concurrent-safe operation, as parallel executions might fail not being able to create the same object (concurrently in creation), and/or will result in an unknown object's altered body (presumably the last one to succeed). As are all the other "drop-then-create" answers, but the warn might not harm.
Below:
-- IsProcedure
-- IsScalarFunction (Returns single value)
-- IsTableFunction (Declared return table structure, multiple statements)
-- IsInlineFunction (Based on single select statement)
-- IsView
-- (Full list available at https://learn.microsoft.com/fr-fr/sql/t-sql/functions/objectproperty-transact-sql?view=sql-server-ver15 )
DECLARE @obj_lookup nvarchar (max) = 'abc.xxx'
DECLARE @obj_alter nvarchar (max) = '() -- Object parameters come here
RETURNS INT AS BEGIN -- Actual body goes here
RETURN 0
END'
DECLARE @obj_oralter bit = 0
DECLARE @obj_type nvarchar(100) = 'IsScalarFunction'
DECLARE @obj_application nvarchar(100) = 'FUNCTION'
IF objectproperty(object_id(@obj_lookup), @obj_type) is null
-- Here is the "create if not exists" behaviour
BEGIN
EXEC('CREATE FUNCTION ' + @obj_lookup + @obj_alter)
-- Configure access rights
EXEC('GRANT EXECUTE on ' + @obj_lookup + ' TO StoredProcedureUser')
END
ELSE IF @obj_oralter = 1 -- Here is the "or alter" behaviour
EXEC('ALTER ' + @obj_application + ' ' + @obj_lookup + @obj_alter) -- Untouched access rights
/* -- Alternatively, you may prefer this 'CREATE OR ALTER' instruction to avoid the above objectproperty [if/else if] block:
EXEC('CREATE OR ALTER ' + @obj_application + ' ' + @obj_lookup + @obj_alter)
*/
GO
-- Actual code (considering the object now exists or was altered)
PRINT abc.xxx()
(..etc)
/* -- For the "@obj_lookup" reuse:
EXEC('PRINT ' + @obj_lookup + '()
(..etc)')
*/
GO
Factory
That can be wrapped in a procedure (yay):
CREATE PROCEDURE dbo.sp_insertoralter(@obj_lookup as nvarchar(max), @obj_alter as nvarchar(max), @obj_oralter as bit = 0, @obj_type as nvarchar(100))
AS BEGIN
-- Type preparation
declare @obj_application nvarchar(100) = case
when @obj_type = 'IsProcedure' then 'PROCEDURE'
when @obj_type = 'IsScalarFunction' or @obj_type = 'IsTableFunction' or @obj_type = 'IsInlineFunction' then 'FUNCTION'
when @obj_type = 'IsView' then 'VIEW'
when @obj_type = 'IsTable' or @obj_type = 'IsUserTable' then 'TABLE'
when @obj_type = 'IsTrigger' then 'TRIGGER'
else null -- Restriction to known (usefull) cases
end
if @obj_application is null
begin
raiserror ('An invalid @obj_type was specified for procedure', 10, 1); --throw 51000, 'An invalid @obj_type was specified for procedure', 1;
return
end
IF objectproperty(object_id(@obj_lookup), @obj_type) is null
BEGIN -- Here is the "create if not exists" behaviour
EXEC('CREATE ' + @obj_application + ' ' + @obj_lookup + @obj_alter)
-- Configure access rights
EXEC('GRANT EXECUTE on ' + @obj_lookup + ' TO StoredProcedureUser')
END
ELSE IF @obj_oralter = 1 -- Here is the "or alter" behaviour
EXEC('ALTER ' + @obj_application + ' ' + @obj_lookup + @obj_alter) -- Untouched access rights
/* -- Alternatively, you may prefer the 'CREATE OR ALTER' instruction to avoid the above objectproperty [if/else if] block:
EXEC('CREATE OR ALTER ' + @obj_application + ' ' + @obj_lookup + @obj_alter)
*/
END
GO
As you might have noticed, that procedure can then be self-invoked (but has to exist to be invoked), so here is an auto-use-case (double yay):
-- The "dbo.sp_insertoralter" procedure has to exist (or whatever name you gave it), let's pretend someone manually executed the `Below:` part with these parameters:
EXEC dbo.sp_insertoralter 'dbo.sp_insertoralter', '(@obj_lookup as nvarchar(max), @obj_alter as nvarchar(max), @obj_oralter as bit = 0, @obj_type as nvarchar(100))
AS BEGIN
-- Type preparation
declare @obj_application nvarchar(100) = case
when @obj_type = ''IsProcedure'' then ''PROCEDURE''
when @obj_type = ''IsScalarFunction'' or @obj_type = ''IsTableFunction'' or @obj_type = ''IsInlineFunction'' then ''FUNCTION''
when @obj_type = ''IsView'' then ''VIEW''
when @obj_type = ''IsTable'' or @obj_type = ''IsUserTable'' then ''TABLE''
when @obj_type = ''IsTrigger'' then ''TRIGGER''
else null -- Restriction to known (usefull) cases
end
if @obj_application is null
begin
raiserror (''An invalid @obj_type was specified for procedure'', 10, 1); --throw 51000, ''An invalid @obj_type was specified for procedure'', 1;
return
end
IF objectproperty(object_id(@obj_lookup), @obj_type) is null
BEGIN -- Here is the "create if not exists" behaviour
EXEC(''CREATE '' + @obj_application + '' '' + @obj_lookup + @obj_alter)
-- Configure access rights
EXEC(''GRANT EXECUTE on '' + @obj_lookup + '' TO StoredProcedureUser'')
END
ELSE IF @obj_oralter = 1 -- Here is the "or alter" behaviour
EXEC(''ALTER '' + @obj_application + '' '' + @obj_lookup + @obj_alter) -- Untouched access rights
/* -- Alternatively, you may prefer the ''CREATE OR ALTER'' instruction to avoid the above objectproperty [if/else if] block:
EXEC(''CREATE OR ALTER '' + @obj_application + '' '' + @obj_lookup + @obj_alter)
*/
END', 1, 'IsProcedure'
And now, here is the op's use-case (triple yay):
EXEC dbo.sp_insertoralter '[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', 0, 'IsScalarFunction'
print [dbo].[FMT_PHONE_NBR] ('987654132456') -- 987-654-1324
Warning: Please do keep in mind that it's not intended to be a general-purpose factory, as user rights may not apply same everywhere, and there are tons of possible sql injections.