it-swarm.com.de

PostgreSQL-Alternative zur `try_cast'-Funktion von SQL Server

Microsoft SQL Server hat eine meiner Meinung nach bemerkenswert sinnvolle Funktion, try_cast(), die ein null zurückgibt, wenn die Umwandlung nicht erfolgreich ist, anstatt einen Fehler auszulösen.

Dies ermöglicht es, dann einen CASE Ausdruck oder einen coalesce zu verwenden, um darauf zurückzugreifen. Zum Beispiel:

SELECT coalesce(try_cast(data as int),0);

Die Frage ist, hat PostgreSQL etwas Ähnliches?

Die Frage wird gestellt, um einige Wissenslücken zu schließen, aber es gibt auch das allgemeine Prinzip, dass einige eine weniger dramatische Reaktion auf einige Benutzerfehler bevorzugen. Das Zurückgeben eines null ist in SQL einfacher als ein Fehler. Zum Beispiel SELECT * FROM data WHERE try_cast(value) IS NOT NULL;. Nach meiner Erfahrung werden Benutzerfehler manchmal besser behandelt, wenn ein Plan B vorliegt.

6
Manngo

Wenn das Casting von einem bestimmten Typ bis einem anderen bestimmten Typ ausreicht, können Sie dies mit einer PL/pgSQL-Funktion tun:

create function try_cast_int(p_in text, p_default int default null)
   returns int
as
$$
begin
  begin
    return $1::int;
  exception 
    when others then
       return p_default;
  end;
end;
$$
language plpgsql;

Dann

select try_cast_int('42'), try_cast_int('foo', -1), try_cast_int('bar')

Kehrt zurück

try_cast_int | try_cast_int | try_cast_int
-------------+--------------+-------------
          42 |           -1 |             

Wenn dies nur für Zahlen gilt, besteht ein anderer Ansatz darin, einen regulären Ausdruck zu verwenden, um zu überprüfen, ob die Eingabezeichenfolge eine gültige Zahl ist. Das wäre wahrscheinlich schneller als das Abfangen von Ausnahmen, wenn Sie viele falsche Werte erwarten.

Begründung

Es ist schwierig, so etwas wie TRY_CAST von SQL Server in eine generische PostgreSQL-Funktion zu packen. Eingabe und Ausgabe können beliebige Datentypen sein, SQL ist jedoch streng typisiert, und Postgres-Funktionen erfordern, dass Parameter- und Rückgabetypen bei der Erstellung deklariert werden.

Postgres hat das Konzept von polymorphen Typen , aber Funktionsdeklarationen akzeptieren höchstens eins polymorpher Typ. Das Handbuch:

Polymorphe Argumente und Ergebnisse sind miteinander verknüpft und werden in einen bestimmten Datentyp aufgelöst, wenn eine Abfrage analysiert wird, die eine polymorphe Funktion aufruft. Jede Position (entweder Argument oder Rückgabewert), die als anyelement deklariert ist, darf einen bestimmten tatsächlichen Datentyp haben, aber bei jedem Aufruf müssen sie alle sein gleich tatsächlicher Typ.

CAST ( expression AS type ) scheint eine Ausnahme von dieser Regel zu sein, die einen beliebigen Typ annimmt und einen beliebigen (anderen) Typ zurückgibt. Aber cast() only sieht aus wie eine Funktion, während es sich um ein SQL-Syntaxelement handelt unter der Haube. Das Handbuch:

[...] Wenn eine der beiden Standard-Cast-Syntaxen zur Durchführung einer Laufzeitkonvertierung verwendet wird, wird intern eine registrierte Funktion aufgerufen, um die Konvertierung durchzuführen.

Für jede Kombination aus Eingabe- und Ausgabetyp gibt es eine separate Funktion. (Sie können Ihre eigenen mit CREATE CAST ...) erstellen.

Funktion

Mein Kompromiss besteht darin, text als Eingabe zu verwenden, da der Typ any In text umgewandelt werden kann. Die zusätzliche Besetzung von text bedeutet zusätzliche Kosten (wenn auch nicht viel). Polymorphismus erhöht auch den Overhead. Die mäßig teuren Teile sind jedoch das dynamische SQL, das wir benötigen, die damit verbundene Verkettung von Zeichenfolgen und vor allem die Ausnahmebehandlung.

Diese kleine Funktion kann jedoch für any Kombination von Typen einschließlich Array-Typen verwendet werden. (Aber Typmodifikatoren wie in varchar(20) gehen verloren):

CREATE OR REPLACE FUNCTION try_cast(_in text, INOUT _out ANYELEMENT) AS
$func$
BEGIN
   EXECUTE format('SELECT %L::%s', $1, pg_typeof(_out))
   INTO  _out;
EXCEPTION WHEN others THEN
   -- do nothing: _out already carries default
END
$func$  LANGUAGE plpgsql;

Der Parameter INOUT_out Dient zwei Zwecken:

  1. deklariert den polymorphen Typ
  2. trägt auch den Standardwert für Fehlerfälle

Sie würden es nicht so nennen wie in Ihrem Beispiel:

SELECT coalesce(try_cast(data as int),0);

.. wobei COALESCE auch echte NULL-Werte aus der Quelle (!!) entfernt, wahrscheinlich nicht wie beabsichtigt. Aber einfach:

SELECT try_cast(data, 0);

.. was NULL bei NULL Eingabe oder 0 bei ungültiger Eingabe zurückgibt.

Die kurze Syntax funktioniert, während data ein Zeichentyp ist (wie text oder varchar) und weil 0 Ein numerisches Literal ist, das implizit als integer. In anderen Fällen müssen Sie möglicherweise expliziter sein:

Beispielaufrufe

Nicht typisierte String-Literale funktionieren sofort:

SELECT try_cast('foo', NULL::varchar);
SELECT try_cast('2018-01-41', NULL::date);   -- returns NULL
SELECT try_cast('2018-01-41', CURRENT_DATE); -- returns current date

Typisierte Werte mit einer registrierten impliziten Umwandlung in text auch sofort einsatzbereit:

SELECT try_cast(name 'foobar', 'foo'::varchar);
SELECT try_cast(my_varchar_column, NULL::numeric);

Umfassende Liste von Datentypen mit registrierter impliziter Umwandlung in text:

SELECT castsource::regtype
FROM   pg_cast
WHERE  casttarget = 'text'::regtype
AND    castcontext = 'i';

Alle anderen Eingabetypen erfordern eine explizite Umwandlung in text:

SELECT try_cast((inet '192.168.100.128/20')::text, NULL::cidr);
SELECT try_cast(my_text_array_column::text, NULL::int[]));

Wir könnten den Funktionskörper leicht für jeden Typ zum Laufen bringen, aber die Auflösung des Funktionstyps schlägt fehl. Verbunden:

7

Hier ist ein allgemeiner Try-Cast, wahrscheinlich sehr langsam.

CREATE OR REPLACE FUNCTION try_cast(p_in text, type regtype, out result text )
RETURNS text AS $$
  BEGIN
    EXECUTE FORMAT('SELECT %L::%s;', $1, $2)
      INTO result;
exception 
    WHEN others THEN result = null;
  END;
$$ LANGUAGE plpgsql;

 SELECT try_cast('2.2','int')::int as "2.2"
   ,try_cast('today','int')::int as "today"
   ,try_cast('222','int')::int as "222";

 SELECT try_cast('2.2','date')::date as "2.2"
   ,try_cast('today','date')::date as "today"
   ,try_cast('222','date')::date as "222";

 SELECT try_cast('2.2','float')::float as "2.2"
   ,try_cast('today','float')::float as "today"
   ,try_cast('222','float')::float as "222";

Dies akzeptiert keine Typen wie varchar(20) (obwohl wir einen weiteren Parameter hinzufügen könnten, um "typemod" wie 20 Zu akzeptieren.

diese Funktion gibt Text zurück, da postgreqsl-Funktionen einen festen Rückgabetyp haben müssen. Daher benötigen Sie möglicherweise eine explizite Umwandlung außerhalb der Funktion, um das Ergebnis auf den gewünschten Typ zu zwingen.

1
Jasen