使用类型(“TEXT”)的字符串表示形式将值转换为类型(TEXT)

Gam*_*mes 1 sql postgresql polymorphism dynamic-sql plpgsql

我想创建一个函数,尝试将一组值转换为用户指定的类型(默认为文本)。一个非常简单的函数如下所示:

CREATE OR REPLACE FUNCTION cast_to(variable jsonb, key text, target_type anyelement DEFAULT 'TEXT'::regtype) RETURNS anyelement as $$
begin
    RETURN CAST(variable->>key AS target_type);
end
$$
language plpgsql;
Run Code Online (Sandbox Code Playgroud)

我已经尝试过以下方法:

  1. SELECT CAST('foo' AS 'text');: 给出语法错误
  2. SELECT CAST('foo' AS 'text'::regtype);: 与 1 相同的错误
  3. SELECT CAST('foo' AS pg_typeof(null::text));type pg_typeof does not exist

最后一次尝试是我可以传入一个具有目标类型的变量而不是文本表示形式。使用该函数将如下所示SELECT cast_to('text', NULL::text);

如何实现这个或类似的功能?

编辑:正如评论中所建议的,我尝试使用动态 SQL。我运气不太好。我创建了一个非常基本的案例,不使用任何变量:

CREATE OR REPLACE FUNCTION audit.cast_to() RETURNS text as $$
DECLARE 
_sql TEXT := 'SELECT CAST($1 AS $2)';
out TEXT;
begin
    EXECUTE _sql
    USING 'foo', 'TEXT'
    INTO out;
    return out;
end
$$
language plpgsql;
Run Code Online (Sandbox Code Playgroud)

然而,这会引发错误:

syntax error at or near "$2"
Run Code Online (Sandbox Code Playgroud)

Erw*_*ter 5

其实是可以做到的。即使没有动态 SQL。表面上也很简单。

CREATE FUNCTION cast_to(_js jsonb, INOUT _target_type anyelement = NULL::text) AS
$func$
BEGIN
   SELECT _js ->> pg_typeof(_target_type)::text
   INTO   _target_type;
END
$func$  LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)

db<>在这里摆弄

但是这个简约的函数包含一些高级/棘手的细节:请参阅最后一章了解基础知识:

难题的最后一部分是将运算符text返回的值转换->>为返回类型。一个简单的 SQL 函数对此很严格,并且不接受textfor,例如 an integer。(也不适用于integer由多态输入参数的实际输入定义的情况。同样的情况也适用于尝试简单地进行 PL/pgSQL 函数RETURN。需要显式强制转换。

CAST (expression AS type)不是一个正常的函数。短语法也不是expression::type。这些是结构语法元素。您可能已经注意到,类型名称没有使用单引号,即:作为标识符。(或者您错过了该细节,这就是您报告的前 3 个语法错误的原因。)并且标识符无法在 SQL 中参数化。这需要动态 SQL。

但是,我们可以将表达式的结果分配text给(必须类型化的)变量或参数,以轻松实现相同的目的。该INTO条款将实现这一目标。为了方便起见,我直接给INOUT参数赋值。_target_type因此_target_type有几个目的:

  1. 定义多态返回类型。

  2. 定义与 JSON ->> 运算符一起使用的键名称。该名称由 的类型携带,_target_type用 - 提取它,pg_typeof()它实际上返回regtype,所以我们需要将显式转换为文本。
    请注意,始终会生成 Postgres 标准类型名称,例如:“integer”,而不是“int”或“int4”。如果您的键名称与默认的 Postgres 类型名称不同,您必须传递一个附加参数,就像您在原始设计中那样!

  3. 用作可以分配的OUT参数(因此我们不需要DECLARE变量)。

  4. DEFAULT附加了一个值: = NULL::text,因此可以省略第二个参数,只返回text
    你在原来的版本中尝试过,但DEFAULT 'TEXT'::regtype偏离了目标。

您可能希望将其中一些用途拆分为多个参数/变量。

所以这是可以做到的。
问题是:你为什么要这么做?通常,很快就会有一个更快、更简单的解决方案——即使更冗长。