Skip to main content
Added section to more directly answer the original question, rather than just its gloss
Source Link

As to how you write 'robust' T-SQL data processing routines: really that just comes down to common sense. Source control SQL object definitions as seriously as you source control pure application code, and where it makes sense add unit tests - realistically test-driven development of T-SQL sprocs is impractical (and a bit pointless, cf. the 'glue' point above), but that doesn't mean you shouldn't have any sort of regression testing in place, it will probably just be more end-to-end in nature compared to unit testing of the C# code. Also don't start using fancier T-SQL features just because you can, for example exception handling is better done on the C# side, even though T-SQL has a TRY/CATCH construct.

As to how you write 'robust' T-SQL data processing routines: really that just comes down to common sense. Source control SQL object definitions as seriously as you source control pure application code, and where it makes sense add unit tests - realistically test-driven development of T-SQL sprocs is impractical (and a bit pointless, cf. the 'glue' point above), but that doesn't mean you shouldn't have any sort of regression testing in place, it will probably just be more end-to-end in nature compared to unit testing of the C# code. Also don't start using fancier T-SQL features just because you can, for example exception handling is better done on the C# side, even though T-SQL has a TRY/CATCH construct.

Source Link

Your specific table variable example isn't literally true since table types exist (https://docs.microsoft.com/en-us/sql/t-sql/statements/create-type-transact-sql). So if you have an exact same structure to reuse in different stored procedures, you can add its definition to the database and then use that type in multiple places:

-- constraints and IDENTITY added just to show they can be
CREATE TYPE NameAmountTable AS TABLE (
    RowNum INT IDENTITY PRIMARY KEY,
    Name VARCHAR(35) NOT NULL UNIQUE,
    Amount DECIMAL(12,2)
);

GO

CREATE OR ALTER PROCEDURE Proc1 AS
BEGIN
    DECLARE @NameAmountTable NameAmountTable;
    INSERT INTO @NameAmountTable
        (Name, Amount)
    VALUES
        ('Example 1', 12345.67),
        ('Example 2', 12345.67);
    
    SELECT * FROM @NameAmountTable;
END;

GO

CREATE OR ALTER PROCEDURE Proc2 AS
BEGIN
    DECLARE @NameAmountTable NameAmountTable;
    INSERT INTO @NameAmountTable
        (Name, Amount)
    VALUES
        ('Example 3', 12345.67),
        ('Example 4', 12345.67);
    
    SELECT * FROM @NameAmountTable;
END;

That said, table types aren't the equivalent of classes in C#, and are barely the equivalent of structs. In fact one of their main purposes is interop with application code: declare a stored procedure parameter as a table type, and it enables passing data in bulk from (say) C# in a strongly-typed fashion, 'strongly typed' in both a general and database-y sense (hence the ability to include most sorts of constraints in a table type's definition). Try to use table types more broadly however, and you will quickly come up against their limitations - hard to change once something else is using the type in its own signature, impossible to nest, no encapsulation or inheritance, etc. So you're right, T-SQL is an absolute disaster when it comes to even a 1990s OOP paradigm, let alone anything more modern.

However, the usage cases of procedural SQL (whether that's T-SQL or PL/pgSQL or whatever) are typically fairly boring, repetitive bulk data preparation and manipulation tasks, like your wider example actually illustrates. Essentially the work is about glueing together a sequence of INSERT, UPDATE or DELETE statements (= pure database SQL things) with a bit of conditional logic, not too much though, with the ability to raise a custom error when needed.

Now, the overall sequence of those INSERT, UPDATE and DELETE statements will likely be important to get right; it will likely require knowledge of the underlying database design (e.g. knowledge of foreign keys, check constraints and other database-y things). Moreover, for performance, the best implementation will probably need to consider topics such as temporary tables, indexes and so forth (so more database-y things). What you need, then, is a language that enables working with the database directly, making pure SQL calls trivial, and includes just enough general programming features to tie things altogether. And that, in a nutshell, is pretty much what T-SQL provides, ditto the equivalent procedural SQL extension languages of other RDBMSs.