Wie wandle ich einen String in eine Ganzzahl um und habe im Fehlerfall bei der Umwandlung mit PostgreSQL 0?

127

In PostgreSQL habe ich eine Tabelle mit einer varchar-Spalte. Die Daten sollen Ganzzahlen sein und ich brauche sie in einer Abfrage als Ganzzahl. Einige Werte sind leere Zeichenfolgen. Folgende:

SELECT myfield::integer FROM mytable

ergibt ERROR: invalid input syntax for integer: ""

Wie kann ich eine Besetzung abfragen und im Fehlerfall während der Besetzung in postgres 0 haben?

Silviot
quelle

Antworten:

160

Ich habe selbst nur mit einem ähnlichen Problem gerungen, wollte aber nicht den Overhead einer Funktion. Ich habe folgende Abfrage gestellt:

SELECT myfield::integer FROM mytable WHERE myfield ~ E'^\\d+$';

Postgres verkürzt seine Bedingungen, sodass Sie keine Nicht-Ganzzahlen erhalten sollten, die Ihre :: Ganzzahl-Besetzung treffen. Es werden auch NULL-Werte verarbeitet (sie stimmen nicht mit dem regulären Ausdruck überein).

Wenn Sie Nullen möchten, anstatt nicht auszuwählen, sollte eine CASE-Anweisung funktionieren:

SELECT CASE WHEN myfield~E'^\\d+$' THEN myfield::integer ELSE 0 END FROM mytable;
Anthony Briggs
quelle
14
Ich würde dringend empfehlen, Matthews Vorschlag zu folgen. Diese Lösung hat Probleme mit Zeichenfolgen, die wie Zahlen aussehen, aber größer sind als der Maximalwert, den Sie in eine Ganzzahl einfügen können.
Pilif
4
Ich zweite Pilif Kommentar. Dieser Maximalwert ist ein Fehler, der darauf wartet, passiert zu werden. Der Punkt, an dem kein Fehler ausgegeben wird, besteht darin, keinen Fehler auszulösen, wenn die Daten ungültig sind. Diese akzeptierte Antwort löst das NICHT. danke Matthew! Gute Arbeit!
Shawn Kovac
3
So großartig Matthews Antwort auch ist, ich brauchte nur eine schnelle und schmutzige Handhabung, um einige Daten zu überprüfen. Ich gebe auch zu, dass mein eigenes Wissen zur Definition von Funktionen in SQL derzeit fehlt. Ich war nur an Zahlen zwischen 1 und 5 Ziffern interessiert, also habe ich den regulären Ausdruck in geändert E'\\d{1,5}$'.
Bobort
3
Ja, ja, diese Lösung ist relativ schnell und schmutzig, aber in meinem Fall wusste ich, welche Daten ich hatte und dass die Tabelle relativ kurz war. Es ist viel einfacher als eine ganze Funktion zu schreiben (und zu debuggen). Das {1,5}oben angegebene Limit von @ Bobort für die Ziffern ist möglicherweise eine gute Idee, wenn Sie sich Gedanken über einen Überlauf machen. Es maskiert jedoch größere Zahlen, was beim Konvertieren einer Tabelle zu Problemen führen kann. Persönlich möchte ich lieber den Abfragefehler im Voraus haben und wissen, dass einige meiner "Ganzzahlen" verrückt sind (Sie können auch zuerst auswählen E'\\d{6,}$', um sicherzugehen).
Anthony Briggs
1
@ Anthony Briggs: Dies funktioniert nicht, wenn myfield "'" oder "," oder "." Oder' - 'enthält
Stefan Steiger
100

Sie können auch Ihre eigene Konvertierungsfunktion erstellen, innerhalb derer Sie können Ausnahmeblöcke verwenden:

CREATE OR REPLACE FUNCTION convert_to_integer(v_input text)
RETURNS INTEGER AS $$
DECLARE v_int_value INTEGER DEFAULT NULL;
BEGIN
    BEGIN
        v_int_value := v_input::INTEGER;
    EXCEPTION WHEN OTHERS THEN
        RAISE NOTICE 'Invalid integer value: "%".  Returning NULL.', v_input;
        RETURN NULL;
    END;
RETURN v_int_value;
END;
$$ LANGUAGE plpgsql;

Testen:

=# select convert_to_integer('1234');
 convert_to_integer 
--------------------
               1234
(1 row)

=# select convert_to_integer('');
NOTICE:  Invalid integer value: "".  Returning NULL.
 convert_to_integer 
--------------------

(1 row)

=# select convert_to_integer('chicken');
NOTICE:  Invalid integer value: "chicken".  Returning NULL.
 convert_to_integer 
--------------------

(1 row)
Matthew Wood
quelle
8
Im Gegensatz zur akzeptierten Antwort ist diese Lösung hier korrekter, da sie ebenso gut mit Zahlen umgehen kann, die zu groß sind, um in eine Ganzzahl zu passen, und wahrscheinlich auch schneller ist, da im allgemeinen Fall keine Validierungsarbeit ausgeführt wird (= gültige Zeichenfolgen) )
Pilif
Wie würden Sie werfen Zeichenfolge in integer auf bestimmte Felder mit Ihrer Funktion , während in in INSERTAussage?
Sk
27

Ich hatte das gleiche Bedürfnis und fand, dass dies für mich gut funktioniert (postgres 8.4):

CAST((COALESCE(myfield,'0')) AS INTEGER)

Einige Testfälle zur Demonstration:

db=> select CAST((COALESCE(NULL,'0')) AS INTEGER);
 int4
------
    0
(1 row)

db=> select CAST((COALESCE('','0')) AS INTEGER);
 int4
------
    0
(1 row)

db=> select CAST((COALESCE('4','0')) AS INTEGER);
 int4
------
    4
(1 row)

db=> select CAST((COALESCE('bad','0')) AS INTEGER);
ERROR:  invalid input syntax for integer: "bad"

Wenn Sie die Möglichkeit behandeln müssen, dass das Feld nicht numerischen Text enthält (z. B. "100bad"), können Sie mit regexp_replace nicht numerische Zeichen vor der Umwandlung entfernen.

CAST(REGEXP_REPLACE(COALESCE(myfield,'0'), '[^0-9]+', '', 'g') AS INTEGER)

Dann geben Text- / Varchar-Werte wie "b3ad5" auch Zahlen an

db=> select CAST(REGEXP_REPLACE(COALESCE('b3ad5','0'), '[^0-9]+', '', 'g') AS INTEGER);
 regexp_replace
----------------
             35
(1 row)

Um Chris Cogdons Besorgnis über die Lösung anzusprechen, die nicht für alle Fälle 0 ergibt, einschließlich eines Falls wie "schlecht" (überhaupt keine Ziffern), habe ich diese angepasste Aussage gemacht:

CAST((COALESCE(NULLIF(REGEXP_REPLACE(myfield, '[^0-9]+', '', 'g'), ''), '0')) AS INTEGER);

Es funktioniert ähnlich wie die einfacheren Lösungen, außer dass 0 angegeben wird, wenn der zu konvertierende Wert nur aus nichtstelligen Zeichen besteht, z. B. "schlecht":

db=> select CAST((COALESCE(NULLIF(REGEXP_REPLACE('no longer bad!', '[^0-9]+', '', 'g'), ''), '0')) AS INTEGER);
     coalesce
----------
        0
(1 row)
Ghbarratt
quelle
Warum brauchst du die '0' ||? ? Aus den Dokumenten: "Die COALESCE-Funktion gibt das erste ihrer Argumente zurück, das nicht null ist." Wenn Sie also null als Wert haben, wird Coalesce ihn los.
Amala
@Amala Stimmt. Schöner Fang. Bearbeitet.
Ghbarratt
1
Die Lösung funktioniert nur, wenn die Eingabe eine Ganzzahl oder NULL ist. Die Frage lautete, jede Art von Eingabe zu konvertieren und 0 zu verwenden, wenn sie nicht konvertierbar ist.
Chris Cogdon
@ChrisCogdon Ich habe der Lösung hinzugefügt, um Ihr Problem zu lösen, dass nicht immer Null angegeben wird, wenn der zu konvertierende Wert "nicht konvertierbar" ist. Diese optimierte Version der Lösung gibt 0 zurück, wenn eine Zeichenfolge ohne Ziffern als zu konvertierender Wert angegeben wird.
Ghbarratt
22

Dies mag ein Hack sein, aber in unserem Fall hat es die Arbeit erledigt:

(0 || myfield)::integer

Erklärung (getestet auf Postgres 8.4):

Der oben erwähnte Ausdruck ergibt NULLfür NULL-Werte in myfieldund 0für leere Zeichenfolgen (Dieses genaue Verhalten kann zu Ihrem Anwendungsfall passen oder nicht).

SELECT id, (0 || values)::integer from test_table ORDER BY id

Testdaten:

CREATE TABLE test_table
(
  id integer NOT NULL,
  description character varying,
  "values" character varying,
  CONSTRAINT id PRIMARY KEY (id)
)

-- Insert Test Data
INSERT INTO test_table VALUES (1, 'null', NULL);
INSERT INTO test_table VALUES (2, 'empty string', '');
INSERT INTO test_table VALUES (3, 'one', '1');

Die Abfrage ergibt das folgende Ergebnis:

 ---------------------
 |1|null        |NULL|
 |2|empty string|0   |
 |3|one         |1   |
 ---------------------

Während nur values::integerdie Auswahl eine Fehlermeldung ergibt.

Hoffe das hilft.

Matt
quelle
3

SELECT CASE WHEN myfield="" THEN 0 ELSE myfield::integer END FROM mytable

Ich habe noch nie mit PostgreSQL gearbeitet, aber ich habe das Handbuch auf die korrekte Syntax von IF-Anweisungen in SELECT-Abfragen überprüft .

Jan Hančič
quelle
Das funktioniert für den Tisch so wie er jetzt ist. Ich habe ein bisschen Angst, dass es in Zukunft nicht numerische Werte enthalten könnte. Ich hätte eine Try / Catch-ähnliche Lösung vorgezogen, aber das macht den Trick. Vielen Dank.
Silviot
Vielleicht könnten Sie reguläre Ausdrücke postgresql.org/docs/8.4/interactive/functions-matching.html verwenden, aber das könnte kostspielig sein. Akzeptieren Sie auch die Antwort, wenn es die Lösung ist :)
Jan Hančič
3

@ Matthews Antwort ist gut. Aber es kann einfacher und schneller sein. In der Frage wird gefragt, ob leere Zeichenfolgen ( '') in 0andere "ungültige Eingabesyntax" oder "außerhalb des Bereichs" eingegeben werden sollen:

CREATE OR REPLACE FUNCTION convert_to_int(text)
  RETURNS int AS
$func$
BEGIN
   IF $1 = '' THEN  -- special case for empty string like requested
      RETURN 0;
   ELSE
      RETURN $1::int;
   END IF;

EXCEPTION WHEN OTHERS THEN
   RETURN NULL;  -- NULL for other invalid input

END
$func$  LANGUAGE plpgsql IMMUTABLE;

Dies gibt 0für eine leere Zeichenfolge und NULLfür jede andere ungültige Eingabe zurück.
Es kann leicht für jede Datentypkonvertierung angepasst werden .

Das Eingeben eines Ausnahmeblocks ist wesentlich teurer. Wenn leere Zeichenfolgen häufig vorkommen, ist es sinnvoll, diesen Fall zu erfassen, bevor eine Ausnahme ausgelöst wird.
Wenn leere Zeichenfolgen sehr selten sind, lohnt es sich, den Test in die Ausnahmeklausel zu verschieben.

Erwin Brandstetter
quelle
1
CREATE OR REPLACE FUNCTION parse_int(s TEXT) RETURNS INT AS $$
BEGIN
  RETURN regexp_replace(('0' || s), '[^\d]', '', 'g')::INT;
END;
$$ LANGUAGE plpgsql;

Diese Funktion wird immer zurückgegeben, 0wenn die Eingabezeichenfolge keine Ziffern enthält.

SELECT parse_int('test12_3test');

wird zurückkehren 123

Oleg Mikhailov
quelle
Haben Sie Leistungstests für die Funktion "Regex vs. String" durchgeführt? Wie geht das mit Nullen um? Würde es wie erwartet 0 oder NULL zurückgeben? Vielen Dank!
Vol7ron
1

Ich fand den folgenden Code einfach und funktionierend. Die ursprüngliche Antwort finden Sie hier https://www.postgresql.org/message-id/[email protected]

prova=> create table test(t text, i integer);
CREATE

prova=> insert into test values('123',123);
INSERT 64579 1

prova=> select cast(i as text),cast(t as int)from test;
text|int4
----+----
123| 123
(1 row)

ich hoffe es hilft

Ashish Rana
quelle
1

SUBSTRING kann in einigen Fällen hilfreich sein. Sie können die Größe des int begrenzen.

SELECT CAST(SUBSTRING('X12312333333333', '([\d]{1,9})') AS integer);
veraltet
quelle
0

Wenn die Daten Ganzzahlen sein sollen und Sie diese Werte nur als Ganzzahlen benötigen, warum gehen Sie nicht die ganze Meile und konvertieren die Spalte in eine Ganzzahlspalte?

Dann können Sie diese Konvertierung von unzulässigen Werten in Nullen nur einmal an der Stelle des Systems durchführen, an der die Daten in die Tabelle eingefügt werden.

Mit der obigen Konvertierung zwingen Sie Postgres, diese Werte für jede einzelne Zeile in jeder Abfrage für diese Tabelle immer wieder zu konvertieren. Dies kann die Leistung erheblich beeinträchtigen, wenn Sie viele Abfragen für diese Spalte in dieser Tabelle durchführen.

Bandit
quelle
Im Prinzip haben Sie Recht, aber in diesem speziellen Szenario muss ich eine einzelne langsame Abfrage in einer Anwendung optimieren. Ich weiß nicht, wie der Code für die Dateneingabe funktioniert. Ich will es nicht anfassen. Bisher funktioniert meine umgeschriebene Abfrage, aber ich möchte, dass sie nicht in unvorhergesehenen Fällen unterbrochen wird. Eine Neuarchitektur der Anwendung ist keine Option, auch wenn dies am sinnvollsten erscheint.
Silviot
0

Die folgende Funktion funktioniert

  • Verwenden Sie einen Standardwert ( error_result) für nicht umsetzbare Ergebnisse, z. B. abcoder999999999999999999999999999999999999999999
  • hält nullwienull
  • schneidet Leerzeichen und andere Leerzeichen in der Eingabe ab
  • Werte, die als gültig gewertet werden, bigintswerden verglichen, lower_boundum beispielsweise nur positive Werte zu erzwingen
CREATE OR REPLACE FUNCTION cast_to_bigint(text) 
RETURNS BIGINT AS $$
DECLARE big_int_value BIGINT DEFAULT NULL;
DECLARE error_result  BIGINT DEFAULT -1;
DECLARE lower_bound   BIGINT DEFAULT 0;
BEGIN
    BEGIN
        big_int_value := CASE WHEN $1 IS NOT NULL THEN GREATEST(TRIM($1)::BIGINT, lower_bound) END;
    EXCEPTION WHEN OTHERS THEN
        big_int_value := error_result;
    END;
RETURN big_int_value;
END;
Th 00 mÄ s
quelle
-1

Ich habe auch das gleiche Bedürfnis, aber das funktioniert mit JPA 2.0 und Hibernate 5.0.2:

SELECT p FROM MatchProfile p WHERE CONCAT(p.id, '') = :keyword

Wirkt Wunder. Ich denke, es funktioniert auch mit LIKE.

Hendy Irawan
quelle
-3

Dies sollte auch die Arbeit erledigen, aber dies ist über SQL und nicht postgres-spezifisch.

select avg(cast(mynumber as numeric)) from my table
Ronak
quelle