web-dev-qa-db-ja.com

SQL Serverの `try_cast`関数に代わるPostgreSQL

Microsoft SQL Serverには、非常に賢明な関数であるtry_cast()があり、エラーが発生するのではなく、キャストが失敗した場合にnullを返します。

これにより、CASE式またはcoalesceを使用してフォールバックできます。例えば:

_SELECT coalesce(try_cast(data as int),0);
_

問題は、PostgreSQLに同様の機能があるかどうかです。

質問には私の知識のいくつかのギャップを埋めるように求められますが、一部のユーザーエラーに対して劇的ではない反応を好む人もいるという一般原則もあります。 nullを返すことは、エラーよりもSQLのストライドで行う方が簡単です。たとえば、SELECT * FROM data WHERE try_cast(value) IS NOT NULL;です。私の経験では、計画Bがあると、ユーザーエラーの処理が改善されることがあります。

6
Manngo

特定の1つの型からのキャストtoもう1つの特定の型で十分な場合は、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;

その後

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

戻り値

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

これが数値のみの場合、別のアプローチは、正規表現を使用して入力文字列が有効な数値であるかどうかを確認することです。多くの不正な値が予想される場合は、例外をキャッチするよりも高速です。

根拠

SQL Serverの_TRY_CAST_ のようなものを汎用のPostgreSQL関数にラップするのは困難です。入力と出力は任意のデータ型にすることができますが、SQLは厳密に型指定されており、Postgres関数はパラメータと戻り値の型が作成時に宣言されることを要求します。

Postgresは 多相型 の概念を持っていますが、関数宣言は最大でone多相型を受け入れます。 マニュアル:

ポリモーフィック引数と結果は互いに関連付けられ、ポリモーフィック関数を呼び出すクエリが解析されるときに特定のデータ型に解決されます。 anyelementとして宣言された各位置(引数または戻り値)は、特定の実際のデータ型を持つことができますが、任意の呼び出しでは、それらはすべてでなければなりません同じ実際のタイプ。

CAST ( expression AS type ) は、このルールの例外のように思われ、任意の型を取り、任意の(その他の)型を返します。ただし、cast()のみlooksのように機能しますが、内部ではSQL構文要素です。 。 マニュアル:

[...] 2つの標準キャスト構文の1つを使用してランタイム変換を行う場合、登録された関数を内部的に呼び出して変換を実行します。

入力タイプと出力タイプの組み合わせごとに個別の機能があります。 ( _CREATE CAST_ ...で独自に作成できます)

関数

anyタイプはtextにキャストできるため、私の妥協点はtextを入力として使用することです。 textへの追加のキャストは、追加のコストを意味します(多くはありません)。ポリモーフィズムは、オーバーヘッドも少し追加します。しかし、適度に高価な部分は、必要な動的SQL、関連する文字列連結、そして何よりも例外処理です。

とはいえ、この小さな関数は、配列タイプを含むanyタイプの組み合わせに使用できます。 (ただし、varchar(20)のような型修飾子は失われます):

_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;
_

INOUTパラメータ__out_には、次の2つの目的があります。

  1. 多相型を宣言します
  2. エラーケースのデフォルト値も保持します

あなたはあなたの例のようにそれを呼び出さないでしょう:

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

..ここでCOALESCEは、ソースから真のNULL値(!!)も削除しますが、おそらく意図したとおりではありません。しかし、単に:

_SELECT try_cast(data, 0);
_

.. NULL入力ではNULLを返し、無効な入力では_0_を返します。

短い構文は、dataが文字タイプ(textまたはvarcharなど)であり、_0_がinteger。他の場合では、より明確にする必要があるかもしれません:

呼び出しの例

型なし文字列リテラルそのまま使用できます:

_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
_

型付き値登録済み暗黙のキャストからtextへもそのまま使用できます。

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

textへの暗黙のキャストが登録されたデータ型の包括的なリスト:

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

他のすべての入力タイプtextへの明示的なキャストが必要です:

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

関数本体をどのタイプでも簡単に機能させることができますが、関数タイプの解決は失敗します。関連:

7

以下は、おそらく非常に遅い、一般的なトライキャストです。

_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";
_

これは、varchar(20)のような型を受け入れません(_20_のような "typemod"を受け入れる別のパラメーターを追加することはできますが)。

この関数はテキストを返すため、postgreqsl関数には固定の戻り値型が必要です。そのため、結果を必要な型に強制するために、関数の外で明示的なキャストが必要になる場合があります。

1
Jasen