3

How would I return a table from a SQL Server function?

In Postgres, I would simply do something like the following:

CREATE FUNCTION table_get(_active_bool BOOLEAN)
RETURNS TABLE(column integer)
language plpgsql
as $$
BEGIN
         RETURN QUERY
SELECT column
FROM table
WHERE active = _active_bool
END;
$$;

And it will just work.

For what ever reason I can't get this one to work in SQL Server.

CREATE FUNCTION hr.naughty_emp_id_get
    (@pquarter NVARCHAR(1),
     @pyear NVARCHAR(4))
RETURNS TABLE (employeeid INT)
AS
BEGIN
    WITH vars AS 
    (
         SELECT @pquarter AS pquarter, @pyear AS pyear
    )
    SELECT tblhr_employees.employeeid
    FROM hr.tblhr_employees
    INNER JOIN hr.tblHR_AttendancePunchTime ON tblhr_employees.employeeid       = tblHR_AttendancePunchTime.EmployeeID
    INNER JOIN hr.tblHR_AttendanceTimeCode  ON tblHR_AttendancePunchTime.CodeID = tblHR_AttendanceTimeCode.CodeID
    WHERE 1 = 1
      AND (tblHR_AttendanceTimeCode.CategoryID = 3
           OR tblHR_AttendanceTimeCode.CategoryID = 11)
      AND dbo.to_year_quarter(tblHR_AttendancePunchTime.AdjTimeIn) = (SELECT vars.pyear FROM vars) + '-' + (SELECT vars.pquarter FROM vars)
      AND tblhr_employees.separationdate IS NULL
    GROUP BY 
        tblhr_employees.employeeid;

    RETURN
END
GO

It is throwing this error:

Msg 156, Level 15, State 1, Procedure naughty_emp_id_get, Line 18 [Batch Start Line 6]
Incorrect syntax near the keyword 'BEGIN'

I tried adding ;s in various spots and it didn't seem to work

3 Answers 3

3

You have mixed 2 ways of declaring the resulting temporal table.

Either declare as table variable and explicitly insert into it:

CREATE FUNCTION hr.naughty_emp_id_get
(
    @pquarter NVARCHAR(1)
  , @pyear NVARCHAR(4)
)
RETURNS @result TABLE (employeeid INT) -- Here
AS
BEGIN
    ;WITH vars AS (SELECT @pquarter AS pquarter, @pyear AS pyear)
    INSERT INTO @result (employeeid) -- And here
    SELECT tblhr_employees.employeeid
    FROM --...

END

Or avoid it's declaration altogether:

CREATE FUNCTION hr.naughty_emp_id_get
(
    -- Add the parameters for the function here
    @pquarter NVARCHAR(1)
  , @pyear NVARCHAR(4)
)
RETURNS TABLE AS RETURN
    WITH vars AS (SELECT @pquarter AS pquarter, @pyear AS pyear)
    SELECT tblhr_employees.employeeid
    FROM --...
Sign up to request clarification or add additional context in comments.

4 Comments

It's worth pointing out that this is not merely a syntax difference. The second version creates an inline table-valued function, which can be expanded by the optimizer when invoked in queries and typically has better performance characteristics than the non-inline variant (although occasionally the opposite can be the case, depending on how the function is used).
Ideally what @JeroenMostert said is true, however Microsoft claims to have fixed this in SQL Server 2017 by adaptive query processing (youtube.com/watch?time_continue=2&v=szTmo6rTUjM). I don't have an instance to verify this , but I will try and get back on this topic when I can
@SQLApostle: SQL Server 2017 improves things by allowing accurate cardinality estimates for multi-statement TVFs and changing the execution plan based on that, which will improve their characteristics in many scenarios. However, the basic difference between multi-statement and inline TVFs remains: inline TVFs are expanded in the query, multi-statement TVFs are materialized first. There's use cases for both; the improvements for MSTVFs mean they can now be better than inline TVFs in more cases (especially for complex queries), making it even more important to try both.
Link to the docs with details for people who can't or don't want to watch videos.
2

You are missing the table name for the table to be returned. This should work

 CREATE FUNCTION hr.naughty_emp_id_get
 (
     -- Add the parameters for the function here
     @pquarter NVARCHAR(1)
   , @pyear NVARCHAR(4)
 )
 RETURNS @employees TABLE (employeeid INT)
 AS
 BEGIN
 WITH vars AS (SELECT @pquarter AS pquarter, @pyear AS pyear)
 INSERT @employees
 SELECT tblhr_employees.employeeid
 FROM hr.tblhr_employees
 INNER JOIN hr.tblHR_AttendancePunchTime ON tblhr_employees.employeeid       = tblHR_AttendancePunchTime.EmployeeID
 INNER JOIN hr.tblHR_AttendanceTimeCode  ON tblHR_AttendancePunchTime.CodeID = tblHR_AttendanceTimeCode.CodeID
 WHERE 1=1
 AND (tblHR_AttendanceTimeCode.CategoryID      = 3
 OR tblHR_AttendanceTimeCode.CategoryID       = 11)
 AND dbo.to_year_quarter(tblHR_AttendancePunchTime.AdjTimeIn) = (SELECT vars.pyear FROM vars) + '-' + (SELECT vars.pquarter FROM vars)
 AND tblhr_employees.separationdate IS NULL
 GROUP BY tblhr_employees.employeeid;

     RETURN
 END

2 Comments

It did thank you. It ran and I was able to successfully use the following select. SELECT * FROM hr.naughty_emp_id_get('1','2018');
@DanielL.VanDenBosch If this works (and read the other suggestion - it is very important), then please accept it as the answer. That is how SO works, questions get asked and answered so others can find those answers.
1

You have to insert into the resulting table variable.

RETURNS @MyTable TABLE (MyID INT)
AS BEGIN    
    INSERT INTO @MyTable SELECT 1
    RETURN 
END 

1 Comment

I think you have a syntax error here... missing the TABLE after the variable.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.