I looking for a way to call REMOTE FUNCTION in SELECT clause in VIEW.
Basically I'am trying to do somthing like this:
CREATE VIEW [dbo].[View_Year_Forcast] AS
(SELECT BshForecast.ProjectId,
BshForecast.JobTypeId,
Job_Type.NAME,
ProjectName ,
ProjectManagerID,
ProjectManagerName ,
***EXEC #START_DATE =[192.168.0.10].[LudanProjectManager].[dbo].[func_Project_Get_Start_Date] #PROJECT_ID as [Start_Date],***
StartBudget ,
AdditionalBudget ,
TotalBudget ,
UsedBudget ,
FROM BshForecast INNER JOIN Job_Type ON BshForecast.JobTypeId = ID
WHERE (dbo.BshForecast.Year = DATEPART(YYYY,GETDATE()))
AND (dbo.BshForecast.IsDeleted = 0)
AND (dbo.BshForecast.BranchId = 200)
AND (dbo.BshForecast.Approved = 1) );
And what I'am trying to get is a view that the seven'th column will hold the start date of each project that will be evaluate from a function in remote server.
The only way I know of calling a remote function is openquery. But openquery only takes a string literal, so you have to wrap the call to openquery in exec.
Here's an example that creates a function and calls it through the linked server called "localhost".
use TestDatabase
if exists (select * from sys.objects where name = 'fn_twice')
drop function fn_twice
go
create function dbo.fn_twice(#i int) returns int as begin return 2*#i end
go
declare #i int
set #i = 21
declare #func_sql nvarchar(max)
set #func_sql = 'select #result = a.result from openquery(localhost, ' +
'''select TestDatabase.dbo.fn_twice(' +
cast(#i as varchar(12)) + ') as result'') a'
declare #result int
exec sp_executesql #func_sql, N'#result int output', #result output
-- The result of the function call is now available in #result, and
-- you can use it in a query.
Related
I am using ##rowcount in my functions like this:
ALTER FUNCTION [dbo].[GetUserNameFamily]
(#UsrID INT)
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE #Name NVARCHAR(MAX)
DECLARE #Family NVARCHAR (MAX)
DECLARE #cou INT
SELECT #Name = ut.Fname, #Family = ut.Lname
FROM User_tbl ut
WHERE ut.UserID = #UsrID
IF ##ROWCOUNT = 0
RETURN 'row 0'
IF #Name IS NULL
SET #Name = ''
IF #Family IS NULL
SET #Family = ''
RETURN #Name + ' ' + #Family
END
When I use this function in a query like that:
declare #ID int=3118
select *
from Files_tbl
where RefID = #ID -- query rows affected is 0
select
dbo.GetUserNameFamily(TicketResponse_tbl.CreateByUserID) as CreateByFullName
from
TicketResponse_tbl
where
TicketResponse_tbl.TicketID = #ID
My result is:
After removing where in "select Files_tbl" query and changed this query rows affected from 0 to n.
declare #ID int = 3118
select *
from Files_tbl
-- where RefID = #ID -- query rows affected is not 0
select
dbo.GetUserNameFamily(TicketResponse_tbl.CreateByUserID) as CreateByFullName
from
TicketResponse_tbl
where
TicketResponse_tbl.TicketID = #ID
My function result changes to :
This problem occurred after upgrading the database compatibility level to SQL Server 2019
As mentioned by others, there was a bug in the new (2019) feature called Scalar UDF Inlining that involved side-affecting functions such as ##ROWCOUNT. Updating to the latest build of SQL Server (which you should do anyway) would have fixed this.
Be that as it may, to continue using Inlining you can avoid ##ROWCOUNT by simplifying your function like this
CREATE OR ALTER FUNCTION [dbo].[GetUserNameFamily]
(#UsrID INT)
RETURNS NVARCHAR(MAX)
AS
BEGIN
RETURN ISNULL((
SELECT CONCAT(ut.Fname, ' ', ut.Lname)
FROM User_tbl ut
WHERE ut.UserID = #UsrID
), 'row 0');
END
But I would advise you to just transform this into an inline Table Valued Function, which will always be inlined:
CREATE OR ALTER FUNCTION [dbo].[GetUserNameFamily]
(#UsrID INT)
RETURNS TABLE
AS RETURN
SELECT
ISNULL((
SELECT CONCAT(ut.Fname, ' ', ut.Lname)
FROM User_tbl ut
WHERE ut.UserID = #UsrID
), 'row 0') AS UserName;
You use it like this
SELECT n.UserName
FROM YourTable t
CROSS APPLY dbo.GetUserNameFamily(t.Id) n;
In my stored procedure I declared two table variables on top of my procedure. Now I am trying to use that table variable within a dynamic sql statement but I get this error at the time of execution of that procedure. I am using Sql Server 2008.
This is how my query looks like,
set #col_name = 'Assoc_Item_'
+ Convert(nvarchar(2), #curr_row1);
set #sqlstat = 'update #RelPro set '
+ #col_name
+ ' = (Select relsku From #TSku Where tid = '
+ Convert(nvarchar(2), #curr_row1) + ') Where RowID = '
+ Convert(nvarchar(2), #curr_row);
Exec(#sqlstat);
And I get the following errors,
Must declare the table variable "#RelPro".
Must declare the table variable "#TSku".
I have tried to take the table outside of the string block of dynamic query but to no avail.
On SQL Server 2008+ it is possible to use Table Valued Parameters to pass in a table variable to a dynamic SQL statement as long as you don't need to update the values in the table itself.
So from the code you posted you could use this approach for #TSku but not for #RelPro
Example syntax below.
CREATE TYPE MyTable AS TABLE
(
Foo int,
Bar int
);
GO
DECLARE #T AS MyTable;
INSERT INTO #T VALUES (1,2), (2,3)
SELECT *,
sys.fn_PhysLocFormatter(%%physloc%%) AS [physloc]
FROM #T
EXEC sp_executesql
N'SELECT *,
sys.fn_PhysLocFormatter(%%physloc%%) AS [physloc]
FROM #T',
N'#T MyTable READONLY',
#T=#T
The physloc column is included just to demonstrate that the table variable referenced in the child scope is definitely the same one as the outer scope rather than a copy.
Your EXEC executes in a different context, therefore it is not aware of any variables that have been declared in your original context. You should be able to use a temp table instead of a table variable as shown in the simple demo below.
create table #t (id int)
declare #value nchar(1)
set #value = N'1'
declare #sql nvarchar(max)
set #sql = N'insert into #t (id) values (' + #value + N')'
exec (#sql)
select * from #t
drop table #t
You don't have to use dynamic SQL
update
R
set
Assoc_Item_1 = CASE WHEN #curr_row = 1 THEN foo.relsku ELSE Assoc_Item_1 END,
Assoc_Item_2 = CASE WHEN #curr_row = 2 THEN foo.relsku ELSE Assoc_Item_2 END,
Assoc_Item_3 = CASE WHEN #curr_row = 3 THEN foo.relsku ELSE Assoc_Item_3 END,
Assoc_Item_4 = CASE WHEN #curr_row = 4 THEN foo.relsku ELSE Assoc_Item_4 END,
Assoc_Item_5 = CASE WHEN #curr_row = 5 THEN foo.relsku ELSE Assoc_Item_5 END,
...
from
(Select relsku From #TSku Where tid = #curr_row1) foo
CROSS JOIN
#RelPro R
Where
R.RowID = #curr_row;
You can't do this because the table variables are out of scope.
You would have to declare the table variable inside the dynamic SQL statement or create temporary tables.
I would suggest you read this excellent article on dynamic SQL.
http://www.sommarskog.se/dynamic_sql.html
Well, I figured out the way and thought to share with the people out there who might run into the same problem.
Let me start with the problem I had been facing,
I had been trying to execute a Dynamic Sql Statement that used two temporary tables I declared at the top of my stored procedure, but because that dynamic sql statment created a new scope, I couldn't use the temporary tables.
Solution:
I simply changed them to Global Temporary Variables and they worked.
Find my stored procedure underneath.
CREATE PROCEDURE RAFCustom_Room_GetRelatedProducts
-- Add the parameters for the stored procedure here
#PRODUCT_SKU nvarchar(15) = Null
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
IF OBJECT_ID('tempdb..##RelPro', 'U') IS NOT NULL
BEGIN
DROP TABLE ##RelPro
END
Create Table ##RelPro
(
RowID int identity(1,1),
ID int,
Item_Name nvarchar(max),
SKU nvarchar(max),
Vendor nvarchar(max),
Product_Img_180 nvarchar(max),
rpGroup int,
Assoc_Item_1 nvarchar(max),
Assoc_Item_2 nvarchar(max),
Assoc_Item_3 nvarchar(max),
Assoc_Item_4 nvarchar(max),
Assoc_Item_5 nvarchar(max),
Assoc_Item_6 nvarchar(max),
Assoc_Item_7 nvarchar(max),
Assoc_Item_8 nvarchar(max),
Assoc_Item_9 nvarchar(max),
Assoc_Item_10 nvarchar(max)
);
Begin
Insert ##RelPro(ID, Item_Name, SKU, Vendor, Product_Img_180, rpGroup)
Select distinct zp.ProductID, zp.Name, zp.SKU,
(Select m.Name From ZNodeManufacturer m(nolock) Where m.ManufacturerID = zp.ManufacturerID),
'http://s0001.server.com/is/sw11/DG/' +
(Select m.Custom1 From ZNodeManufacturer m(nolock) Where m.ManufacturerID = zp.ManufacturerID) +
'_' + zp.SKU + '_3?$SC_3243$', ep.RoomID
From Product zp(nolock) Inner Join RF_ExtendedProduct ep(nolock) On ep.ProductID = zp.ProductID
Where zp.ActiveInd = 1 And SUBSTRING(zp.SKU, 1, 2) <> 'GC' AND zp.Name <> 'PLATINUM' AND zp.SKU = (Case When #PRODUCT_SKU Is Not Null Then #PRODUCT_SKU Else zp.SKU End)
End
declare #curr_row int = 0,
#tot_rows int= 0,
#sku nvarchar(15) = null;
IF OBJECT_ID('tempdb..##TSku', 'U') IS NOT NULL
BEGIN
DROP TABLE ##TSku
END
Create Table ##TSku (tid int identity(1,1), relsku nvarchar(15));
Select #curr_row = (Select MIN(RowId) From ##RelPro);
Select #tot_rows = (Select MAX(RowId) From ##RelPro);
while #curr_row <= #tot_rows
Begin
select #sku = SKU from ##RelPro where RowID = #curr_row;
truncate table ##TSku;
Insert ##TSku(relsku)
Select distinct top(10) tzp.SKU From Product tzp(nolock) INNER JOIN
[INTRANET].raf_FocusAssociatedItem assoc(nolock) ON assoc.associatedItemID = tzp.SKU
Where (assoc.isActive=1) And (tzp.ActiveInd = 1) AND (assoc.productID = #sku)
declare #curr_row1 int = (Select Min(tid) From ##TSku),
#tot_rows1 int = (Select Max(tid) From ##TSku);
If(#tot_rows1 <> 0)
Begin
While #curr_row1 <= #tot_rows1
Begin
declare #col_name nvarchar(15) = null,
#sqlstat nvarchar(500) = null;
set #col_name = 'Assoc_Item_' + Convert(nvarchar(2), #curr_row1);
set #sqlstat = 'update ##RelPro set ' + #col_name + ' = (Select relsku From ##TSku Where tid = ' + Convert(nvarchar(2), #curr_row1) + ') Where RowID = ' + Convert(nvarchar(2), #curr_row);
Exec(#sqlstat);
set #curr_row1 = #curr_row1 + 1;
End
End
set #curr_row = #curr_row + 1;
End
Select * From ##RelPro;
END
GO
I don't think that is possible (though refer to the update below); as far as I know a table variable only exists within the scope that declared it. You can, however, use a temp table (use the create table syntax and prefix your table name with the # symbol), and that will be accessible within both the scope that creates it and the scope of your dynamic statement.
UPDATE: Refer to Martin Smith's answer for how to use a table-valued parameter to pass a table variable in to a dynamic SQL statement. Also note the limitation mentioned: table-valued parameters are read-only.
Here is an example of using a dynamic T-SQL query and then extracting the results should you have more than one column of returned values (notice the dynamic table name):
DECLARE
#strSQLMain nvarchar(1000),
#recAPD_number_key char(10),
#Census_sub_code varchar(1),
#recAPD_field_name char(100),
#recAPD_table_name char(100),
#NUMBER_KEY varchar(10),
if object_id('[Permits].[dbo].[myTempAPD_Txt]') is not null
DROP TABLE [Permits].[dbo].[myTempAPD_Txt]
CREATE TABLE [Permits].[dbo].[myTempAPD_Txt]
(
[MyCol1] char(10) NULL,
[MyCol2] char(1) NULL,
)
-- an example of what #strSQLMain is : #strSQLMain = SELECT #recAPD_number_key = [NUMBER_KEY], #Census_sub_code=TEXT_029 FROM APD_TXT0 WHERE Number_Key = '01-7212'
SET #strSQLMain = ('INSERT INTO myTempAPD_Txt SELECT [NUMBER_KEY], '+ rtrim(#recAPD_field_name) +' FROM '+ rtrim(#recAPD_table_name) + ' WHERE Number_Key = '''+ rtrim(#Number_Key) +'''')
EXEC (#strSQLMain)
SELECT #recAPD_number_key = MyCol1, #Census_sub_code = MyCol2 from [Permits].[dbo].[myTempAPD_Txt]
DROP TABLE [Permits].[dbo].[myTempAPD_Txt]
Using Temp table solves the problem but I ran into issues using Exec so I went with the following solution of using sp_executesql:
Create TABLE #tempJoin ( Old_ID int, New_ID int);
declare #table_name varchar(128);
declare #strSQL nvarchar(3072);
set #table_name = 'Object';
--build sql sting to execute
set #strSQL='INSERT INTO '+#table_name+' SELECT '+#columns+' FROM #tempJoin CJ
Inner Join '+#table_name+' sourceTbl On CJ.Old_ID = sourceTbl.Object_ID'
**exec sp_executesql #strSQL;**
I have a linked server which contains data. Calling each table separately takes too long so the only choice is to use OpenQuery. The wrinkle being that I need to pass a parameter to the query.
OpenQuery does not support parameters meaning it has to be done via Exec and a string built up with the full command in it. I also need to client application written in C#.
I could build the string in C# and pass that but I am trying to make the application agnostic when it comes to the data source so want to avoid having SQL embedded in the application.
Code to the stored procedure is
ALTER PROCEDURE [dbo].[Get_Patient_By_MRN] ( #MRN VarChar(10) )
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
Declare #Patient_Sql VarChar(768) =
'
SELECT * FROM OPENQUERY([CERNER_APP_DB_PROD],''
Select P.PERSON_ID
, PA1.ALIAS AS MRN
, PA2.ALIAS AS NHS_Number
, P.NAME_LAST_KEY
, P.NAME_FIRST_KEY
, P.NAME_FULL_FORMATTED
, P.BIRTH_DT_TM
From V500.PERSON P LEFT JOIN V500.PERSON_ALIAS PA1
ON PA1.PERSON_ID = P.PERSON_ID AND
PA1.PERSON_ALIAS_TYPE_CD = 10
LEFT JOIN V500.PERSON_ALIAS PA2
ON PA2.PERSON_ID = P.PERSON_ID AND
PA2.PERSON_ALIAS_TYPE_CD = 18
Where PA1.ALIAS = ''''' + #MRN + '''''
'')
'
Return Exec #Patient_Sql
END
You were close with your SQL. You don't RETURN a dataset with a Stored procedure. RETURN is normally used to return the success result of an SP; 0 means succes, anything else means it didn't (this is how the Microsoft SP's work). What you need to do here is simply execute your dynamic statement.
I've changed the SQL a little bit, and stopped injection (although very hard with a varchar(10)) by using QUOTENAME, and changed the datatype to be correct for sp_executesql:
ALTER PROCEDURE [dbo].[Get_Patient_By_MRN] (#MRN varchar(10))
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #Patient_Sql nvarchar(MAX) = '
SELECT * FROM OPENQUERY([CERNER_APP_DB_PROD],''
Select P.PERSON_ID
, PA1.ALIAS AS MRN
, PA2.ALIAS AS NHS_Number
, P.NAME_LAST_KEY
, P.NAME_FIRST_KEY
, P.NAME_FULL_FORMATTED
, P.BIRTH_DT_TM
From V500.PERSON P LEFT JOIN V500.PERSON_ALIAS PA1
ON PA1.PERSON_ID = P.PERSON_ID AND
PA1.PERSON_ALIAS_TYPE_CD = 10
LEFT JOIN V500.PERSON_ALIAS PA2
ON PA2.PERSON_ID = P.PERSON_ID AND
PA2.PERSON_ALIAS_TYPE_CD = 18
Where PA1.ALIAS = ' + QUOTENAME(#MRN,'''') + ');';
EXEC sp_executesql #Patient_Sql;
END;
I have some trouble creating a dynamic Where clause.
I would like to pass in a parameter to a function, and then use that parameter to retrieve values from the database and use that in my Where clause, and then return a resulting value.
I've tried numerous options, but my best try so far is:
CREATE FUNCTION dbo.GetID (#TaskID varchar(10))
RETURNS Int
AS
BEGIN
DECLARE #TaskType varchar(10)
DECLARE #TaskSubType TinyInt
DECLARE #ID Int
DECLARE #SQL varchar(400)
SELECT #TaskType = TaskType, #TaskSubType = TaskSubType
FROM Tasks
WHERE TaskID = #TaskID
SET #SQL = 'SELECT #ID = ID
FROM ZCircuitFaults
WHERE TaskType = #TaskType AND ' +
CASE WHEN ISNULL(#TaskSubType, '') <> ''
THEN '(TaskSubType Is Null OR TaskSubType = CAST(#TaskSubType AS Varchar))'
ELSE 'TaskSubType Is Null'
END
exec sp_executesql #SQL
, N'#ID Int, #TaskType varchar(10), #TaskSubType tinyint'
, #ID, #TaskType, #TaskSubType
, #ID = #ID OUTPUT
RETURN #ID
END
When I call:
PRINT dbo.GetID('ABC123')
I get the error:
Only functions and some extended stored procedures can be executed from within a function.
Problem is that you cannot use dynamic SQL from within a function and you can't call stored procedures as well. With that solution is, convert your function to a stored procedure.
Also, I don't see why you need a dynamic SQL. Your dynamic SQL part can just be
SELECT #ID = ID
FROM ZCircuitFaults
WHERE TaskType = #TaskType AND
CASE WHEN ISNULL(#TaskSubType, '') <> ''
THEN (TaskSubType Is Null OR TaskSubType = CAST(#TaskSubType AS Varchar))
ELSE TaskSubType Is Null
END
and then
RETURN #ID;
EDIT:
Your entire WHERE part can be simplified to below
WHERE TaskType = #TaskType
AND (
TaskSubType Is Null
OR
TaskSubType = ISNULL(CAST(#TaskSubType AS Varchar), '')
)
Per books online, you cannot use dynamic SQL inside a function:
User-defined functions cannot make use of dynamic SQL or temp tables.
Table variables are allowed.
http://msdn.microsoft.com/en-us/library/ms191320.aspx Limitations and Restrictions section. You will need to put this into a stored procedure.
#Rahul is right, you don't need dynamic SQL.
Can we perform Insert/Update/Delete statement with SQL Server Functions. I have tried with but SQL Server error is occured.
Error:
Invalid use of side-effecting or time-dependent operator in 'DELETE' within a function.
AnyBody have any Idea why we can not use Insert/Update/Delete statements with SQL Server functions.
Waiting for your good idea's
No, you cannot.
From SQL Server Books Online:
User-defined functions cannot be used
to perform actions that modify the
database state.
Ref.
Yes, you can!))
Disclaimer: This is not a solution, it is more of a hack to test out something. User-defined functions cannot be used to perform actions that modify the database state.
I found one way to make INSERT, UPDATE or DELETE in function using xp_cmdshell.
So you need just to replace the code inside #sql variable.
CREATE FUNCTION [dbo].[_tmp_func](#orderID NVARCHAR(50))
RETURNS INT
AS
BEGIN
DECLARE #sql varchar(4000), #cmd varchar(4000)
SELECT #sql = 'INSERT INTO _ord (ord_Code) VALUES (''' + #orderID + ''') '
SELECT #cmd = 'sqlcmd -S ' + ##servername +
' -d ' + db_name() + ' -Q "' + #sql + '"'
EXEC master..xp_cmdshell #cmd, 'no_output'
RETURN 1
END
Functions in SQL Server, as in mathematics, can not be used to modify the database. They are intended to be read only and can help developer to implement command-query separation. In other words, asking a question should not change the answer. When your program needs to modify the database use a stored procedure instead.
You can't update tables from a function like you would a stored procedure, but you CAN update table variables.
So for example, you can't do this in your function:
create table MyTable
(
ID int,
column1 varchar(100)
)
update [MyTable]
set column1='My value'
but you can do:
declare #myTable table
(
ID int,
column1 varchar(100)
)
Update #myTable
set column1='My value'
Yes, you can.
However, it requires SQL CLR with EXTERNAL_ACCESS or UNSAFE permission and specifying a connection string. This is obviously not recommended.
For example, using Eval SQL.NET (a SQL CLR which allow to add C# syntax in SQL)
CREATE FUNCTION [dbo].[fn_modify_table_state]
(
#conn VARCHAR(8000) ,
#sql VARCHAR(8000)
)
RETURNS INT
AS
BEGIN
RETURN SQLNET::New('
using(var connection = new SqlConnection(conn))
{
connection.Open();
using(var command = new SqlCommand(sql, connection))
{
return command.ExecuteNonQuery();
}
}
').ValueString('conn', #conn).ValueString('sql', #sql).EvalReadAccessInt()
END
GO
DECLARE #conn VARCHAR(8000) = 'Data Source=XPS8700;Initial Catalog=SqlServerEval_Debug;Integrated Security=True'
DECLARE #sql VARCHAR(8000) = 'UPDATE [Table_1] SET Value = -1 WHERE Name = ''zzz'''
DECLARE #rowAffecteds INT = dbo.fn_modify_table_state(#conn, #sql)
Documentation: Modify table state within a SQL Function
Disclaimer: I'm the owner of the project Eval SQL.NET
You can have a table variable as a return type and then update or insert on a table based on that output.
In other words, you can set the variable output as the original table, make the modifications and then do an insert to the original table from function output.
It is a little hack but if you insert the #output_table from the original table and then say for example:
Insert into my_table
select * from my_function
then you can achieve the result.
We can't say that it is possible of not their is some other way exist to perform update operation in user-defined Function. Directly DML is not possible in UDF it is for sure.
Below Query is working perfectly:
create table testTbl
(
id int identity(1,1) Not null,
name nvarchar(100)
)
GO
insert into testTbl values('ajay'),('amit'),('akhil')
Go
create function tblValued()
returns Table
as
return (select * from testTbl where id = 1)
Go
update tblValued() set name ='ajay sharma' where id = 1
Go
select * from testTbl
Go
"Functions have only READ-ONLY Database Access"
If DML operations would be allowed in functions then function would be prety similar to stored Procedure.
No, you can not do Insert/Update/Delete.
Functions only work with select statements. And it has only READ-ONLY Database Access.
In addition:
Functions compile every time.
Functions must return a value or result.
Functions only work with input parameters.
Try and catch statements are not used in functions.
CREATE FUNCTION dbo.UdfGetProductsScrapStatus
(
#ScrapComLevel INT
)
RETURNS #ResultTable TABLE
(
ProductName VARCHAR(50), ScrapQty FLOAT, ScrapReasonDef VARCHAR(100), ScrapStatus VARCHAR(50)
) AS BEGIN
INSERT INTO #ResultTable
SELECT PR.Name, SUM([ScrappedQty]), SC.Name, NULL
FROM [Production].[WorkOrder] AS WO
INNER JOIN
Production.Product AS PR
ON Pr.ProductID = WO.ProductID
INNER JOIN Production.ScrapReason AS SC
ON SC.ScrapReasonID = WO.ScrapReasonID
WHERE WO.ScrapReasonID IS NOT NULL
GROUP BY PR.Name, SC.Name
UPDATE #ResultTable
SET ScrapStatus =
CASE WHEN ScrapQty > #ScrapComLevel THEN 'Critical'
ELSE 'Normal'
END
RETURN
END
Functions are not meant to be used that way, if you wish to perform data change you can just create a Stored Proc for that.
if you need to run the delete/insert/update you could also run dynamic statements. i.e.:
declare
#v_dynDelete NVARCHAR(500);
SET #v_dynDelete = 'DELETE some_table;';
EXEC #v_dynDelete
Just another alternative using sp_executesql (tested only in SQL 2016).
As previous posts noticed, atomicity must be handled elsewhere.
CREATE FUNCTION [dbo].[fn_get_service_version_checksum2]
(
#ServiceId INT
)
RETURNS INT
AS
BEGIN
DECLARE #Checksum INT;
SELECT #Checksum = dbo.fn_get_service_version(#ServiceId);
DECLARE #LatestVersion INT = (SELECT MAX(ServiceVersion) FROM [ServiceVersion] WHERE ServiceId = #ServiceId);
-- Check whether the current version already exists and that it's the latest version.
IF EXISTS(SELECT TOP 1 1 FROM [ServiceVersion] WHERE ServiceId = #ServiceId AND [Checksum] = #Checksum AND ServiceVersion = #LatestVersion)
RETURN #LatestVersion;
-- Insert the new version to the table.
EXEC sp_executesql N'
INSERT INTO [ServiceVersion] (ServiceId, ServiceVersion, [Checksum], [Timestamp])
VALUES (#ServiceId, #LatestVersion + 1, #Checksum, GETUTCDATE());',
N'#ServiceId INT = NULL, #LatestVersion INT = NULL, #Checksum INT = NULL',
#ServiceId = #ServiceId,
#LatestVersion = #LatestVersion,
#Checksum = #Checksum
;
RETURN #LatestVersion + 1;
END;