使用PL/PgSQL创建动态查询字符串以在PostgreSQL 9.6中实现DRY函数

3

我有一些表格,每年都包含相同类型的数据,但是收集的数据略有不同,因为它们可能没有相同的字段。

d_abc_2016
d_def_2016
d_ghi_2016
d_jkl_2016

每个表都有一些常量:company_id, employee_id, salary
然而,每个表可能有或可能没有用于计算总激励的这些字段:bonus, commission, cash_incentives。还有很多其他字段,但这里只是举例。numeric 值得注意的是,用户只能运行SELECT语句。
我想要做的是:
  1. 给用户调用SELECT并指定自己的字段的能力
  2. 将正在使用的表名传递到函数中以使用条件逻辑来确定查询字符串应如何构建为最终的total_incentives计算,同时传递整个表格,以便不必传递大量参数到函数中
基本上就是这样:
SELECT employee_id, salary, total_incentives(t, 'd_abc_2016')
FROM d_abc_2016 t;

所以被调用的函数将计算该员工的数字奖励total_incentives,并显示他们的工资salary。但用户可能会选择添加其他字段进行查看。
对于该函数,由于在total_incentives函数中使用的字段会因表而异,因此我需要创建逻辑来动态构建查询字符串。
CREATE OR REPLACE FUNCTION total_incentives(ANYELEMENT, t text)
    RETURNS numeric AS
$$
DECLARE
    -- table name lower case in case user typed wrong
    tbl          varchar(255) := lower($2;

    -- parse out the table code to use in conditional logic
    tbl_code     varchar(255) := split_part(survey, '_', 2);

    -- the starting point if the query string
    base_calc    varchar(255) := 'salary + '

    -- query string
    query_string varchar(255);

    -- have to declare this to put computation INTO
    total_incentives_calc numeric;
BEGIN
    IF tbl_code = 'abc' THEN
        query_string := base_calc || 'bonus';
    ELSIF tbl_code = 'def' THEN
        query_string := base_calc || 'bonus + commission';
    ELSIF tbl_code = 'ghi' THEN
        -- etc...
    END IF;

    EXECUTE format('SELECT $1 FROM %I', tbl)
    INTO total_incentives_calc
    USING query_string;

    RETURN total_incentives_calc;
END;
$$
LANGUAGE plpgsql;

这将导致一个:
ERROR:  invalid input syntax for type numeric: "salary + bonus"
CONTEXT:  PL/pgSQL function total_incentives(anyelement,text) line 16 at EXECUTE

由于它应该返回一组“numeric”值,因此请更改为以下内容:

CREATE OR REPLACE FUNCTION total_incentives(ANYELEMENT, t text)
    RETURNS SETOF numeric AS
$$
...
    RETURN;

我得到了相同的错误。

看来可能是它试图返回一个表格。

CREATE OR REPLACE FUNCTION total_incentives(ANYELEMENT, t text)
    RETURNS TABLE(tot_inc numeric) AS
$$
...

出现了相同的错误。

实际上,任何变化都会产生这个结果。所以真的不确定如何使其工作。

查看RESULT QUERYRESULT NEXTRESULT QUERY EXECUTE

https://www.postgresql.org/docs/9.6/static/plpgsql-control-structures.html

RESULT QUERY不起作用,因为它似乎采用硬编码查询,不会接受变量。

RESULT NEXT迭代每个记录,我认为这对我的需求不合适,而且似乎会非常慢...并且从我所看到的内容来看,它采用了硬编码查询。

RESULT QUERY EXECUTE听起来很有前途。

-- EXECUTE format('SELECT $1 FROM %I', tbl)
-- INTO total_incentives_calc
-- USING query_string;

RETURN QUERY 
    EXECUTE format('SELECT $1 FROM %I', tbl)
    USING query_string;

并获得:

ERROR:  structure of query does not match function result type
DETAIL:  Returned type character varying does not match expected type numeric in column 1.
CONTEXT:  PL/pgSQL function total_incentives(anyelement,text) line 20 at RETURN QUERY

它应该返回数字

最后,我可以让它工作,但这不是DRY。我不想为每个表格创建一堆重复的代码的单独函数。我看到的大多数有效示例在函数中有整个查询,并像以下方式调用:

SELECT total_incentives(d_abc_2016, 'd_abc_2016');

所以任何额外的列都必须在函数中指定为:
EXECUTE format('SELECT employee_id...)

考虑到用户只能在查询中运行SELECT,这真的不是一个选择。他们需要在查询中指定想要查看的任何其他列。

我发布了类似的问题,但被告知不清楚,希望这个更详细的版本能更清楚地解释我正在尝试做什么。

1个回答

4

不应将列名和表名用作通过USING子句传递的查询参数。

可能的行:

RETURN QUERY 
    EXECUTE format('SELECT $1 FROM %I', tbl)
    USING query_string;

should be:

RETURN QUERY 
    EXECUTE format('SELECT %s FROM %I', query_string, tbl);

这个例子说明了过于DRY原则有时会带来问题。如果你直接编写它,那么你的代码将更简单、更清洁,可能也更短。

动态SQL是最后一种解决方案之一 - 而不是第一种。只有当使用动态SQL比不使用动态SQL能使你的代码显著缩短时才使用动态SQL。


太棒了,查询已经成功运行。感谢您的帮助!现在需要解决并防止它将返回记录的数量乘以总记录数。 - cjones

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接