带循环的 PostgreSQL 函数

发布于 2024-12-27 18:22:17 字数 1489 浏览 2 评论 0原文

我不擅长 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 并计算相应的值。我可以使用 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

如何直接在 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

I'm not good at postgres functions. Could you help me out?
Say, I have this 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

I want to write a Postgres function that can loop from position=1 to position=4 and calculate the corresponding value. I could do this in python with 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

How can I do the same thing directly in Postgres so that it returns a table of (name, result1, result2)

UPDATE:
@a_horse_with_no_name, the expected value would be:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

铃予 2025-01-03 18:22:17

@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)
;

通用函数

CREATE OR REPLACE FUNCTION f_grp_prod()
  RETURNS TABLE (name text
               , round int
               , result double precision)
  LANGUAGE plpgsql STABLE AS
$func$
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
$func$;

调用:

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)
  LANGUAGE plpgsql STABLE AS
$func$
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
$func$;

调用的特定函数:

SELECT * FROM f_grp_prod('A');

结果:

name | result1       |  result2
-----+---------------+---------------
A    | 0.90430333812 | 0.653458283632

@Glenn gave you a very elegant solution with an aggregate function. But to answer your question, a plpgsql function could look like this:

Test setup:

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

Generic function

CREATE OR REPLACE FUNCTION f_grp_prod()
  RETURNS TABLE (name text
               , round int
               , result double precision)
  LANGUAGE plpgsql STABLE AS
$func$
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
$func$;

Call:

SELECT * FROM f_grp_prod();

Result:

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

Specific function as per question

CREATE OR REPLACE FUNCTION f_grp_prod(text)
  RETURNS TABLE (name text
               , result1 double precision
               , result2 double precision)
  LANGUAGE plpgsql STABLE AS
$func$
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
$func$;

Call:

SELECT * FROM f_grp_prod('A');

Result:

name | result1       |  result2
-----+---------------+---------------
A    | 0.90430333812 | 0.653458283632
纸伞微斜 2025-01-03 18:22:17

我猜您正在寻找聚合的“产品”功能。您可以在 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 文档中的“用户定义的聚合”。上面的例子是我借用的
此处。还有其他 stackoverflow 响应显示了其他方法做这个。

I guess you are looking for an aggregate "product" function. You can create your own aggregate functions in Postgresql and 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)  

See "User-Defined Aggregates" in the Postgresql doc. The example above I borrowed from
here. There are other stackoverflow responses that show other methods to do this.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文