从表值参数将数据插入表中

Posted

技术标签:

【中文标题】从表值参数将数据插入表中【英文标题】:Inserting data into table from table-valued parameter 【发布时间】:2021-06-27 22:24:26 【问题描述】:

我正在尝试创建一个存储过程,将 TVP 传递给该存储过程,然后将 TVP 中的一些数据插入到两个表中。

我已经实现了存储过程,但只有第二个插入(唯一一个不从 TVP 读取的)在工作。另外两个不工作(不要插入任何东西),我似乎不知道为什么。

我尝试在 SQL Server 中创建一个虚拟 TVP 并在那里运行该过程,但这也不起作用。这是因为 TVP 是只读的吗?我认为不会,因为我实际上并没有在 TVP 中插入或更新数据。

有没有办法让它工作?

感谢您的帮助!

表值参数定义:

CREATE TYPE dbo.Ingredients 
AS TABLE 
( 
  Quantity int,
  Measure nvarchar(50),
  Ingredient nvarchar(50),
)
GO

存储过程:

ALTER PROCEDURE uspCreateRecipe 
    (@IDUser int, 
     @RecipeName nvarchar(50), 
     @Category nvarchar(50), 
     @Difficulty nvarchar(50), 
     @Duration nvarchar(50), 
     @ING dbo.Ingredients READONLY, 
     @Execution text)
AS
BEGIN
    INSERT INTO dbo.Ingredients 
    VALUES ((SELECT Ingredient FROM @ING WHERE NOT EXISTS (SELECT Ingredient FROM @ING WHERE Ingredient IN (SELECT IngredientName FROM dbo.Ingredients))), 2)

    INSERT INTO dbo.Recipes 
    VALUES (@IDUser, @RecipeName, NULL, 
            (SELECT IDDifficulty FROM dbo.Difficulty WHERE Difficulty = @Difficulty), 
            (SELECT IDDuration FROM dbo.Duration  WHERE Duration = @Duration ), 
            NULL, 
            (SELECT IDCategory FROM dbo.Category WHERE CategoryName = @Category ), 
            @Execution , NULL, 2, GETDATE())

    INSERT INTO dbo.Recipes_Ingredients 
    VALUES (SCOPE_IDENTITY(), 
            (SELECT Quantity FROM @ING), 
            (SELECT IDMeasure FROM dbo.Measure WHERE Measure IN (SELECT Measure FROM @ING)), 
            (SELECT IDIngredient FROM dbo.Ingredients WHERE IngredientName IN (SELECT Ingredient FROM @ING)))
END

【问题讨论】:

向我们展示您的minimal reproducible example,即您在 TVP 中提供了一些值但没有任何反应。并且出于调试目的,在您的 SP 中放置一些选择来计算即将到来的内容。此外,作为最佳实践,您应该始终列出要插入的列。并且不要使用valuesselect 的组合来获取要插入的值,使用一个或另一个。 @Execution text) 不不不不不!这种数据类型已被弃用近 20 年。它不应该用于新的开发。 空白不花钱,你知道 我的眼睛不够大,无法分辨,但看起来您在not exists 子查询中没有使用correlated subqueries。如果没有相关性,它只会变成外部查询中 all 行的全有或全无开关。 嘿@SMor!感谢您的输入,我将数据库中的所有文本变量都更改为 nvarchar(max),就像 Dale 说的那样。 【参考方案1】:
    不要在子查询中使用VALUES,而是使用SELECT。 始终列出要插入的列。它更清晰,并且会减少错误,尤其是在您将来修改表结构时, 您的第一个查询似乎过于复杂 - 如果它确实有效的话。 您的第三个查询应该抛出错误,因为您有多个 IN 子查询,这应该导致“子查询返回多个结果”错误。 text 数据类型已折旧使用 varchar(max)。 通常你想SET NOCOUNT, XACT_ABORT ON。 始终RETURN 一个状态,以便您的调用应用程序知道它是否成功。默认情况下会返回 0,我更喜欢显式。 分号终止所有语句。
ALTER PROCEDURE uspCreateRecipe
(
  @IDUser int
  , @RecipeName nvarchar(50)
  , @Category nvarchar(50)
  , @Difficulty nvarchar(50)
  , @Duration nvarchar(50)
  , @ING dbo.Ingredients READONLY
  , @Execution nvarchar(max) -- text is depreciated
)
AS
BEGIN
    SET NOCOUNT, XACT_ABORT ON;

    INSERT INTO dbo.Ingredients ([Name], Col2)
        SELECT Ingredient, 2
        FROM @ING
        WHERE Ingredient NOT IN (SELECT IngredientName FROM dbo.Ingredients);

    INSERT INTO dbo.Recipes (IDUser, RecipeName, Col3, IDDifficulty, IDDuration, Col6, IDCategory, Col8, Col9, Col10, Co11)
        SELECT @IDUser, @RecipeName, NULL, IDDifficulty
            , (SELECT IDDuration FROM dbo.Duration WHERE Duration = @Duration)
            , NULL
            , (SELECT IDCategory FROM dbo.Category WHERE CategoryName = @Category)
            , @Execution, NULL, 2, GETDATE()
        FROM dbo.Difficulty
        WHERE Difficulty = @Difficulty;

    INSERT INTO dbo.Recipes_Ingredients (IDRecipe, Quantity, IDMeasureid, IDIngredient)
        SELECT SCOPE_IDENTITY(), Quantity
            , (SELECT IDMeasure FROM dbo.Measure WHERE Measure = I.Measure)
            , (SELECT IDIngredient FROM dbo.Ingredients WHERE IngredientName = I.Ingredient)
        FROM @ING I;

    RETURN 0;
END;

【讨论】:

为什么RETURN 0; 你还期望它返回什么,你为什么还要关心? @Charlieface 我喜欢明确说明这些事情 :) 嗨@DaleK!很抱歉没有尽快回复您,但我没空。很快回答您的问题:是的,该程序现在就像一个魅力!但也要感谢您分享的所有提示和最佳实践!以后我会把它们牢记在心。它们都非常有意义,除了一个:为什么需要返回状态?它实际上是做什么的?我对这个世界很陌生,从一开始就学会以正确的方式做事总是很重要的。这不是我们第一次见面,所以感谢您的可用性和知识共享! :) @Martunis99 SP 的返回值是一个 int 状态,旨在与调用上下文通信,无论 SP 是否成功。默认返回 0,表示 OK,但我更喜欢明确。如果出现故障情况,那么您可以返回一些其他数字以将其传达给您的应用程序/调用上下文。 @DaleK 我明白了,这是有道理的。再次感谢您分享的知识世界,并帮助我在这里学习绳索!

以上是关于从表值参数将数据插入表中的主要内容,如果未能解决你的问题,请参考以下文章

从表中选择所有行并仅将不同的值插入到 C# 中另一个位置的数据库中

将数据从表插入临时表,然后从临时表中选择特定行

如何从表中检索我的最后一个插入行?

根据特定条件从表中获取已插入数据的插入查询

如何使用 pl/sql 中的游标将多列数据插入包含单列的表中?

通过将表名作为参数传递,使用 oracle 中的存储过程从表中搜索数据