【问题标题】:PLpgSQL (or ANSI SQL?) Conditional calculation on a columnPLpgSQL(或 ANSI SQL?)列的条件计算
【发布时间】:2012-01-16 05:31:14
【问题描述】:

我想编写一个对列执行条件计算的存储过程。理想情况下,SP 的实现将与数据库无关 - 如果可能的话。如果不是底层数据库是 PostgreSQL (v8.4),那么优先。

被查询的基础表如下所示:

CREATE TABLE treatment_def (  id         PRIMARY SERIAL KEY,
                    name       VARCHAR(16) NOT NULL
                 );

CREATE TABLE foo_group_def (  id         PRIMARY SERIAL KEY,
                    name       VARCHAR(16) NOT NULL
                 );

CREATE TABLE foo (  id         PRIMARY SERIAL KEY,
                    name       VARCHAR(16) NOT NULL,
                    trtmt_id   INT REFERENCES treatment_def(id) ON DELETE RESTRICT,
                    foo_grp_id INT REFERENCES foo_group_def(id) ON DELETE RESTRICT,
                    is_male    BOOLEAN NOT NULL,
                    cost       REAL NOT NULL
             );

我想写一个SP,返回以下'table'结果集:

treatment_name、foo_group_name、averaged_cost

平均成本的计算方式不同,取决于行字段 *is_male* 标志设置为 true 还是 false

对于这个问题,假设如果 is_male 标志设置为 true,则平均成本计算为分组成本值的 SUM,如果 is_male flag 设置为 false,则成本值计算为分组成本值的 AVERAGE

(显然)数据按 trmt_id、foo_grp_id(和 is_male?)分组。

如果没有对 is_male 标志进行条件测试,我对如何编写 SQL 有一个粗略的想法。但是,我可以在编写上述 SP 时得到一些帮助。

这是我的第一次尝试:

CREATE TYPE FOO_RESULT AS (treatment_name VARCHAR(16), foo_group_name VARCHAR(64), averaged_cost DOUBLE);      

// Outline plpgsql (Pseudo code)

CREATE FUNCTION somefunc() RETURNS SETOF FOO_RESULT AS $$
BEGIN
   RETURN QUERY SELECT t.name treatment_name, g.name group_name, averaged_cost  FROM foo f 
                     INNER JOIN treatment_def t ON t.id = f.trtmt_id
                     INNER JOIN foo_group_def g ON g.id = f.foo_grp_id
                GROUP BY f.trtmt_id, f.foo_grp_id;
END;
$$ LANGUAGE plpgsql;

对于如何正确编写此 SP 以在列结果中实现条件计算,我将不胜感激

【问题讨论】:

    标签: sql postgresql stored-procedures plpgsql


    【解决方案1】:

    可能是这样的:

    CREATE FUNCTION somefunc()
     RETURNS TABLE (
      treatment_name varchar(16)
    , foo_group_name varchar(16)
    , averaged_cost double precision)
    AS
    $BODY$
    
        SELECT t.name          -- AS treatment_name
             , g.name          -- AS group_name
             , CASE WHEN f.is_male THEN sum(f.cost)
                                   ELSE avg(f.cost) END -- AS averaged_cost  
        FROM   foo f 
        JOIN   treatment_def t ON t.id = f.trtmt_id
        JOIN   foo_group_def g ON g.id = f.foo_grp_id
        GROUP  BY 1, 2, f.is_male;
    
    $BODY$ LANGUAGE sql;
    

    要点

    • 我使用了sql 函数,而不是plpgsql。你可以使用任何一个,我只是为了缩短代码。 plpgsql 可能会稍微快一些,因为查询计划被缓存了。

    • 我跳过了自定义复合类型。您可以使用RETURNS TABLE 更简单地做到这一点。

    • 我通常建议使用data type text 而不是varchar(n)。让您的生活更轻松。

    • 注意不要在函数体中使用没有表限定 (tbl.col) 的 RETURN 参数名称,否则会产生命名冲突。这就是我评论别名的原因。

    • 我调整了GROUP BY 子句。原来的没用。 (@Ken 的回答中也没有。)

    【讨论】:

    • +1 表示干净的简单 ANSI SQL 有效。再一次,你救了我的培根! :)
    【解决方案2】:

    您应该可以使用CASE 语句:

    SELECT t.name treatment_name, g.name group_name, 
      CASE is_male WHEN true then SUM(cost)
       ELSE AVG(cost) END AS averaged_cost  
    FROM foo f 
      INNER JOIN treatment_def t ON t.id = f.trtmt_id
      INNER JOIN foo_group_def g ON g.id = f.foo_grp_id
    GROUP BY 1, 2, f.is_male;
    

    我不熟悉PLpgSQL,因此我不确定BOOLEAN 列的确切语法,但以上内容至少应该让您朝着正确的方向开始。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-09-07
      • 1970-01-01
      • 2021-06-21
      • 2018-08-17
      • 1970-01-01
      • 2015-01-22
      • 2013-02-26
      • 1970-01-01
      相关资源
      最近更新 更多