Microsoft SQL Server a ce que je considère comme une fonction remarquablement sensible, try_cast()
qui renvoie un null
si le transtypage échoue, plutôt que de déclencher une erreur.
Cela permet ensuite d'utiliser une expression CASE
ou coalesce
pour se replier. Par exemple:
SELECT coalesce(try_cast(data as int),0);
La question est, est-ce que PostgreSQL a quelque chose de similaire?
La question est posée pour combler certaines lacunes dans mes connaissances, mais il y a aussi le principe général selon lequel certains préfèrent une réaction moins dramatique à certaines erreurs utilisateur. Renvoyer un null
est plus facilement pris dans sa foulée en SQL qu'une erreur. Par exemple SELECT * FROM data WHERE try_cast(value) IS NOT NULL;
. D'après mon expérience, les erreurs des utilisateurs sont parfois mieux gérées s'il existe un plan B.
Si la conversion à partir d'un type spécifique vers un autre type spécifique suffit, vous pouvez le faire avec une fonction PL/pgSQL:
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;
Alors
select try_cast_int('42'), try_cast_int('foo', -1), try_cast_int('bar')
Retour
try_cast_int | try_cast_int | try_cast_int
-------------+--------------+-------------
42 | -1 |
S'il s'agit uniquement de nombres, une autre approche consisterait à utiliser une expression régulière pour vérifier si la chaîne d'entrée est un nombre valide. Ce serait probablement plus rapide que de détecter des exceptions lorsque vous attendez de nombreuses valeurs incorrectes.
Il est difficile d'envelopper quelque chose comme TRY_CAST
De SQL Server dans une fonction PostgreSQL générique. L'entrée et la sortie peuvent être de n'importe quel type de données, mais SQL est strictement typé et les fonctions Postgres exigent que les types de paramètres et de retour soient déclarés au moment de la création.
Postgres a le concept de types polymorphes , mais les déclarations de fonction acceptent au plus un type polymorphe. Le manuel:
Les arguments et les résultats polymorphes sont liés les uns aux autres et sont résolus en un type de données spécifique lorsqu'une requête appelant une fonction polymorphe est analysée. Chaque position (argument ou valeur de retour) déclarée comme
anyelement
est autorisée à avoir n'importe quel type de données réel spécifique, mais dans tout appel donné, elles doivent toutes être les même type réel.
CAST ( expression AS type )
semblerait être une exception à cette règle, prenant n'importe quel type et retournant n'importe quel (autre) type. Mais cast()
seulement ressemble à une fonction alors que c'est un élément de syntaxe SQL SQL sous le capot. Le manuel:
[...] Lorsqu'une des deux syntaxes de transtypage standard est utilisée pour effectuer une conversion au moment de l'exécution, elle invoque en interne une fonction enregistrée pour effectuer la conversion.
Il existe une fonction distincte pour chaque combinaison de type d'entrée et de sortie. (Vous pouvez créer le vôtre avec CREATE CAST
...)
Mon compromis est d'utiliser text
comme entrée car tout type peut être converti en text
. La conversion supplémentaire en text
signifie un coût supplémentaire (mais pas beaucoup). Le polymorphisme ajoute également un peu de surcharge. Mais les parties modérément chères sont le SQL dynamique dont nous avons besoin, la concaténation de chaînes impliquée et, surtout, la gestion des exceptions.
Cela dit, cette petite fonction peut être utilisée pour toute combinaison de types y compris les types de tableau. (Mais les modificateurs de type comme dans varchar(20)
sont perdus):
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;
Le paramètre INOUT
_out
A deux fonctions:
Vous ne l'appelleriez pas comme dans votre exemple:
SELECT coalesce(try_cast(data as int),0);
.. où COALESCE
élimine également les valeurs NULL authentiques de la source (!!), probablement pas comme prévu. Mais simplement:
SELECT try_cast(data, 0);
.. qui retourne NULL
sur NULL
entrée, ou 0
sur entrée invalide.
La syntaxe courte fonctionne lorsque data
est un type de caractère (comme text
ou varchar
) et parce que 0
Est un littéral numérique qui est implicitement tapé comme integer
. Dans d'autres cas, vous devrez peut-être être plus explicite:
Les littéraux de chaîne non typés fonctionnent hors de la boîte:
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
Valeurs typées qui ont un cast implicite enregistré en text
travailler hors de la boîte aussi:
SELECT try_cast(name 'foobar', 'foo'::varchar);
SELECT try_cast(my_varchar_column, NULL::numeric);
Liste complète des types de données avec transtypage implicite enregistré en text
:
SELECT castsource::regtype
FROM pg_cast
WHERE casttarget = 'text'::regtype
AND castcontext = 'i';
Tous les autres types d'entrée nécessitent une conversion explicite en text
:
SELECT try_cast((inet '192.168.100.128/20')::text, NULL::cidr);
SELECT try_cast(my_text_array_column::text, NULL::int[]));
Nous pourrions facilement faire fonctionner le corps de la fonction pour tout type, mais la résolution du type de fonction échoue. En relation:
Voici un essai générique, probablement très lent.
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";
Cela n'acceptera pas les types comme varchar(20)
(bien que nous puissions ajouter un autre paramètre pour accepter "typemod" comme 20
.
cette fonction renvoie du texte car les fonctions postgreqsl doivent avoir un type de retour fixe. vous pouvez donc avoir besoin d'un cast explicite en dehors de la fonction pour contraindre le résultat au type souhaité.