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

Trying to create dynamic query strings with PL/PgSQL to make DRY functions in PostgreSQL 9.6

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

d_abc_2016
d_def_2016
d_ghi_2016
d_jkl_2016

每个 table 都有特定的常量:company_idemployee_idsalary

但是,每个人可能有也可能没有这些用于计算总激励的字段:bonuscommissioncash_incentives。还有很多,但仅以这些为例。全部 numeric

此时我应该注意,用户只能运行 SELECT 语句。

我希望能够做的是:

  1. 让用户能够调用 SELECT 并在调用
  2. 之外指定他们自己的字段
  3. 将正在使用的 table 名称传递到函数中,以便在条件逻辑中使用,以确定除了传递整个 [=103] 之外应该如何为最终的 total_incentives 计算构建查询字符串=] 因此不必将大量参数传递给函数

基本上是这样的:

SELECT employee_id, salary, total_incentives(t, 'd_abc_2016')
FROM d_abc_2016 t;

因此被调用的函数将计算 total_incentives,即 employee_idnumeric,并显示它们的 salary。但用户可能会选择添加其他字段来查看。

对于函数,因为total_incentives函数中使用的字段会从table到table不等,我需要创建逻辑来动态构造查询字符串。

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

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

因为它应该是 return 一组 numeric 值。将其更改为以下内容:

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

得到同样的错误。

图不错,说不定是个table正要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 遍历每条记录,我认为这不会满足我的需要 table 并且看起来真的很慢......并且需要硬编码查询据我所知。

RESULT QUERY EXECUTE 听起来很有希望。

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

RETURN QUERY 
    EXECUTE format('SELECT  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

应该是 returning numeric.

最后,我可以让它工作,但它不会干。我不想用重复代码为每个 table 创建一堆单独的函数。我见过的大多数工作示例都在函数中包含整个查询,并且这样调用:

SELECT total_incentives(d_abc_2016, 'd_abc_2016');

所以任何额外的列都必须在函数中指定为:

EXECUTE format('SELECT employee_id...)

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

我发布了一个类似的问题,但被告知不清楚,所以希望这个较长的版本能更清楚地解释我正在尝试做的事情。

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

大概线路:

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

应该是:

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

这个案例就是为什么过于 DRY 原则有时会出现问题的例子。如果您直接编写它,那么您的代码将更简单、更清晰并且可能更短。

动态 SQL 是上一个解决方案中的一个 - 不是第一个。仅当使用动态 sql 的代码比不使用动态 SQL.

的代码明显短时,才使用动态 SQL