2013-04-24 12 views
25

Potrzebuję określić, czy dany ciąg może być interpretowany jako liczba (liczba całkowita lub zmiennoprzecinkowa) w instrukcji SQL. Jak w poniższym przykładzie:isnumeric() z PostgreSQL

SELECT AVG(CASE WHEN x ~ '^[0-9]*.?[0-9]*$' THEN x::float ELSE NULL END) FROM test 

stwierdziliśmy, że Postgres' pattern matching mogą być wykorzystane do tego celu. I tak zaadaptowałem oświadczenie podane w this place, aby włączyć liczby zmiennoprzecinkowe. To jest mój kod:

WITH test(x) AS (
    VALUES (''), ('.'), ('.0'), ('0.'), ('0'), ('1'), ('123'), 
    ('123.456'), ('abc'), ('1..2'), ('1.2.3.4')) 

SELECT x 
    , x ~ '^[0-9]*.?[0-9]*$' AS isnumeric 
FROM test; 

Wyjście:

x | isnumeric 
---------+----------- 
     | t 
.  | t 
.0  | t 
0.  | t 
0  | t 
1  | t 
123  | t 
123.456 | t 
abc  | f 
1..2 | f 
1.2.3.4 | f 
(11 rows) 

Jak widać, pierwsze dwa egzemplarze (pusty ciąg '' i jedynym okresem '.') zostały błędnie sklasyfikowane jako typ numeryczny (których nie są). W tej chwili nie mogę się do tego zbliżyć. Każda pomoc doceniona!


Aktualizacja podstawie this answer (i uwagach), I dostosowany wzorzec do:

WITH test(x) AS (
    VALUES (''), ('.'), ('.0'), ('0.'), ('0'), ('1'), ('123'), 
    ('123.456'), ('abc'), ('1..2'), ('1.2.3.4'), ('1x234'), ('1.234e-5')) 

SELECT x 
    , x ~ '^([0-9]+[.]?[0-9]*|[.][0-9]+)$' AS isnumeric 
FROM test; 

co daje:

 x | isnumeric 
----------+----------- 
      | f 
.  | f 
.0  | t 
0.  | t 
0  | t 
1  | t 
123  | t 
123.456 | t 
abc  | f 
1..2  | f 
1.2.3.4 | f 
1x234 | f 
1.234e-5 | f 
(13 rows) 

Są jeszcze pewne problemy z naukowego notacja i liczby ujemne, jak widzę teraz.

+1

Czy musisz martwić się liczbami ujemnymi? A co z notacją naukową? –

+0

@muistooshort dzięki jeszcze raz, szczególnie zainteresował mnie ten rodzaj danych wejściowych. To podejście do dopasowywania wzorców nie jest tak proste, jak się spodziewałem. – moooeeeep

+1

Wyrażenie dla liczb ujemnych to po prostu: ''^ -? ([0-9] + [.]? [0-9] * | [.] [0-9] +) $'' poprawne? –

Odpowiedz

58

Jak można zauważyć, metoda oparta na regex jest prawie niemożliwa do wykonania poprawnie. Na przykład twój test mówi, że 1.234e-5 nie jest prawidłową liczbą, kiedy tak naprawdę jest. Poza tym pominąłeś liczby ujemne. Co, jeśli coś wygląda jak liczba, ale kiedy spróbujesz go zapisać, spowoduje to przepełnienie?

Zamiast tego, polecam, aby utworzyć funkcję, która stara się faktycznie oddane do NUMERIC (lub FLOAT jeśli zadanie wymaga) i zwraca TRUE lub FALSE w zależności od tego, czy to odlew był udany, czy nie.

CREATE OR REPLACE FUNCTION isnumeric(text) RETURNS BOOLEAN AS $$ 
DECLARE x NUMERIC; 
BEGIN 
    x = $1::NUMERIC; 
    RETURN TRUE; 
EXCEPTION WHEN others THEN 
    RETURN FALSE; 
END; 
$$ 
STRICT 
LANGUAGE plpgsql IMMUTABLE; 

Wywołanie tej funkcji na dysku dane zostaną następujące wyniki::

Kod ten pełni funkcję ISNUMERIC() symulować

WITH test(x) AS (VALUES (''), ('.'), ('.0'), ('0.'), ('0'), ('1'), ('123'), 
    ('123.456'), ('abc'), ('1..2'), ('1.2.3.4'), ('1x234'), ('1.234e-5')) 
SELECT x, isnumeric(x) FROM test; 

    x  | isnumeric 
----------+----------- 
      | f 
.  | f 
.0  | t 
0.  | t 
0  | t 
1  | t 
123  | t 
123.456 | t 
abc  | f 
1..2  | f 
1.2.3.4 | f 
1x234 | f 
1.234e-5 | t 
(13 rows) 

Nie tylko jest to bardziej poprawne i łatwiejsze do odczytania, to będzie również działają szybciej, jeśli dane faktycznie są liczbą.

+0

1.234d + 5 to także "poprawna" liczba. Wpadłem na ten format, robiąc kilka magazynów danych kilka lat temu. Było to na wyjściu starego programu Fortran; reprezentuje wartość pływającą o podwójnej precyzji. Jakiekolwiek oprogramowanie biurowe zaimportowały je poprawnie. –

+3

Cóż, moim celem jest to, że jeśli próbujesz powiedzieć, czy dany ciąg przechowywany w bazie danych Postgres jest prawidłową liczbą, jedyną rozsądną metodą jest zapytanie serwera Postgres o to, co o nim myśli. Jeśli mówi, że '1.234d + 5' nie jest prawidłową liczbą, to naprawdę nie można go rzucić na prawidłową liczbę, używając Postgres. – mvp

+0

Zmodyfikuję go trochę, aby obsłużyć NULL: 'FUNCTION isnumeric (anyelement)' do podjęcia jakiegokolwiek argumentu. Następnie zwraca wartość dynamiczną dla pomyślnych operacji: 'DECLARE x NUMERIC; wyniki BOOLEAN; 'I ustaw tę wartość w bloku BEGIN:' results = CASE WHEN $ 1 JEST NIŻEJ PONIŻEJ NULL ELSE TRUE END; x = 1 $ :: NUMERYCZNY; RETURN results; --- --- Oznacza, że ​​isnumeric (NULL) zwróci wartość NULL, ponieważ wartości NULL nie mają żadnej wartości. – vol7ron

10

Twoim problemem są dwa 0 lub więcej elementów [0-9] po każdej stronie przecinka dziesiętnego. Trzeba użyć logiczną OR | w linii numer identyfikacyjny:

~'^([0-9]+\.?[0-9]*|\.[0-9]+)$' 

To wykluczy separator dziesiętny sam jako ważnego numeru.

+4

Brakuje ci niektórych ucieczek na '' ', które będą pasować zarówno do' '1x1'' i' 'x1''. –

+0

Tak, jestem przyzwyczajony do Oracle i Java, proszę upewnij się, że twoje eskapady są poprawne, | powinien być prawidłowym operatorem POSIX OR i. powinno być PERIOD, a nie operator wszystkich znaków POSIX. –