Tag: Table-Valued Parameters

  • Stored Procedure Parameters in SQL Server: Input, Output, Default, and Table-Valued

    Stored Procedure Parameters in SQL Server: Input, Output, Default, and Table-Valued

    Four parameter patterns cover almost everything you’ll need to build — from a simple optional filter to passing an entire list of values into a procedure without ever concatenating a string.

    Four ways to pass data in and outDEFAULT parameter@tier NVARCHAR(20)=’standard’omit it → default usedOUTPUT parametervalue flows caller ⇆ procneeds OUTPUT on BOTH sides ⚠️TABLE-VALUED parampass a whole list, READONLYtype-safe, multi-columnOLD WAY: CSV string‘Dana Park,Elena Petrova’breaks on embedded commasno type safety, one column onlyupgrade toNEW WAY: Table-Valued ParamCREATE TYPE … AS TABLE(…)many columns, real types, READONLYRemember: OUTPUT is required at the CALL SITE too —omit it there and SQL Server treats it as input-only.

    Default Parameters (Optional Input)

    CREATE PROCEDURE dbo.usp_CountCustomersByTier
        @tier NVARCHAR(20) = 'standard'
    AS
    BEGIN
        SET NOCOUNT ON;
        SELECT COUNT(*) AS customer_count FROM dbo.Customer WHERE tier = @tier;
    END;
    GO
    EXEC dbo.usp_CountCustomersByTier; -- uses default
    EXEC dbo.usp_CountCustomersByTier @tier = 'premium'; -- overrides it

    OUTPUT Parameters (Returning Values to the Caller)

    CREATE PROCEDURE dbo.usp_GetCustomerCount
        @tier NVARCHAR(20), @total INT OUTPUT
    AS
    BEGIN
        SET NOCOUNT ON;
        SELECT @total = COUNT(*) FROM dbo.Customer WHERE tier = @tier;
    END;
    GO
    
    DECLARE @count INT;
    EXEC dbo.usp_GetCustomerCount @tier = 'standard', @total = @count OUTPUT;
    PRINT 'Standard customers: ' + CAST(@count AS VARCHAR(10));
    Common mistake: Forgetting the OUTPUT keyword on the calling side, not just in the CREATE PROCEDURE definition. Without it at the call site too, SQL Server silently treats the parameter as input-only — your @count variable stays whatever it was before the call, with no error raised.

    Table-Valued Parameters: The Modern Way to Pass a List

    CREATE TYPE dbo.CustomerNameList AS TABLE (name NVARCHAR(100));
    GO
    
    CREATE PROCEDURE dbo.usp_GetCustomersByNames
        @Names dbo.CustomerNameList READONLY
    AS
    BEGIN
        SET NOCOUNT ON;
        SELECT c.customer_id, c.name, c.email
        FROM dbo.Customer c
        INNER JOIN @Names n ON n.name = c.name;
    END;
    GO
    
    DECLARE @list dbo.CustomerNameList;
    INSERT INTO @list VALUES ('Dana Park'), ('Elena Petrova');
    EXEC dbo.usp_GetCustomersByNames @Names = @list;

    TVPs are the correct, set-based way to pass a list into a procedure — far better than the old pattern of passing a comma-separated string and splitting it inside the procedure, which is exactly the kind of row-by-row string manipulation Chapter 1’s WHILE-loop lesson warned against. They’re always READONLY: you can read from them, never modify the caller’s table — attempting an UPDATE/DELETE/INSERT against @Names inside the procedure body is a compile error, by design.

    The Old Way, for Comparison

    -- The pre-TVP pattern (2005 and earlier, still seen in legacy code):
    CREATE PROCEDURE dbo.usp_GetCustomersByNames_Legacy @NameCsv NVARCHAR(MAX) AS
    BEGIN
        SELECT c.* FROM dbo.Customer c
        INNER JOIN STRING_SPLIT(@NameCsv, ',') s ON s.value = c.name; -- fragile: commas in names break it
    END;

    Beyond fragility with embedded delimiters, the string-splitting approach also loses type safety entirely (everything is text until parsed) and can’t easily pass more than one column of data per “row.” A TVP’s table type can have as many columns as you need, each with its own real data type.

    Practice tip: Extend the CustomerNameList table type to include a second column (say, a minimum tier to filter by per name), and adjust the procedure and JOIN accordingly. Seeing a TVP carry more than one column per row is what makes its advantage over a comma-separated string genuinely click.

    Enjoyed this?

    Subscribe to get every new SQL Server lesson as soon as it’s published, and share it with a developer who’d find it useful.

    📡 Subscribe via RSS  | 
    Share on X  | 
    Share on LinkedIn  | 
    Share on Facebook

    Want the full structured course with quizzes, projects, and 10+ exercises per chapter? Check out SQL Server for Developers & DBAs, coming soon on this site.