Функция distribute_expenses

Таблица:

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

Разумеется, возможны и другие алгоритмы, например, перенос всех ошибок округления на одну строку и т. п.

Функция merge

Эта реализация предполагает, что числа не могут быть 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