带循环的PostgreSQL函数

BPm*_*BPm 4 postgresql function plpgsql aggregate-functions

我不擅长postgres功能.你能救我吗?
说,我有这个db:

name    | round   |position | val
-----------------------------------
A       | 1       | 1       | 0.5
A       | 1       | 2       | 3.4
A       | 1       | 3       | 2.2
A       | 1       | 4       | 3.8
A       | 2       | 1       | 0.5
A       | 2       | 2       | 32.3
A       | 2       | 3       | 2.21
A       | 2       | 4       | 0.8
Run Code Online (Sandbox Code Playgroud)

我想写一个Postgres函数,它可以循环position=1position=4并计算相应的值.我可以用psycopg2在python中做到这一点:

import psycopg2
import psycopg2.extras

conn = psycopg2.connect("host='localhost' dbname='mydb' user='user' password='pass'")
CURSOR = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
cmd = """SELECT name, round, position, val from mytable"""
CURSOR.execute(cmd)
rows = CURSOR.fetchall()

dict = {}
for row in rows:
    indx = row['round']
    try:
        dict[indx] *= (1-row['val']/100)
    except:
        dict[indx] = (1-row['val']/100)
    if row['position'] == 4:
        if indx == 1:
            result1 = dict[indx]
        elif indx == 2:
            result2 = dict[indx]
print result1, result2
Run Code Online (Sandbox Code Playgroud)

我怎么能直接在Postgres中做同样的事情,以便它返回一个表 (name, result1, result2)

更新:
@a_horse_with_no_name,期望值为:

result1 = (1 - 0.5/100) * (1 - 3.4/100) * (1 - 2.2/100) * (1 - 3.8/100) = 0.9043
result2 = (1 - 0.5/100) * (1 - 32.3/100) * (1 - 2.21/100) * (1 - 0.8/100) = 0.6535
Run Code Online (Sandbox Code Playgroud)

Erw*_*ter 8

@Glenn为您提供了一个具有聚合功能的非常优雅的解决方案.但要回答你的问题,plpgsql函数可能如下所示:

测试设置:

CREATE TEMP TABLE mytable (
  name  text
, round int
, position int
, val double precision);

INSERT INTO mytable VALUES
 ('A', 1, 1, 0.5)
,('A', 1, 2, 3.4)
,('A', 1, 3, 2.2)
,('A', 1, 4, 3.8)
,('A', 2, 1, 0.5)
,('A', 2, 2, 32.3)
,('A', 2, 3, 2.21)
,('A', 2, 4, 0.8);
Run Code Online (Sandbox Code Playgroud)

通用功能

CREATE OR REPLACE FUNCTION f_grp_prod()
  RETURNS TABLE (
    name text
  , round int
  , result double precision) AS
$BODY$
DECLARE
    r mytable%ROWTYPE;
BEGIN
    -- init vars
    name    := 'A';     -- we happen to know initial value
    round   := 1;       -- we happen to know initial value
    result  := 1;

FOR r IN
    SELECT *
    FROM mytable m
    ORDER BY m.name, m.round
LOOP
    IF (r.name, r.round) <> (name, round) THEN  -- return result before round
        RETURN NEXT;
        name    := r.name;
        round   := r.round;
        result  := 1;
    END IF;

    result := result * (1 - r.val/100);
END LOOP;

RETURN NEXT;    -- return final result

END;
$BODY$ LANGUAGE plpgsql STABLE;
Run Code Online (Sandbox Code Playgroud)

呼叫:

SELECT * FROM f_grp_prod();
Run Code Online (Sandbox Code Playgroud)

结果:

name | round |  result
-----+-------+---------------
A    | 1     | 0.90430333812
A    | 2     | 0.653458283632
Run Code Online (Sandbox Code Playgroud)

具体功能按问题

CREATE OR REPLACE FUNCTION f_grp_prod(text)
  RETURNS TABLE (
    name text
  , result1 double precision
  , result2 double precision) AS
$BODY$
DECLARE
    r      mytable%ROWTYPE;
    _round integer;
BEGIN
    -- init vars
    name    := $1;
    result2 := 1;       -- abuse result2 as temp var for convenience

FOR r IN
    SELECT *
    FROM   mytable m
    WHERE  m.name = name
    ORDER  BY m.round
LOOP
    IF r.round <> _round THEN   -- save result1 before 2nd round
        result1 := result2;
        result2 := 1;
    END IF;

    result2 := result2 * (1 - r.val/100);
    _round  := r.round;
END LOOP;

RETURN NEXT;

END;
$BODY$      LANGUAGE plpgsql STABLE;
Run Code Online (Sandbox Code Playgroud)

呼叫:

SELECT * FROM f_grp_prod('A');
Run Code Online (Sandbox Code Playgroud)

结果:

name | result1       |  result2
-----+---------------+---------------
A    | 0.90430333812 | 0.653458283632
Run Code Online (Sandbox Code Playgroud)


Gle*_*enn 5

我猜您正在寻找聚合的“产品”功能。您可以在 Postgresql 和 Oracle 中创建自己的聚合函数。

    CREATE TABLE mytable(name varchar(32), round int, position int, val decimal);

    INSERT INTO mytable VALUES('A', 1, 1, 0.5);
    INSERT INTO mytable VALUES('A', 1, 2, 3.4);
    INSERT INTO mytable VALUES('A', 1, 3, 2.2);
    INSERT INTO mytable VALUES('A', 1, 4, 3.8);

    INSERT INTO mytable VALUES('A', 2, 1, 0.5);
    INSERT INTO mytable VALUES('A', 2, 2, 32.3);
    INSERT INTO mytable VALUES('A', 2, 3, 2.21);
    INSERT INTO mytable VALUES('A', 2, 4, 0.8);

    CREATE AGGREGATE product(double precision) (SFUNC=float8mul, STYPE=double precision, INITCOND=1);

    SELECT name, round, product(1-val/100) AS result
      FROM mytable
      GROUP BY name, round;

     name | round |     result
    ------+-------+----------------
     A    |     2 | 0.653458283632
     A    |     1 |  0.90430333812
    (2 rows)  
Run Code Online (Sandbox Code Playgroud)

请参阅 Postgresql 文档中的“用户定义的聚合”。上面的例子是我从 这里借来的。还有其他 stackoverflow 响应显示了执行此操作的其他方法。