Search code examples
sqlsql-servert-sqlstored-procedurestable-valued-parameters

Inserting data into table from table-valued parameter


I am trying to create a stored procedure to which is passed a TVP and then some data from the TVP is inserted into two tables.

I have already implemented the stored procedure, but only the second insert (the only one that does not read from the TVP) is working. The other two are not working (do not insert anything) and I can't seem to figure out why.

I have tried to create a dummy TVP in SQL Server and run the procedure there, but that also did not work. Is this being caused by the fact TVPs are readonly? I would assume not, since I am not actually inserting or updating data inside the TVP.

Is there a way to make this work?

Thank you for your assistance!

Table-valued parameter definition:

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

Stored procedure:

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

Solution

    1. Rather than using VALUES with sub-queries, just use SELECT.
    2. Always list the columns you are inserting into. Its clearer and will reduce errors especially if you modify the table structure in future,
    3. Your first query appeared to be overcomplicated - if indeed it worked at all.
    4. Your third query should have thrown an error because you have multiple IN sub-queries which should have resulted in a "sub-query returned multiple results" error.
    5. The text datatype is depreciated use varchar(max).
    6. Normally you want to SET NOCOUNT, XACT_ABORT ON.
    7. Always RETURN a status so your calling app knows whether it succeeded or not. 0 will be returned by default by I prefer to be explicit.
    8. Semi-colon terminate all statements.
    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;