2012-09-18 12 views
5

Mam funkcję, która dzieli ciąg znaków (wklejony na końcu dla jasności). Ta funkcja działa zgodnie z oczekiwaniami, gdy jest używana samodzielnie. Przykład:Dziwny błąd w udf, gdy jest zawarty w "WHERE ... IN" klauzula

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

Zwraca

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

Ale kiedy jest stosowany w "gdzie" klauzula, tak jak w tym przykładzie (więcej na TABLEA później):

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

otrzymuję błąd: "Niepoprawny parametr długości przekazywany do funkcji LEFT lub SUBSTRING."

Tabela A jest tutaj użyta jako przykład. Używanie różnych tabel (zakładając, że mają kolumnę id) czasami zwraca poprawne wyniki, podczas gdy na innych tabelach pojawia się błąd.

Zakładam, że ma to coś wspólnego z kolejnością wykonania, ale nadal nie widzę, co mogłoby "zepsuć" funkcję.

Szukam wyjaśnienia "co się dzieje", a nie "użyj tego zamiast". Wiem, że mogę używać sprzężeń na przykład, aby uzyskać wyniki.

Definicja funkcji:

-- 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: mam założyć skrzypce wykazują to: http://sqlfiddle.com/#!3/9f9ff/3

+3

UDF wbudowane zostają rozszerzone na zapytanie, więc prawdopodobnie niektóre operacje łączenia lub filtry są oceniane w innej kolejności, jakiej się nie spodziewałeś. –

+0

Jaki jest powód nieużywania połączeń? IMHO najlepiej jest zostawić parsowanie ciągów poleceń sql, jeśli to możliwe. –

+0

Zgadzam się z komentarzem Martina, uważam, że twoja klauzula WHERE dla twojej funkcji jest podnoszona do twojego Select * z TableA. Jeśli skomentujesz następującą etykietę And Substring (CL.List, N.Value, CL.DelimiterLen) = @Delimiter w twojej funkcji, dostaniesz ten sam problem, który został wystawiony, spowodowany przez jedną z twoich wartości Substringów oceniających -1. –

Odpowiedz

0

To się dzieje, gdy dane w kwerendzie wewnętrznej staje się następująco.

WYBIERZ wartość Z dbo.mg_fn_Split ('#', '#') --------------> Otrzymasz błąd tutaj.

WYBIERZ wartość Z dbo.mg_fn_Split ("2 # 1", "# ') -------------> Brak błędu tutaj.

WYBIERZ wartość Z dbo.mg_fn_Split ('2', '#') --------------------> Brak błędu tutaj.

WYBIERZ wartość Z dbo.mg_fn_Split ('', '#') ----------------------> Brak błędu tutaj.

więc w zasadzie, gdy rozdzielane dane i separator są takie same, wystąpi błąd.

Problem dotyczy tych instrukcji.

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

jeśli zmienisz 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 

to będzie wypracować w porządku .. wszystko robisz jest dodanie „1” zamiast „” ... mam nadzieję, że to pomaga.

+0

Jak już wspomniałem w poście, parametry funkcji nie zmieniają się. Zrobiłem edycję, aby dodać skrzypce, zobacz demo problemu. – pkExec

Powiązane problemy