Функция PostgreSQL с циклом - PullRequest
       12

Функция PostgreSQL с циклом

4 голосов
/ 19 января 2012

Я плохо разбираюсь в функциях postgres.Не могли бы вы мне помочь?
Скажите, у меня есть эта дБ:

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

Я хочу написать функцию Postgres, которая может выполнять цикл от position=1 до position=4 и вычислить соответствующее значение.Я мог бы сделать это в Python с помощью psycopg2:

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

Как я могу сделать то же самое непосредственно в 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

Ответы [ 2 ]

8 голосов
/ 19 января 2012

@ Гленн дал вам очень элегантное решение с агрегатной функцией. Но чтобы ответить на ваш вопрос, функция 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);

Общая функция

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;

Звоните:

SELECT * FROM f_grp_prod();

Результат:

name | round |  result
-----+-------+---------------
A    | 1     | 0.90430333812
A    | 2     | 0.653458283632

Специфическая функция согласно вопросу

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;

Звоните:

SELECT * FROM f_grp_prod('A');

Результат:

name | result1       |  result2
-----+---------------+---------------
A    | 0.90430333812 | 0.653458283632
4 голосов
/ 19 января 2012

Полагаю, вы ищете совокупную функцию "продукт".Вы можете создавать свои собственные агрегатные функции в 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)  

См. «Определяемые пользователем агрегаты» в документе Postgresql.Пример выше я позаимствовал у здесь .Существуют другие ответы на стек, которые показывают другие методы , чтобы сделать это.

...