0

I was recently posed a problem by a student based on assignment instructions they'd gotten elsewhere, and I don't think the requested result is possible. Perhaps the question was poorly worded. See what you think.....

The instructions were to create a FUNCTION (Notably, NOT a stored proc) that returns rows based on a parameter that would be plugged into a WHERE clause. (Here's the goofy part). The instructions also said "If the result is null, return a zero."

My major problems with this:

  • The result of a TVF (inline or multi-valued) would never to my knowledge return NULL. An empty result set maybe, but not null.
  • We could still phony this up somehow using Stored Proc return values, but the instructions pointedly requested a function.
  • Even if we somehow COULD return a zero from a TVF, what happens when we're using the TVF as a data source in a query? (Either alone or joined to another table's result set). We'd be... what? Joining a scalar value to a result set?!?

Is there some new feature of T-SQL that I missed that would make this possible? Cuz I'm just not seeing it.

(With a stored proc, we could do this)

CREATE PROCEDURE EmployeeDetails
(@empID int)
AS

declare @rows int;

SELECT @rows= count(*) from Employees
 where EmployeeID=@empID;

if @rows=0
 RETURN 0;
else
 SELECT * from Employees where EmployeeID=@empID;
 RETURN @@rowcount;
go

(But we're still getting either a full result set and a row count for the return value for a valid EmployeeID, or just a return value for an invalidEmployeeID)

(For an actual TVF, this is the best I could do, which is still going to result in problems, problems that would be even further exacerbated when there were more fields in the results that would potentially need to be joined to other PK/FK combos)

CREATE FUNCTION dbo.EmployeeWithNullTest 
(
    @empID int
)
RETURNS 
@outputTable TABLE 
(
     employeeid int
    ,lastname varchar(20)
    ,firstname varchar(20)
)
AS
BEGIN
    -- Fill the table variable with the rows for your result set
    insert into @outputTable
    select employeeid, firstname, lastname 
    from employees
    where employeeid=@empID

    if (select count(*) from @outputTable)=0
    insert into @outputTable  values (0,0,0)

    RETURN 
END
GO
Dale K
  • 25,246
  • 15
  • 42
  • 71
J. Barrett
  • 49
  • 3
  • 5
    A function returns a type, and that type *must* be well defined. A TVF can return a scalar value, yes, but it would still be a table; it would just contain a single row and column. Considering that inline TVFs are often more performant that scalar functions (especially in older versions of SQL Server) such functions aren't uncommon. – Thom A Mar 02 '23 at 22:23
  • 2
    *"The instructions also said "If the result is null, return a zero.""* I would interpret this to mean `ISNULL(,0)` in the `SELECT`. – Thom A Mar 02 '23 at 22:25
  • 1
    It could also mean return zero rows – siggemannen Mar 02 '23 at 22:31
  • Yeah I think the instructions probably originally said return 0 rows. – Dale K Mar 02 '23 at 22:32
  • 1
    But the actual answer to your question is no... it just wouldn't make sense, rows are used in different scenarios to a scalar value, and they don't mix. – Dale K Mar 02 '23 at 22:33
  • To clarify, the exact instruction was "If the result is NULL, return a Zero." Doesn't really make things any clearer, does it? – J. Barrett Mar 02 '23 at 22:37

2 Answers2

0

Yes this is possible using an inline table-valued function (or a regular query for that matter. You can do it in a single statement, so a multi-statement TVF is not necessary.

We just start with a single-row VALUES constructor as a driving row, and left-join anything else to it. Note that all WHERE conditions must be moved to the ON.

CREATE OR ALTER FUNCTION dbo.EmployeeWithNullTest 
(
    @empID int
)
RETURNS TABLE
AS RETURN
SELECT
     employeeid = ISNULL(e.employeeid, 0)
    ,lastname   = ISNULL(e.lastname, '')
    ,firstname  = ISNULL(e.firstname, '')
FROM (VALUES(0)) v(dummy)
LEFT JOIN employees e ON e.employeeid = @empID;

Having said that, it is rare that you would actually want to do this, because you could instead just use OUTER APPLY to call the function. That acts similar to a left-join, so you can just ISNULL it on the outside.

Charlieface
  • 52,284
  • 6
  • 19
  • 43
  • That doesn't return a scalar value 0 though (as OP asked), it returns no rows. – Dale K Mar 03 '23 at 00:00
  • Wrong, it returns a single row with `0` https://dbfiddle.uk/6Eh1cVEv – Charlieface Mar 03 '23 at 00:11
  • 1
    Either way, thats not what the OP is asking. – Dale K Mar 03 '23 at 00:12
  • 1
    It would actually return a single row with "0 - NULL - NULL", which, again, is not what the original question was asking for. – J. Barrett Mar 03 '23 at 19:23
  • 1
    @CharlieFace, YOUR version returns a single scalar value in either case (And it's also not the same code as what you're commenting on). The real "challenge" here is not just to return a single column, but to return ROWS if the employeeID is valid, but a single scalar ZERO if it isn't. I think the permutations we've gone through to this point prove fairly definitively that the original question was poorly phrased. – J. Barrett Mar 03 '23 at 19:30
  • OK you can `ISNULL` the other columns also. The example i gave in comments was because I couldn't be bothered to mock up data. And this can indeed return multiple rows, eg https://dbfiddle.uk/SbihJN0E. Unless your problem is that you want either one column or multiple columns depending on results, which you absolutely cannot do in SQL without dynamic code, nor should you ever try to do. SQL queries *must* have a well defined "shape" ie the columns have specific names and types, doing it dynamically is not really supported. – Charlieface Mar 04 '23 at 21:10
  • Heh... interesting. What answer would all you folks badmouthing this solution actually provide, eh? – Jeff Moden Mar 05 '23 at 00:39
  • 1
    @JeffModen There have been rare occasions when this is useful. One case comes to mind, don't remember exact details: an `APPLY` subquery against a child table, where I couldn't `ISNULL` on the outside due to some complex aggregation, also this similar one on DBA https://dba.stackexchange.com/a/316542/220697. Also some answers on here where OP wants a row to always be returned from a query https://stackoverflow.com/a/68818459/14868997 https://stackoverflow.com/a/66753356/14868997 https://stackoverflow.com/a/65829485/14868997 – Charlieface Mar 05 '23 at 01:28
  • @Charlieface - Just to be sure, I wasn't in contention with what you said. I was in contention with all the others that were but provided no useful alternative. As to its basic utility, I can see lot's of uses for it. For example, a splitter function returning a NULL if it was given a NULL as the string to split rather than returning nothing. Otherwise, it would return the 1 or more elements that have been split from the given string. – Jeff Moden Mar 06 '23 at 02:34
0

"Is there some new feature of T-SQL that I missed that would make this possible? Cuz I'm just not seeing it."

What you stated is correct. TVF only returns a table. You could do it with a procedure, but that would not meet the "odd" requirements

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Mar 17 '23 at 20:43