If we want to reuse parameterized queries,
it is usually preferable to wrap them in user-defined functions. It is
typically less convenient to reuse parameterized queries that are
wrapped in stored procedures, as the following examples will
demonstrate.
Let's say we have a stored procedure that returns all sales for the month, across all states, as shown in Listing 1.
Hopefully, you spotted the missed opportunity for code reuse in this listing. We should have reused our MonthReportingPeriodStart in the WHERE clause; I leave this as an exercise for the reader.
We now need to develop a stored procedure that retrieves the total sales per state for a given month, and we want to reuse the SelectSalesForMonth
stored procedure, Although it's possible to do this, we will need to
create a table variable or a temporary table with a structure that
matches the structure of the result set returned by stored procedure, as
shown in Listing 2.
We can run a smoke test to verify that our two stored procedures work.
So far so good; we have reused the code wrapped in SelectSalesForMonth
procedure and it works. However, now suppose we want to select the
state with the highest total sales for a given month. It looks as if we
can simply reuse the SelectSalesPerStateForMonth procedure, again with a slight modification to create a table variable or a temporary table, as shown in Listing 4.
Unfortunately, although the procedure creates, it does not work.
Unfortunately, the INSERT...EXEC approach that we used in SelectSalesPerStateForMonth procedure cannot be nested. This is a very serious limitation.
The two inline UDFs shown in Listing 5-18 implement the same requirements. Note that the TotalSalesPerStateForMonth function implements the same functionality as our previous SelectTotalSalesPerStateForMonth stored procedure.
As per our rules of code reuse, we would only ever implement one or the other, not both, in our solutions.
In contrast to what we saw in Listing 5, our attempt to reuse result sets returned from nested inline UDFs works just fine.
It is often easier to reuse code when it is wrapped
in inline UDFs than when it is wrapped in stored procedures. I should
emphasize that I refer only to inline UDFs, not to all three varieties
of UDF. Whenever we are deciding whether to use stored procedures or
UDFs, we also need to consider the following:
INSERT EXEC requires you to create a
table variable or temporary table before doing the call; stored
procedures can have multiple and/or varying result sets, depending on
code path, causing all kinds of problems with INSERT EXEC
some functionality, such as data modifications and TRY...CATCH blocks, is not allowed in UDFs
the
inline UDF, like a view, is expanded in the execution plan, giving the
optimizer the choice to take shortcuts, or even remove joined tables if
their columns are not used.
Let's discuss performance considerations and see why it might not be a good idea to use scalar UDFs.