Cannot nest inline table functions - bug or bad code?

  • I have the following "dummy" function:

    IF OBJECT_ID('FN_REPLACE', N'IF') IS NOT NULL

    DROP FUNCTION FN_REPLACE

    GO

    CREATE FUNCTION dbo.FN_REPLACE

    (

    @pString NVARCHAR(MAX)

    ,@pReplaceSegment NVARCHAR(MAX)

    ,@pReplaceWith NVARCHAR(MAX)

    )

    RETURNS TABLE AS

    RETURNSELECTREPLACE(@pString, @pReplaceSegment, @pReplaceWith) AS replaced_string

    GO

    And i try to nest calls to the function like following:

    SELECT*

    FROMdbo.FN_REPLACE(

    (

    SELECT*

    FROMdbo.FN_REPLACE('Testing string', 'Test', '')

    )

    ,'String'

    ,''

    ) x

    But this raises error in Microsoft SQL Server 2012 - 11.0.2100.60 (X64):

    Msg 4429, Level 16, State 1, Line 2

    View or function 'dbo.FN_REPLACE' contains a self-reference. Views or functions cannot reference themselves directly or indirectly.

    Is this a limitation of Inline-Functions or is there a way to make nested functions work?

  • Deleted comment

    The absence of evidence is not evidence of absence
    - Martin Rees
    The absence of consumable DDL, sample data and desired results is, however, evidence of the absence of my response
    - Phil Parkin

  • You're not simply nesting the functions, you're self-referencing. That's very different, and, as you're seeing in the error message, not allowed.

    "The credit belongs to the man who is actually in the arena, whose face is marred by dust and sweat and blood"
    - Theodore Roosevelt

    Author of:
    SQL Server Execution Plans
    SQL Server Query Performance Tuning

  • I don't actually reference the function inside itself.

    Script #2 is what i try to run outside of the created function body.

    The function definition is still the same as in Script #1, the whole script is therefore:

    IF OBJECT_ID('FN_REPLACE', N'IF') IS NOT NULL

    DROP FUNCTION FN_REPLACE

    GO

    CREATE FUNCTION dbo.FN_REPLACE

    (

    @pString NVARCHAR(MAX)

    ,@pReplaceSegment NVARCHAR(MAX)

    ,@pReplaceWith NVARCHAR(MAX)

    )

    RETURNS TABLE AS

    RETURNSELECTREPLACE(@pString, @pReplaceSegment, @pReplaceWith) AS replaced_string

    GO

    SELECT*

    FROMdbo.FN_REPLACE(

    (

    SELECT*

    FROMdbo.FN_REPLACE('Testing string', 'Test', '')

    )

    ,'String'

    ,''

    ) x

    GO

  • The syntax of your query might work for scalar functions, but it's incorrect for table-valued functions. Try this instead:

    SELECT *

    FROM dbo.FN_REPLACE('Testing string', 'Test', '') x1

    CROSS APPLY dbo.FN_REPLACE(x1.replaced_string,'String','')

    β€œWrite the query the simplest way. If through testing it becomes clear that the performance is inadequate, consider alternative query forms.” - Gail Shaw

    For fast, accurate and documented assistance in answering your questions, please read this article.
    Understanding and using APPLY, (I) and (II) Paul White
    Hidden RBAR: Triangular Joins / The "Numbers" or "Tally" Table: What it is and how it replaces a loop Jeff Moden

  • Nah, it works fine, if the function name in the "nest" is different:

    IF OBJECT_ID('FN_REPLACE', N'IF') IS NOT NULL

    DROP FUNCTION FN_REPLACE

    GO

    IF OBJECT_ID('FN_REPLACE2', N'IF') IS NOT NULL

    DROP FUNCTION FN_REPLACE2

    GO

    CREATE FUNCTION dbo.FN_REPLACE

    (

    @pString NVARCHAR(MAX)

    ,@pReplaceSegment NVARCHAR(MAX)

    ,@pReplaceWith NVARCHAR(MAX)

    )

    RETURNS TABLE AS

    RETURNSELECTREPLACE(@pString, @pReplaceSegment, @pReplaceWith) AS replaced_string

    GO

    CREATE FUNCTION dbo.FN_REPLACE2

    (

    @pString NVARCHAR(MAX)

    ,@pReplaceSegment NVARCHAR(MAX)

    ,@pReplaceWith NVARCHAR(MAX)

    )

    RETURNS TABLE AS

    RETURNSELECTREPLACE(@pString, @pReplaceSegment, @pReplaceWith) AS replaced_string

    GO

    SELECT*

    FROMdbo.FN_REPLACE(

    (

    SELECT*

    FROMdbo.FN_REPLACE2('Testing string', 'Test', '') -- FN_REPLACE2 works fine

    )

    ,'String'

    ,''

    ) x

    GO

  • Consider closely what this is doing. The return from your function is NOT an nvarchar(max). It returns a table with a single column that you are then trying to pass as the first parameter to your outer function. That is why you can't "nest" them. You would need to create a user defined table type and make that both the inbound and outbound datatype to do this like you are trying to do.

    I was going to suggest using cross apply but I see that Chris already posted that suggestion. That would be a lot cleaner than creating a duplicate of your function.

    _______________________________________________________________

    Need help? Help us help you.

    Read the article at http://www.sqlservercentral.com/articles/Best+Practices/61537/ for best practices on asking questions.

    Need to split a string? Try Jeff Modens splitter http://www.sqlservercentral.com/articles/Tally+Table/72993/.

    Cross Tabs and Pivots, Part 1 – Converting Rows to Columns - http://www.sqlservercentral.com/articles/T-SQL/63681/
    Cross Tabs and Pivots, Part 2 - Dynamic Cross Tabs - http://www.sqlservercentral.com/articles/Crosstab/65048/
    Understanding and Using APPLY (Part 1) - http://www.sqlservercentral.com/articles/APPLY/69953/
    Understanding and Using APPLY (Part 2) - http://www.sqlservercentral.com/articles/APPLY/69954/

  • Sean Lange (10/10/2014)


    Consider closely what this is doing. The return from your function is NOT an nvarchar(max). It returns a table with a single column that you are then trying to pass as the first parameter to your outer function. That is why you can't "nest" them. You would need to create a user defined table type and make that both the inbound and outbound datatype to do this like you are trying to do.

    I was going to suggest using cross apply but I see that Chris already posted that suggestion. That would be a lot cleaner than creating a duplicate of your function.

    Or simply make the function a scalar function. The nesting of scalar functions wouldn't be causing you any headaches.

    ----------------------------------------------------------------------------------
    Your lack of planning does not constitute an emergency on my part...unless you're my manager...or a director and above...or a really loud-spoken end-user..All right - what was my emergency again?

  • Matt Miller (#4) (10/10/2014)


    Sean Lange (10/10/2014)


    Consider closely what this is doing. The return from your function is NOT an nvarchar(max). It returns a table with a single column that you are then trying to pass as the first parameter to your outer function. That is why you can't "nest" them. You would need to create a user defined table type and make that both the inbound and outbound datatype to do this like you are trying to do.

    I was going to suggest using cross apply but I see that Chris already posted that suggestion. That would be a lot cleaner than creating a duplicate of your function.

    Or simply make the function a scalar function. The nesting of scalar functions wouldn't be causing you any headaches.

    No headaches other than the awful performance. πŸ˜‰

    _______________________________________________________________

    Need help? Help us help you.

    Read the article at http://www.sqlservercentral.com/articles/Best+Practices/61537/ for best practices on asking questions.

    Need to split a string? Try Jeff Modens splitter http://www.sqlservercentral.com/articles/Tally+Table/72993/.

    Cross Tabs and Pivots, Part 1 – Converting Rows to Columns - http://www.sqlservercentral.com/articles/T-SQL/63681/
    Cross Tabs and Pivots, Part 2 - Dynamic Cross Tabs - http://www.sqlservercentral.com/articles/Crosstab/65048/
    Understanding and Using APPLY (Part 1) - http://www.sqlservercentral.com/articles/APPLY/69953/
    Understanding and Using APPLY (Part 2) - http://www.sqlservercentral.com/articles/APPLY/69954/

  • Sean Lange (10/10/2014)


    Consider closely what this is doing. The return from your function is NOT an nvarchar(max). It returns a table with a single column that you are then trying to pass as the first parameter to your outer function. That is why you can't "nest" them. You would need to create a user defined table type and make that both the inbound and outbound datatype to do this like you are trying to do.

    Yes, that's what i thought to, but originally my query was just trying to select this value as a correlated subquery or whatever the name for this thing is, something like:

    SELECT'123', (SELECT TOP 1 y.replaced_string FROM dbo.FN_REPLACE(

    (SELECT (

    SELECTTOP 1 x.replaced_string

    FROMdbo.FN_REPLACE('Testing string', 'Test', '') x

    )

    )

    ,'String'

    ,''

    ) y)

    Of course this is a convoluted example, but it shows that i "convert" whatever is returned from FN_REPLACE into a regular field in both places.

    I also added all aliases that might be needed and removed '*' so there is absolutely no doubt that it just fetches a single field.

    From my understanding of SQL syntax, this code should "convert" whatever is returned from FN_REPLACE into a scalar value, and i see no reason for this syntax not to work, that's why i'm asking if this is a bug. I agree with everyone that this is not a good solution AND looks ugly as hell, but i got hit by this issue today, so wanted to check if this is an actual bug/limitation of SQL Server or "buggy" code on my part.

  • siggemannen (10/10/2014)


    Sean Lange (10/10/2014)


    Consider closely what this is doing. The return from your function is NOT an nvarchar(max). It returns a table with a single column that you are then trying to pass as the first parameter to your outer function. That is why you can't "nest" them. You would need to create a user defined table type and make that both the inbound and outbound datatype to do this like you are trying to do.

    Yes, that's what i thought to, but originally my query was just trying to select this value as a correlated subquery or whatever the name for this thing is, something like:

    SELECT'123', (SELECT TOP 1 y.replaced_string FROM dbo.FN_REPLACE(

    (SELECT (

    SELECTTOP 1 x.replaced_string

    FROMdbo.FN_REPLACE('Testing string', 'Test', '') x

    )

    )

    ,'String'

    ,''

    ) y)

    Of course this is a convoluted example, but it shows that i "convert" whatever is returned from FN_REPLACE into a regular field in both places.

    I also added all aliases that might be needed and removed '*' so there is absolutely no doubt that it just fetches a single field.

    From my understanding of SQL syntax, this code should "convert" whatever is returned from FN_REPLACE into a scalar value, and i see no reason for this syntax not to work, that's why i'm asking if this is a bug. I agree with everyone that this is not a good solution AND looks ugly as hell, but i got hit by this issue today, so wanted to check if this is an actual bug/limitation of SQL Server or "buggy" code on my part.

    It really isn't a bug or anything like that. You are trying to force a square peg in a round hole. By that I mean you are trying to treat a table valued function like a scalar function. They are not the same beast. Did you take a look at using cross apply like Chris posted? It works perfectly and that is by far the best way to deal with this type of thing.

    Just for completeness sake here is the entire package. Using your original create function code and a slight modification to Chris' code to only return the final value.

    IF OBJECT_ID('FN_REPLACE', N'IF') IS NOT NULL

    DROP FUNCTION FN_REPLACE

    GO

    CREATE FUNCTION dbo.FN_REPLACE

    (

    @pString NVARCHAR(MAX)

    ,@pReplaceSegment NVARCHAR(MAX)

    ,@pReplaceWith NVARCHAR(MAX)

    )

    RETURNS TABLE AS

    RETURNSELECTREPLACE(@pString, @pReplaceSegment, @pReplaceWith) AS replaced_string

    GO

    SELECT x2.*

    FROM dbo.FN_REPLACE('Testing string', 'Test', '') x1

    CROSS APPLY dbo.FN_REPLACE(x1.replaced_string,'String','') x2

    _______________________________________________________________

    Need help? Help us help you.

    Read the article at http://www.sqlservercentral.com/articles/Best+Practices/61537/ for best practices on asking questions.

    Need to split a string? Try Jeff Modens splitter http://www.sqlservercentral.com/articles/Tally+Table/72993/.

    Cross Tabs and Pivots, Part 1 – Converting Rows to Columns - http://www.sqlservercentral.com/articles/T-SQL/63681/
    Cross Tabs and Pivots, Part 2 - Dynamic Cross Tabs - http://www.sqlservercentral.com/articles/Crosstab/65048/
    Understanding and Using APPLY (Part 1) - http://www.sqlservercentral.com/articles/APPLY/69953/
    Understanding and Using APPLY (Part 2) - http://www.sqlservercentral.com/articles/APPLY/69954/

  • Hi,

    Yes, i saw the solution. The reason i'm stubborn is that it works fine if i call different inline TVFs inside each other, as i demonstrated with my second script.

    If my syntax is incorrect, i should then be given an error there as well.

    But anyway, i will use the CROSS APPLY syntax so we can "close" this issue πŸ™‚

Viewing 12 posts - 1 through 11 (of 11 total)

You must be logged in to reply to this topic. Login to reply