Jaka jest logika ISNUMERIC dla niektórych znaków specjalnych?

14

ISNUMERICFunkcja ma pewne nieoczekiwane zachowanie. Dokumentacja MSDN mówi:

ISNUMERICzwraca 1, gdy wyrażenie wejściowe zwraca poprawny numeryczny typ danych; w przeciwnym razie zwraca 0. Prawidłowe typy danych numerycznych obejmują: int, bigint, smallint, tinyint, dziesiętny, numeryczny, pieniądze, smallmoney, zmiennoprzecinkowy, rzeczywisty .

Ma też przypis:

ISNUMERICzwraca 1 dla niektórych znaków, które nie są liczbami, takich jak plus (+), minus (-) oraz prawidłowe symbole walut, takie jak znak dolara ($). Aby uzyskać pełną listę symboli walut, zobacz pieniądze i smallmoney (Transact-SQL) .

Porządku, tak +, -oraz oczekuje się, że wymienione symbole waluty należy uznać numeryczne. Jak na razie dobrze.

Teraz część nieparzysta. Po pierwsze, niektóre symbole walut z powiązanego artykułu nie są numeryczne, w tym:

  • Znak euro, szesnastkowy 20A0:
  • Znak Naira, hex 20A6:
  • Znak rial, hex FDFC:

To dziwne i nie mogę się dowiedzieć, dlaczego? Czy ta wersja lub środowisko jest zależne?

Jednak sprawy stają się coraz dziwniejsze. Oto kilka innych, których nie potrafię wyjaśnić:

  • /nie jest numeryczny, ale \jest ( Huh ?! )
  • REPLICATE(N'9', 308)jest liczbowy, ale REPLICATE(N'9', 309)nie jest

Pierwszym i najbardziej podstawowym pytaniem jest: co wyjaśnia powyższe przypadki? Co ważniejsze: jaka jest logikaISNUMERIC , abym mógł samodzielnie wyjaśnić / przewidzieć wszystkie przypadki?

Oto dobry sposób na odtworzenie rzeczy:

DECLARE @tbl TABLE(txt NVARCHAR(1000));

INSERT INTO @tbl (txt) 
VALUES (N''), (N' '), (N'€'), (N'$'), (N'$$'), 
       (NCHAR(8356)), (NCHAR(8352)), (NCHAR(8358)), (NCHAR(65020)), 
       (N'+'), (N'-'), (N'/'), (N'\'), (N'_'), (N'e'), (N'1e'), (N'e1'), (N'1e1'), 
       (N'1'), (N'-1'), (N'+1'), (N'1+1'), (N''), (N'🄂'), (N'¹'), (N''), (N'½'), 
       (N'🎅'), (REPLICATE(N'9', 307)), (REPLICATE(N'9', 308)), (REPLICATE(N'9', 309)), 
       (REPLICATE(N'9', 310));

SELECT  UNICODE(LEFT(txt, 1)) AS FirstCharAsInt,
        LEN(txt) AS TxtLength,
        txt AS Txt,
        ISNUMERIC(txt) AS [ISNUMERIC]
FROM    @tbl;

Gdy uruchomię to na moim lokalnym Sql Server 2012 box, otrzymuję następujące wyniki:

FirstCharAsInt   TxtLength   Txt        ISNUMERIC
---------------  ----------  ---------  ----------
NULL             0                      0
32               0                      0
8364             1           €          1
36               1           $          1
36               2           $$         0
8356             1           ₤          1
8352             1           ₠          0  --??
8358             1           ₦          0  --??
65020            1           ﷼‎          0  --??
43               1           +          1
45               1           -          1
47               1           /          0
92               1           \          1  --??
95               1           _          0
101              1           e          0
49               2           1e         0
101              2           e1         0
49               3           1e1        1
49               1           1          1
45               2           -1         1
43               2           +1         1
49               3           1+1        0
9352             1           ⒈         0
55356            2           🄂          0
185              1           ¹          0
9312             1           ①          0
189              1           ½          0
55356            2           🎅         0
57               307        /*...*/     1
57               308        /*...*/     1  --??
57               309        /*...*/     0  --??
57               310        /*...*/     0
Jeroen
źródło
Jedyne, które wydają mi się niepoprawne, to to, że fałszywie zgłasza 0pięć wartości, które faktycznie pasują money. Pozostałe wydają się dokładne. FIDDLE SQL
Martin Smith
Chociaż NCHAR(0) - NCHAR(65535)widzę 112 rozbieżności. W tym postacie, ₁,₂,₃,4,5,6,7,8,9które wyglądają na liczbowe, ale nie rzucają na mnie niczego. Fiddle
Martin Smith

Odpowiedzi:

13

Szczegółowe zachowania ISNUMERICnie są udokumentowane i prawdopodobnie nie są w pełni znane nikomu bez dostępu do kodu źródłowego. To powiedziawszy, być może interpretacja zależy od kategoryzacji Unicode (numerycznej lub nie). Podobnie dziwne przypadki, o których wspominasz, mogą być błędami, które zostały zachowane w celu zachowania kompatybilności wstecznej. Tak, wiem, że to brzmi szalenie, ale tak się dzieje.

Ponieważ używasz programu SQL Server 2012, nie musisz go używać ISNUMERIC. Zamiast tego użyj TRY_CONVERTlub synonimu, TRY_CASTaby sprawdzić, czy ciąg znaków można przekonwertować na dany typ. Tam, gdzie zapewniają one odpowiednią funkcjonalność, są one preferowane TRY_PARSE, ponieważ te ostatnie wymagają droższego przetwarzania poprzez integrację CLR.

Paul White 9
źródło
2
I prawdopodobnie nie do końca znany wielu osobom z dostępem do kodu źródłowego. :-) Chciałbym móc jeszcze raz dać +1 za drugi akapit. ISNUMERIC () jest w dużej mierze bezużyteczne, ponieważ jego celem jest ustalenie, czy coś można przekonwertować na co najmniej jeden typ liczbowy; jest oczywiście o wiele ważniejsze, aby wiedzieć, że można przekonwertować na pojedynczy, konkretny typ liczbowy.
Aaron Bertrand
1
@AaronBertrand Wydaje się, że istnieje dość znaczna liczba przypadków, w których nie spełnia nawet tego zamiaru.
Martin Smith
9

Ukośnik odwrotny ASCII (punkt kodowy 5C) ma ten sam punkt kodowy co znak jena (¥) w kodowaniu Shift-JIS używanym przez japońską wersję systemu Windows, a znak wygranej (₩) w koreańskim EUC-KR. Dlatego jest bardzo prawdopodobne, że jest to kontynuacja motywu znaku waluty.

użytkownik47620
źródło
Ach, to ciekawa teoria. Chodzi o to money, że również rzuca.
Martin Smith
@Jeroen - Jest na Wikipedii FWIW
Martin Smith
3
@Jeroen Obawiam się, że nie. Przełącz C:¥Program Files¥
starszą