Weird error in udf when included in “WHERE…IN” cla

2019-06-21 05:57发布

问题:

I have a function that splits up a string (pasted at the end for clarity). This function works as expected when used alone. Example:

SELECT value
FROM dbo.mg_fn_Split('2#1','#')

Returns

-- value --
--   2   --
--   1   --
-----------

But when used in a "WHERE IN" clause, as in this example (more on tableA later on):

SELECT * FROM TableA WHERE TableA.id IN
(
  SELECT value
  FROM dbo.mg_fn_Split('2#1','#')
)

I get the error: "Invalid length parameter passed to the LEFT or SUBSTRING function."

TableA is used here as an example. Using different tables (assuming they have the id column) sometimes returns correct results, while on other tables I get the error.

I'm assuming it has something to do with order of execution, but I still fail to see what could "corrupt" the function.

I'm looking for a "what's happening" explanation, not a "use this instead". I know I can use joins for example to get the results.

The function definition:

-- Description: Returns a table containing the results of a string-split operation.
-- Params:
--      DelimitedList: The string to split
--      Delimiter: The delimiter char, defaults to ','
-- Columns:
--      Position - The char index of the item
--      Value - The actual item
-- =============================================
CREATE Function [dbo].[mg_fn_Split]
(   
    @DelimitedList nvarchar(max)
    , @Delimiter nvarchar(2) = ','
)
RETURNS TABLE 
AS
RETURN 
    (
    With CorrectedList As
        (
        Select Case When Left(@DelimitedList, Len(@Delimiter)) <> @Delimiter Then @Delimiter Else '' End
            + @DelimitedList
            + Case When Right(@DelimitedList, Len(@Delimiter)) <> @Delimiter Then @Delimiter Else '' End
            As List
            , Len(@Delimiter) As DelimiterLen
        )
        , Numbers As 
        (
        Select TOP( Coalesce(DataLength(@DelimitedList)/2,0) ) Row_Number() Over ( Order By c1.object_id ) As Value
        From sys.columns As c1
            Cross Join sys.columns As c2
        )
    Select CharIndex(@Delimiter, CL.list, N.Value) + CL.DelimiterLen As Position
        , Substring (
                    CL.List
                    , CharIndex(@Delimiter, CL.list, N.Value) + CL.DelimiterLen     
                    , CharIndex(@Delimiter, CL.list, N.Value + 1)                           
                        - ( CharIndex(@Delimiter, CL.list, N.Value) + CL.DelimiterLen ) 
                    ) As Value
    From CorrectedList As CL
        Cross Join Numbers As N
    Where N.Value <= DataLength(CL.List) / 2
        And Substring(CL.List, N.Value, CL.DelimiterLen) = @Delimiter
    )

EDIT: I've set up a fiddle to exhibit this: http://sqlfiddle.com/#!3/9f9ff/3

回答1:

This happens when you data in inner query becomes as follows.

SELECT value FROM dbo.mg_fn_Split('#','#') --------------> You will get error here.

SELECT value FROM dbo.mg_fn_Split('2#1','#') -------------> No error here.

SELECT value FROM dbo.mg_fn_Split('2','#') --------------------> No error here.

SELECT value FROM dbo.mg_fn_Split('','#') ----------------------> No error here.

so basically when the data you are splitting and the delimiter is same the error will happen.

The problem is with these statements.

      " Select Case When Left(@DelimitedList, Len(@Delimiter)) <> @Delimiter Then @Delimiter Else '' End
        + @DelimitedList
        + Case When Right(@DelimitedList, Len(@Delimiter)) <> @Delimiter Then @Delimiter Else '' End"

if you change this to

    Select Case When Left(@DelimitedList, Len(@Delimiter)) <> @Delimiter Then @Delimiter Else '1' End
        + @DelimitedList
        + Case When Right(@DelimitedList, Len(@Delimiter)) <> @Delimiter Then @Delimiter Else '1' End

then it will work out fine.. all you are doing is adding '1' instead of ''... hope this helps.