I would like to use a query result as a condition on what action to perform inside a postgres function. Here is my attempt:
CREATE OR REPLACE FUNCTION upsert_bin_effect(operation_id integer, typ text, asset_id integer, qty double precision) RETURNS boolean AS $$
BEGIN
existing_locked := (SELECT SUM(qty) AS qty INTO existing FROM bin_effect be WHERE be.operation_id = operation_id AND be.type = typ AND be.asset_id = asset_id AND be.locked = true GROUP BY be.operation_id, be.type, be.asset_id);
qty = qty - existing.locked.qty
existing_unlocked := (SELECT * INTO existing FROM bin_effect be WHERE be.operation_id = operation_id AND be.type = typ AND be.asset_id = asset_id AND (be.locked = false OR be.locked IS NULL));
IF EXISTS(existing_unlocked)
THEN
UPDATE bin_effect be SET be.qty = qty WHERE be.id = existing_unlocked.id
ELSE
INSERT INTO bin_effect be (be.operation_id, be."type", be.asset_id, be.qty) VALUES (operation_id, typ, asset_id, qty);
END IF;
END;
$$ LANGUAGE plpgsql;
existing_locked can have multiple rows, I'd like to subtract the sum of existing_locked.qty from the incoming qty. Then, update the record that is not locked (i.e. in existing_unlocked), if it exists, with the net qty - otherwise insert a new row with the net qty.
If we assume there is a table with the following data:
operation_id, type, asset_id, qty, locked
1, 'A', 1, 10, true
1, 'A', 1, 20, true
1, 'A', 2, 5, true
1, 'A', 2, 15, null
The following call:
upsert_bin_effect(1, 'A', 1, 100)
should result in:
operation_id, type, asset_id, qty, locked
1, 'A', 1, 10, true
1, 'A', 1, 20, true
1, 'A', 2, 5, true
1, 'A', 2, 15, null
1, 'A', 1, 70, null
The following call:
upsert_bin_effect(1, 'A', 2, 100)
should result in:
operation_id, type, asset_id, qty, locked
1, 'A', 1, 10, true
1, 'A', 1, 20, true
1, 'A', 2, 5, true
1, 'A', 2, 95, null
The following call:
upsert_bin_effect(1, 'A', 3, 100)
should result in:
operation_id, type, asset_id, qty, locked
1, 'A', 1, 10, true
1, 'A', 1, 20, true
1, 'A', 2, 5, true
1, 'A', 2, 95, null
1, 'A', 3, 100, null
To better describe how I'd like this function to work here is some javascript pseudocode which implements the desired functionality:
// these are mock result sets, assume they were queried where operation_id, type, asset_id are equal and locked is true/falsy respectively.
const existingLocked = [];
const existingUnlocked = [];
function upsert_bin_effect(operationId, typ, assetId, qty) {
const lockedQty = existingLocked.reduce((sum, r) => sum + r.qty, 0);
// incoming qty represents the total qty. lockedQty represents qty for locked rows (rows we cannot update)
// If there is nonzero lockedQty, we subtract it from qty because when we upsert qty
// we need to ensure that all rows qty will sum to the incoming qty.
qty = qty - lockedQty;
// existingUnlocked should only ever contain a single row (because of the upsert logic below)
if (!!existingUnlocked[0]) {
// if there is an existing unlocked row, update it with the (incoming qty - any locked qty)
existingUnlocked[0].update('qty', qty);
}
else {
// otherwise insert a new row with qty = (incoming qty - any locked qty)
db.binEffect.insert(operationId, typ, assetId, qty)
}
}
I am pretty new to sql function programming. Does this make sense? If not, how can I accomplish what I am trying to do?
existing_lockedvariable using an aggregate query that does not GROUP BY the column (or even select it tbf) by which you are considering rows unique (idI think). I think it would be helpful to write out a sample table, using a simplified schema, that addresses a few use cases, and then resulting table, given some example function call.GROUP BYclause in theexisting_lockedquery...