如何在Postgres 9.1+中实现变量参数ICase

时间:2015-05-06 17:56:42

标签: sql postgresql postgresql-9.1

以下代码用于在Postgres 9.1及以上版本中实现ICase()(Switch in VB)功能。

它几乎没有限制:

  1. 参数数量固定为3.实际上icase可以有4到2 * n + 1的参数数量

  2. resi参数具有固定类型text。 ICase也应接受resi的数字,十进制,日期或其他数据类型,返回值应与resi的类型相同

  3. 如何解决这个问题? 或者为每个可能数量的参数和rosi类型创建单独的重载更好吗?

    CREATE OR REPLACE FUNCTION public.ICase(cond1 bool, res1 text, cond2 bool, res2 text,cond3 bool, res3 text, conddefault text )
      RETURNS TEXT AS
    $BODY$
    SELECT CASE when $1 then $2 
                when $3 then $4
                when $5 then $6
    else $7 end;
    $BODY$ language sql immutable;
    

    更新

    我试着按照回答

    CREATE OR REPLACE FUNCTION public.icase(
        cond1 boolean,
        res1 anyelement,
        conddefault anyelement)
      RETURNS anyelement AS
    ' SELECT CASE WHEN $1 THEN $2 ELSE $3 END; '
      LANGUAGE sql IMMUTABLE;
    

    声明

    select icase( true, 1.0, 0 )
    

    导致错误

    ERROR:  function icase(boolean, numeric, integer) does not exist
    LINE 9: select icase( true, 1.0, 0 )
                   ^
    HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
    

    如何在9.1+中修复此问题,以便第二个和第三个参数可以是int和numeric?

2 个答案:

答案 0 :(得分:1)

请参阅下面的编辑,了解新方法。

如果可以将输入作为数组提供,则可以使用:

CREATE FUNCTION public.ICase(
  p_cond boolean[],
  p_array anyarray)
RETURNS anyelement AS
$BODY$
DECLARE
  v_arrlen integer;
BEGIN
  v_arrlen := array_upper(p_array, 1);
  IF v_arrlen <> array_upper(p_cond, 1) + 1 THEN
    RETURN NULL;
  END IF;
  FOR i IN 1..v_arrlen LOOP
    IF p_cond[i] THEN
      RETURN p_array[i];
    END IF;
  END LOOP;
  RETURN p_array[v_arrlen];
END;
$BODY$
LANGUAGE plpgsql IMMUTABLE;

尝试使用:

SELECT * FROM ICase(ARRAY[FALSE, FALSE, TRUE], ARRAY[1, 2, 3, 4])

产生结果3

编辑:好吧,因为你真的试图保持你的原始呼叫完好无损,我认为如果你用动态查询创建这个函数的所有可能的排列可能是最简单的。这很难看,但它确实有效。

CREATE OR REPLACE FUNCTION build_icase(p_num integer, p_types text[]) RETURNS VOID AS 
$BODY$ 
DECLARE
  v_qry text;
BEGIN 
FOR i IN 1..p_num LOOP
  FOR j IN 1..array_upper(p_types, 1) LOOP
    v_qry := 'CREATE OR REPLACE FUNCTION icase(';
    FOR k IN 1..i LOOP
        IF k > 1 THEN v_qry := v_qry || ', '; END IF;
        v_qry := v_qry || 'cond' || k || ' boolean, res' || k || ' ' || p_types[j];
    END LOOP;
    v_qry := v_qry || ', conddefault ' || p_types[j] || ') RETURNS ' || p_types[j] || ' AS $FUNC$ SELECT CASE ';
    FOR k in 1..(i * 2) BY 2 LOOP
        v_qry := v_qry || 'WHEN $' || k || ' THEN $' || k + 1 || ' ';
    END LOOP;
    v_qry := v_qry || 'ELSE $' || (i * 2) + 1 || ' END; $FUNC$ LANGUAGE sql IMMUTABLE;';
    EXECUTE v_qry;
  END LOOP;
END LOOP;
END; 
$BODY$ LANGUAGE plpgsql VOLATILE;

然后为最大条件/结果10以及“日期”,“数字”或“文本”的结果数据类型构建批量函数,运行:

SELECT build_icase(10, ARRAY['date', 'numeric', 'text'])

这将为您需要传递多少条件参数以及结果的所有可能数据类型的所有迭代构建30个函数。在做完这个之后,我意识到我可能已经用anyelement抽象了这个,这将把它减少到10个函数(参见下面的EDIT2)。我对anyelement没有很多经验,所以我不确定它是否会遇到更多的投射错误,而不是像你在本例中那样使用显式数据类型。

运行时请小心,因为它可能会为您创建许多功能以进行潜在的清理。我建议使用较低的数字进行测试,您始终可以将EXECUTE v_qry;替换为RAISE INFO 'v_qry is: %', v_qry;,这样只会将动态查询作为消息打印出来而不执行它们。

EDIT2:以下是使用anyelement创建较少功能的版本。我没有测试这个,但从这里看起来不错。

CREATE OR REPLACE FUNCTION build_icase(p_num integer) RETURNS VOID AS 
$BODY$ 
DECLARE
  v_qry text;
BEGIN 
FOR i IN 1..p_num LOOP
    v_qry := 'CREATE OR REPLACE FUNCTION icase(';
    FOR k IN 1..i LOOP
        IF k > 1 THEN v_qry := v_qry || ', '; END IF;
        v_qry := v_qry || 'cond' || k || ' boolean, res' || k || ' anyelement';
    END LOOP;
    v_qry := v_qry || ', conddefault anyelement) RETURNS anyelement AS $FUNC$ SELECT CASE ';
    FOR k in 1..(i * 2) BY 2 LOOP
        v_qry := v_qry || 'WHEN $' || k || ' THEN $' || k + 1 || ' ';
    END LOOP;
    v_qry := v_qry || 'ELSE $' || (i * 2) + 1 || ' END; $FUNC$ LANGUAGE sql IMMUTABLE;';
    EXECUTE v_qry;
END LOOP;
END; 
$BODY$ LANGUAGE plpgsql VOLATILE;

答案 1 :(得分:1)

从PostgreSQL 9.4开始,目前无法从VB创建与ICase()具有相同参数行为的PostgreSQL函数。

目前的两个限制是:

  1. VARIADIC只能在函数末尾指定一次(因此2*n+1本身不可强制执行)。
  2. 虽然支持polymorphic arguments,但每次调用只能指定一种数据类型,因为它会将VARIADIC参数转换为ARRAY(因此ARRAY[TRUE, 'text']无效)。< / LI>

    但是,如果我们重新排列参数,可以通过使用数组和pseudo-type参数来实现非常相似的行为:

    CREATE OR REPLACE FUNCTION public.ICase(boolean[], anyarray, anyelement)
      RETURNS anyelement AS
    $BODY$
    SELECT
      $2[i]
    FROM
      generate_subscripts($1, 1) g(i)
    WHERE
      $1[i] IS TRUE
    UNION ALL
    SELECT
      $3
    LIMIT 1;
    $BODY$ LANGUAGE sql IMMUTABLE;
    
    1. 第一个参数接受一个条件boolean值数组。
    2. 第二个参数接受任何数据类型的值数组。
    3. 第三个参数接受任何数据类型,但它必须与第二个参数数组的元素的数据类型匹配。
    4. TRUE的{​​{1}}的第一个boolean[]元素将导致返回$1的相同索引:

      $2

      如果SELECT ICase(ARRAY[0=1,1=1], ARRAY['a','b'], 'default'); icase ------- b (1 row) 中没有TRUE元素,则会返回$1

      $3

      SELECT ICase(ARRAY[FALSE,FALSE], ARRAY['a','b'], 'default'); icase --------- default (1 row) $2支持任何数据类型:

      $3

      理论上,您可以创建一个由SELECT ICase(ARRAY[TRUE], ARRAY['12:00'::time], NOW()::time); icase ---------- 12:00:00 (1 row) SELECT ICase(ARRAY[FALSE], ARRAY[1], -1); icase ------- -1 (1 row) 和另一种数据类型组成的复合类型,并创建一个类似boolean的函数,但它不会像上面的示例那样灵活。

      我不确定VB的ICaseText(text, VARIADIC boolean_text[]),但使用这样的函数不允许你像内置CASE那样使用子表达式的短路,例如:

      ICase()

      SELECT CASE WHEN TRUE THEN 1 ELSE 1/0 END; case ------ 1 (1 row) 实际上从未被评估过,所以我们没有得到“除以零”错误。

      使用1/0函数时,情况并非如此:

      ICase