【问题标题】:Inserting DataTable & individual parameters into a table using stored procedure使用存储过程将 DataTable 和单个参数插入表中
【发布时间】:2020-10-11 21:11:01
【问题描述】:

我正在尝试使用存储过程更新/插入 SQL 表。它的输入是一个 DataTable 和其他单独的参数。

EmployeeDetails表:

ID |  Name | Address    |  Operation  |  Salary
---+-------+------------+-------------+------------
1  | Jeff  | Boston, MA |  Marketing  |  95000.00
2  | Cody  | Denver, CO |  Sales      |  91000.00 

用户定义表类型(DataTable)的语法:

CREATE TYPE EmpType AS TABLE 
(
    ID INT, 
    Name VARCHAR(3000), 
    Address VARCHAR(8000), 
    Operation SMALLINT
)

操作流程:

ALTER PROCEDURE spEmpDetails
    @Salary Decimal(10,2),
    @Details EmpType READONLY
AS
BEGIN
    UPDATE e 
    SET e.Name = d.Name, 
        e.Address = d.Address 
    FROM EmployeeDetails e, @Details d 
    WHERE d.ID = e.ID
            
   --For inserting the new records in the table
   INSERT INTO EmployeeDetails(ID, Name, Address) 
       SELECT ID, Name, Address 
       FROM @Details;
END

此过程spEmpDetails 将其输入作为单独的参数@Salary 和数据表@Details。使用这些输入,我正在尝试更新/取消插入 EmployeeDetails 表。但是,我未能在更新/插入语句中将这些输入连接在一起。在上面的代码中,我只使用了@Details DataTable 数据来更新EmployeeDetails 表,而我缺少@Salary 来更新表中的数据。

我正在寻找一些关于如何做到这一点的建议。任何建议将不胜感激。

【问题讨论】:

  • 您希望表中的所有员工都拥有相同的薪水吗?如果不是,为什么薪水是标量值而不是表值参数的另一列?另外,为什么要使用隐式连接?显式连接成为 ANSI 标准的一部分已有 30 年了,真的没有理由再使用隐式连接了。
  • @ZoharPeled - 感谢您的回复。并非所有员工的薪水都相同,但输入数据表也一次获取一条记录,具体取决于数据表的来源。 “薪水”是从不同的数据源获取到存储过程,但我认为我可以将此薪水添加到数据表源,同时使用其他信息(即 ID、姓名和地址)创建记录。
  • Bad habits to kick : using old-style JOINs - 旧式 逗号分隔的表格列表 样式已替换为 ANSI 中的 proper ANSI JOIN 语法-92 SQL 标准(25 多年前),不鼓励使用它
  • @marc_s。我可以很容易地纠正这个问题,但我的实际问题是如何使用 Insert 语句插入两个数据源值。我认为我们偏离了我最初的问题。
  • 正如@ZoharPeled 已经提到的:这里应该如何使用@Salary 变量非常不清楚。将所有员工的工资设置为该值?将要插入的所有员工的工资设置为该值?你需要澄清......

标签: sql sql-server


【解决方案1】:

...但是输入数据表也一次获取一条记录...

这是一个危险的假设,即使您现在控制正在发送到存储过程的数据表。在未来的某一天,您可能会被替换,或者其他人可能想要使用此存储过程 - 由于该过程本身没有针对数据表中的多条记录的内置保护 - 这只是一个等待发生的错误。

如果您只需要将一条记录传递到存储过程中,请不要使用以 - 开头的表值参数,而是将所有参数设为标量。
它不仅会更安全,而且会更好地传达存储过程的意图,因此更易于维护。

如果您希望存储过程能够处理多条记录,请在表值参数中添加薪金列,并删除@salary 标量参数。


话虽如此,您的存储过程中还有其他问题:

  • insert...select 语句中没有 where 子句 - 这意味着您要么在表值参数中插入所有记录,要么因违反唯一约束而失败。
  • 您在更新语句中使用了隐式联接。当您只对两个表使用内部联接时,这可能不是一个大问题,但显式联接有充分的理由使其成为 SQL-92 - 因为它们提供了更好的可读性,更重要的是,更好的编译检查。欲了解更多信息,请阅读 Aaron Bertrand 的 Bad Habits to Kick : Using old-style JOINs

那么,如何正确编写“upsert”程序?好吧,Aaron 也写过这个 - right here in StackOverflow.


但是,在某些有效的用例中,您确实希望将来自表值参数和标量变量的输入结合起来 - 这样做的方式非常简单。

更新:

UPDATE target
SET Column1 = source.Column1,
    Column2 = source.Column2,
    Column3 = @ScalarVariable
FROM TargetTable As target
JOIN @TVP As source
    ON target.Id = source.Id -- or whatever join condition

对于插入:

INSERT INTO TargetTable(Column1, Column2, Column3) 
SELECT Column1, Column2, @ScalarVariable
FROM @TVP

【讨论】:

    【解决方案2】:

    我认为您正在寻找类似的东西。通过在一个块中有 2 个 DML 语句时设置 XACT_ABORT ON,如果抛出异常,both 将完全回滚。为了确保只插入新记录,OUTPUT 子句被添加到UPDATE 语句中,以识别受影响的 ID。 INSERT 语句不包括 UPDATE'ed 的 ID。

    这种情况和 Aaron Bertrand 的优秀 answer 有点不同。在这种情况下,只有一行被插入,Aaron 在允许 INSERT 发生之前明智地检查UPDATE 是否影响了一行(通过检查@@rowcount)。在这种情况下,UDT 可能包含许多行,因此 both UPDATEINSERT 都是可能的。

    ALTER PROCEDURE spEmpDetails
        @Salary Decimal(10,2),
        @Details EmpType READONLY
    AS
    set nocount on;
    set xact_abort on;
    --set transaction isolation level serializable; /* please look into */
    
    begin transaction
    begin try
        declare @e          table(ID        int unique not null);
    
        UPDATE e 
        SET e.Name = d.Name, 
            e.Address = d.Address,
            e.Salary = @Salary
        output inserted.ID into @e
        FROM EmployeeDetails e,
             join @Details d on e.ID=d.ID;
    
       --For inserting the new records in the table
        INSERT INTO EmployeeDetails(ID, Name, Address, Operation, Salary) 
        SELECT ID, Name, Address, Operation, @Salary 
        FROM @Details d
        where not exists(select 1
                         from EmployeeDetails e
                         where d.ID=e.ID);
    
        commit transaction;
    end try
    begin catch
        /* logging / raiserror / throw */
    
        rollback transaction;
    end catch
    go
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-01-05
      • 2020-05-22
      • 1970-01-01
      • 1970-01-01
      • 2016-01-31
      • 1970-01-01
      • 2016-11-21
      • 1970-01-01
      相关资源
      最近更新 更多