使用 SQL Server 中的存储过程动态更新

Posted

技术标签:

【中文标题】使用 SQL Server 中的存储过程动态更新【英文标题】:Dynamically update with stored procedures in SQL Server 【发布时间】:2016-06-03 11:05:05 【问题描述】:

我们有一张表如下:

CREATE TABLE dbo.Test (
    StartDate DATETIME2 NOT NULL,
    EndDate DATETIME2 NULL
    Field1 NVARCHAR(50) NOT NULL,
    Field2 NVARCHAR(50) NOT NULL,
    etc.
)

如果我们插入一行,EndDate 总是 NULL。如果涉及更新,我们不会真正更新现有行,而是设置它的 EndDate 并插入一个新行。假设有一个用于插入和更新的存储过程。

DECLARE @newStartDate DATETIME2
SELECT @newStartDate = GETDATE()

UPDATE dbo.Test SET (EndDate = @newStartDate) WHERE StartDate = @someDate

INSERT INTO dbo.Test (StartDate, EndDate, Field1, Field2) 
     SELECT @newStartDate, NULL, Field1, Field2 
       FROM dbo.Test where StartDate = @someDate AND EndDate = @newStartDate

可能会发生我们需要设置其他开始/插入值而不是旧值的情况。可以按如下方式完成。

INSERT INTO dbo.Test (StartDate, EndDate, Field1, Field2) 
    SELECT @newStartDate, NULL, @parameter, Field2 
      FROM dbo.Test where StartDate = @someDate AND EndDate = @newStartDate

现在的主要问题是实际表包含超过 100 列,上述方法虽然可行,但很容易出错。

我正在寻找一种动态方法,其中插入语句动态获取参数值(如果存在)。

在 C# 中,我会做这样的事情。 existing 将是包含每一列的 Dictionaryoverrides 不是需要更改的默认值。

var existing= ..."SELECT TOP 1 * from dbo.Test"
foreach (var changeParameter in overrides)
     existing[changeParameter.Key] = changeParameter.Value;

existing 中的所有值现在都存储为它们应该用于插入的值。因此我可以构建一个新的插入语句。

这种方法在数据库上是否可行?我不想加载所有数据行只是为了立即将其写回。我宁愿直接在数据库上做所有事情。

【问题讨论】:

“我正在寻找一种动态方法,其中插入语句动态获取参数值” - 为什么不只是动态构建插入 SQL? @stuartd 当然可以,但我该怎么做呢?我想在数据库上构建插入语句。如何决定是否可以动态地从旧行中获取参数“@field1”或 Field1? 我是否更正了您想要具有 insert (cols) values (vals) 的 sql 脚本,其中 cols listvals 列表将包含一些值数组,这些值是从别的地方传过来的?这是你的动态部分吗?一长串可能不同的列? @IvanStarostin 是的,传递参数的列表可能很长,主要目标是从参数中选择值(如果提供),或者从上一行中选择。 @Dani 所以参数集具有恒定数量的元素,但其中一些是空值(并且此插入可能放置在存储过程中) 元素数量 传递变量和insert 脚本是在客户端构建的临时查询? 【参考方案1】:

我正在寻找一种动态方法,其中插入语句 如果存在,则动态获取参数值。

这是我一直这样做的方式:

通过将默认值设为 NULL 来使参数可选。

如果没有提供参数值,则COALESCE 带有您要使用的字段的参数:

INSERT INTO dbo.Test (StartDate, EndDate, Field1, Field2) 
    SELECT @newStartDate, NULL, COALESCE(@parameter,Field1), Field2 
      FROM dbo.Test where StartDate = @someDate AND EndDate = @newStartDate

就这么简单。

编辑:

如果您使用表值参数并加入它,那么您的 COALESCE 看起来像这样,如果 TVP 中每个字段有 1 列:

COALESCE(tvp.Field1, Test.Field1)

或者,如果您将 TVP 作为 FieldName、FieldValue 对的垂直表传递,那么您将不得不使用 CASE 表达式或子查询,如下所示:

COALESCE((SELECT FieldValue FROM TVP WHERE FieldName='Field1'),Field1)

【讨论】:

谢谢,唯一的问题是我的表有超过 270 列,那将是一个相当多的参数列表... 在这种情况下,您可以将表值参数 JOIN 传递给它,并使用相同的技术。 “加入”是什么意思?我认为我没有正确理解它。你有例子吗? JOINing 表是一个基本的 SQL 操作,它过于宽泛,无法在 SO 答案中教授。如果你 google SQL JOIN Tutorial 你会发现很多例子。 我知道 JOIN 是什么,但我不明白加入表值参数如何解决我的问题。【参考方案2】:

回答你的问题:

我正在寻找一种动态方法,其中插入语句 如果存在,则动态获取参数值。这种方法是 以某种方式可能在数据库上?

    要在 SQL 服务器中获得 T-SQL 动态,您必须使用 sp_executesql,更多信息:

    Execute Dynamic SQL commands in SQL Server

    您必须将字段参数作为某种数组传递。有几种方法,这里是一种方法:

    Passing array to a SQL Server Stored Procedure

    您的解决方案将涉及使用CURSOR 来构建动态 Sql 查询,请参见示例:

    SQL Server Cursor Example

【讨论】:

请看我上面的评论 您已经在 C# 示例中通过提及键/值对回答了这个问题。您必须传递给存储过程字段和值组合的内容。【参考方案3】:

不确定 c# 如何解析查询参数,但您可以执行以下操作:

var sql = "
declare @param1 int = :param1,
  @param2 int = :param2,
  @param3 date = :param3,
  @somedate datetime = :somedate,
  @newstartdate datetime = :newstartdate,
  ...


insert into dbo.Test (...full list of columns)
select isnull(@param1, field1), isnull(@param2, field2), ...and so on
from dbo.Test
where StartDate = @someDate AND EndDate = @newStartDate
"

然后将值或NULL分配给所有参数。 ISNULL 将执行此操作,如果 new 为 NULL,您将拥有 新值旧值。但是需要一些技巧才能完全放置 null。

您必须在运行时在客户端构建整个查询

按照@TabAlleman 的建议传递带有参数名称和值的 TVP,并检查此表是否存在与表中特定列同名的参数。然后继续ISNULL 或其他东西来选择是保留现有值还是从 TVP 中获取传递。

【讨论】:

以上是关于使用 SQL Server 中的存储过程动态更新的主要内容,如果未能解决你的问题,请参考以下文章

动态sql存储过程更新查询问题?

SQL Server 存储过程中的可选参数?

SQL Server存储过程的使用

SQL Server创建存储过程——动态SQL

SQL Server创建存储过程——动态SQL

SQL Server创建存储过程——动态SQL