Таблица:
=> CREATE TABLE depts( id serial PRIMARY KEY, employees integer, expenses numeric(10,2) );
CREATE TABLE
=> INSERT INTO depts(employees) VALUES (10),(10),(10);
INSERT 0 3
Функция:
=> CREATE FUNCTION distribute_expenses(amount numeric) RETURNS void AS $$ DECLARE depts_cur CURSOR FOR SELECT employees FROM depts FOR UPDATE; total_employees numeric; expense numeric; rounding_err numeric := 0.0; cent numeric; BEGIN SELECT sum(employees) FROM depts INTO total_employees; FOR dept IN depts_cur LOOP expense := amount * (dept.employees / total_employees); rounding_err := rounding_err + (expense - round(expense,2)); cent := round(rounding_err,2); expense := expense + cent; rounding_err := rounding_err - cent; UPDATE depts SET expenses = round(expense,2) WHERE CURRENT OF depts_cur; END LOOP; END; $$ VOLATILE LANGUAGE plpgsql;
CREATE FUNCTION
Проверка:
=> SELECT distribute_expenses(100.0);
distribute_expenses --------------------- (1 row)
=> SELECT * FROM depts;
id | employees | expenses ----+-----------+---------- 1 | 10 | 33.33 2 | 10 | 33.34 3 | 10 | 33.33 (3 rows)
Разумеется, возможны и другие алгоритмы, например, перенос всех ошибок округления на одну строку и т. п.
Эта реализация предполагает, что числа не могут быть NULL.
=> CREATE FUNCTION merge(c1 refcursor, c2 refcursor) RETURNS SETOF integer AS $$ DECLARE a integer; b integer; BEGIN FETCH c1 INTO a; FETCH c2 INTO b; LOOP EXIT WHEN a IS NULL AND b IS NULL; IF a < b OR b IS NULL THEN RETURN NEXT a; FETCH c1 INTO a; ELSE RETURN NEXT b; FETCH c2 INTO b; END IF; END LOOP; END; $$ VOLATILE LANGUAGE plpgsql;
CREATE FUNCTION
Проверяем.
=> CREATE TABLE t1(n integer);
CREATE TABLE
=> CREATE TABLE t2(n integer);
CREATE TABLE
=> INSERT INTO t1 VALUES (1),(3),(5);
INSERT 0 3
=> INSERT INTO t2 VALUES (2),(3),(4);
INSERT 0 3
=> BEGIN;
BEGIN
=> DECLARE c1 CURSOR FOR SELECT * FROM t1 ORDER BY n;
DECLARE CURSOR
=> DECLARE c2 CURSOR FOR SELECT * FROM t2 ORDER BY n;
DECLARE CURSOR
=> SELECT * FROM merge('c1','c2');
merge ------- 1 2 3 3 4 5 (6 rows)
=> COMMIT;
COMMIT