使用 SQL Server 中的函数插入/更新/删除

Posted

技术标签:

【中文标题】使用 SQL Server 中的函数插入/更新/删除【英文标题】:Insert/Update/Delete with function in SQL Server 【发布时间】:2011-09-03 07:12:17 【问题描述】:

我们可以用SQL Server Functions 执行Insert/Update/Delete 语句吗?我试过了,但是出现了 SQL Server 错误。

错误:

Invalid use of side-effecting or time-dependent operator in 'DELETE' within a function.

AnyBody 知道为什么我们不能将 Insert/Update/Delete 语句与 SQL Server functions. 一起使用

等待你的好主意

【问题讨论】:

另见Execute Stored Procedure from a Function 你为什么需要这样的东西? 【参考方案1】:

函数并不意味着以这种方式使用,如果您希望执行数据更改,您可以为此创建一个存储过程。

【讨论】:

【参考方案2】:
  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

【讨论】:

【参考方案3】:

是的,你可以!))

免责声明:这不是一个解决方案,它更像是一个 hack 来测试一些东西。用户定义的函数不能用于执行修改数据库状态的操作。

我找到了一种使用xp_cmdshell 在函数中生成INSERTUPDATEDELETE 的方法。

所以你只需要替换 @sql 变量中的代码。

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

【讨论】:

+1 为答案,因为它似乎是唯一的解决方案,但组件 xp_cmdshell 必须打开。这在很多情况下都会成为问题,因为出于安全原因,系统管理员不允许这样做。 虽然很有创意,但我会尽量记住将来是否绝对必要,但这种方法没有交易覆盖范围。 需要先开启cmdshell EXEC sp_configure 'show advanced options', 1 GO RECONFIGURE GO EXEC sp_configure 'xp_cmdshell', 1 GO RECONFIGURE GO【参考方案4】:

您可以将表变量作为返回类型,然后根据该输出更新或插入表。 换句话说,您可以将变量输出设置为原始表,进行修改,然后从函数输出中插入原始表。 这是一个小技巧,但是如果您从原始表中插入 @output_table 然后例如说: 插入 my_table select * from my_function

那么你就可以达到效果了。

【讨论】:

【参考方案5】:

不,您不能进行插入/更新/删除。

函数仅适用于 select 语句。而且它只有只读数据库访问权限。

另外:

函数每次都编译。 函数必须返回一个值或结果。 函数仅适用于输入参数。 函数中不使用 Try 和 catch 语句。

【讨论】:

【参考方案6】:

“函数只有只读数据库访问” 如果函数中允许 DML 操作,那么函数将与存储过程非常相似。

【讨论】:

和执行过程不同【参考方案7】:

是的,你可以。

但是,它需要具有 EXTERNAL_ACCESS 或 UNSAFE 权限并指定连接字符串的 SQL CLR。这显然是不推荐

例如,使用Eval SQL.NET(允许在 SQL 中添加 C# 语法的 SQL CLR)

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)

文档:Modify table state within a SQL Function

免责声明:我是项目的所有者Eval SQL.NET

【讨论】:

【参考方案8】:

我们不能说在用户自定义函数中执行更新操作的方式可能不是他们的其他方式。在 UDF 中直接 DML 是不可能的,这是肯定的。

以下查询运行良好:

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

【讨论】:

【参考方案9】:

使用 sp_executesql 的另一种选择(仅在 SQL 2016 中测试)。 正如之前的帖子所指出的,原子性必须在其他地方处理。

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;

【讨论】:

错误:只能在函数内执行函数和一些扩展存储过程。 这是在 SQL 2016 上运行的生产代码,如帖子中所述。不同版本的 SQL Server 会有不同的功能和限制。【参考方案10】:

如果您需要运行删除/插入/更新,您也可以运行动态语句。即:

declare 
    @v_dynDelete                 NVARCHAR(500);

 SET @v_dynDelete = 'DELETE some_table;'; 
 EXEC @v_dynDelete

【讨论】:

EXEC 不能在函数中使用。【参考方案11】:

您不能像使用存储过程那样通过函数更新表,但可以更新表变量。

例如,你不能在你的函数中这样做:

create table MyTable
(
    ID int,
    column1 varchar(100)
)
update [MyTable]
set column1='My value'

但你可以这样做:

declare @myTable table
(
    ID int,
    column1 varchar(100)
)

Update @myTable
set column1='My value'

【讨论】:

请记住,如果您从临时内存表中更新/删除,Microsoft Management Studio 可能会给您一个误报,因为遗憾的是,即使在版本 14 上,智能感知仍然无法识别数据库之间表和内存变量表。 是的,如果您只是按照建议使用更新语句,它就可以正常工作。有没有办法使用 update-from 语法?在函数内部使用 update-from 语法时,出现“在函数中无效使用副作用运算符 'update'”错误。【参考方案12】:

SQL Server 中的函数,如数学中的函数,不能用于修改数据库。它们是只读的,可以帮助开发人员实现command-query separation。换句话说,问一个问题不应该改变答案。当您的程序需要修改数据库时,请改用存储过程。

【讨论】:

【参考方案13】:

,你不能。

来自 SQL Server 联机丛书:

不能使用用户自定义函数 执行修改 数据库状态。

Ref.

【讨论】:

以上是关于使用 SQL Server 中的函数插入/更新/删除的主要内容,如果未能解决你的问题,请参考以下文章

SQL server 管理表数据

sql server有批量插入和批量更新的sql语句吗

使用 SQL Server DTS 包有条件地在目标表中插入/更新行

更新或插入 SQL Server 时忽略错误行

深入理解SQL Server 2005 中的 COLUMNS_UPDATED函数

如果数据是从表中更新的,则应将数据插入到 SQL Server 2017 中的另一个表中