我想将查询结果用作在postgres函数中执行什么操作的条件。这是我的尝试:
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
可以有多行,我想从传入的existing_locked.qty
中减去qty
的总和。然后,使用网络locked
更新不属于existing_unlocked
的记录(即qty
中的记录,如果存在的话)-否则,使用网络qty
插入新行。
如果我们假设有一个包含以下数据的表:
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
以下通话:
upsert_bin_effect(1, 'A', 1, 100)
应导致:
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
以下通话:
upsert_bin_effect(1, 'A', 2, 100)
应导致:
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
以下通话:
upsert_bin_effect(1, 'A', 3, 100)
应导致:
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
为了更好地描述我希望此功能在这里如何工作,是一些实现所需功能的javascript伪代码:
// 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)
}
}
我对sql函数编程非常陌生。这有意义吗?如果没有,我该如何完成我想做的事情?
答案 0 :(得分:1)
在使用所需功能之前,此功能存在几个问题:
DECLARE existing RESULT
--There is no RESULT type and you do end with ; So:
DECLARE existing RECORD;
existing_locked未声明,因此分配失败。 相同于existing_unlocked。
qty = qty - existing.locked.qty
不能以;
我会花一些时间在这里
https://www.postgresql.org/docs/12/plpgsql-structure.html
从下面的评论中,我看不到新示例与您想要的内容相符:
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
“ existing可以有多行,我想从任何锁定行的传入数量中减去现存.qty的总和。然后,如果存在未锁定的行,则更新任何未用传入数量锁定的记录,否则插入一个新的。”
我原以为结果会是:
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, 115, null
更新
刺探我想你想要的东西。显然没有经过测试,但这应该是一个合理的起点。
CREATE OR REPLACE FUNCTION upsert_bin_effect(operation_id integer, typ text, asset_id integer, qty double precision) RETURNS boolean AS $$
DECLARE
existing_locked RECORD;
existing_unlocked RECORD;
net_qty float;
unlocked_ct integer;
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);
net_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));
GET DIAGNOSTICS unlocked_ct = ROW_COUNT;
IF EXISTS(existing_unlocked)
THEN
IF unlocked_ct = 1 THEN
UPDATE bin_effect be SET be.qty = net_qty WHERE be.id = existing_unlocked.id;
ELSEIF unlocked_ct > 1
--Not sure if you want this to happen, included as example.
RAISE EXCEPTION 'Too many unlocked row';
END IF;
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;
答案 1 :(得分:1)
让我在开头说一下,这可能有点XY problem。 Postgres具有锁定事务中的行的机制,但无需修改它们。这意味着其他事务无法更新它们,但仍可以读取其预锁定状态。这称为SELECT FOR UPDATE
。因此,您的upsert_bin_effect
可以读取锁定的行,但无法对其进行修改。
如果在未锁定的行上创建唯一索引,则可以使用INSERT ... ON CONFLICT UPDATE
在单个查询中执行此操作。给出以下示例架构:
create table bin_effect (
operation_id integer not null,
typ text not null,
asset_id integer not null,
qty double precision not null,
locked boolean not null
-- don't understand why you were using null to indicated that the row was not locked
);
-- create a partial unique index that guarantees at most one unlocked row per "key"
create unique index bin_effect_unqiue_unlocked
on bin_effect (operation_id, typ, asset_id) where not locked;
然后给一个从以下插入中初始化的表:
insert into bin_effect values
(1, 'A', 1, 10, true),
(1, 'A', 1, 20, true),
(1, 'A', 2, 5, true),
(1, 'A', 2, 15, false);
然后,以下查询将为给定的operation_id
,typ
和asset_id
插入未锁定的行或更新未锁定的行。然后,您可以直接或作为存储函数的一部分将此查询用作参数化查询。注意这是operation_id = 1
,typ = 'A'
,asset_id = 1
的原始查询,新查询的数量为100。
-- pre-calculate the desired qty, so we only compute it once
with new_value as (
select
-- use coalesce for when there no matching rows in table (locked or otherwise)
100 - coalesce(sum(qty), 0) as qty
from bin_effect
where
operation_id = 1
and typ = 'A'
and asset_id = 1
and locked
)
-- try to insert a new row
insert into bin_effect (operation_id, typ, asset_id, locked, qty)
values
(1, 'A', 1, false, (select qty from new_value))
-- if the insertion fails, then update the pre-existing row
on conflict (operation_id, typ, asset_id) where not locked
do update set qty = (select qty from new_value)
;
作为存储函数:
create or replace function upsert_bin_effect(operation_id_ integer, typ_ text, asset_id_ integer, new_qty double precision)
returns double precision as $$
with new_value as (
select
new_qty - coalesce(sum(qty), 0) as qty
from bin_effect
where
operation_id = operation_id_
and typ = typ_
and asset_id = asset_id_
and locked
)
insert into bin_effect (operation_id, typ, asset_id, locked, qty)
values
(operation_id_, typ_, asset_id_, false, (select qty from new_value))
on conflict (operation_id, typ, asset_id) where not locked
do update set qty = (select qty from new_value)
returning qty
;
$$
language sql;
用法和输出示例:
postgres=# select upsert_bin_effect(1, 'A', 1, 100);
upsert_bin_effect
-------------------
70
(1 row)
postgres=# select upsert_bin_effect(1, 'A', 2, 100);
upsert_bin_effect
-------------------
95
(1 row)
postgres=# select upsert_bin_effect(1, 'A', 3, 100);
upsert_bin_effect
-------------------
100
(1 row)
postgres=# table bin_effect;
operation_id | typ | asset_id | qty | locked
--------------+-----+----------+-----+--------
1 | A | 1 | 10 | t
1 | A | 1 | 20 | t
1 | A | 2 | 5 | t
1 | A | 1 | 70 | f
1 | A | 2 | 95 | f
1 | A | 3 | 100 | f
(6 rows)