【问题标题】:How to assign a local variable in an update sentence in PostgreSQL如何在 PostgreSQL 的更新语句中分配局部变量
【发布时间】:2020-08-29 13:02:24
【问题描述】:

我试图使用在几行上运行的update 语句的SET 子句分配一个局部变量(实际上是两个)。好的,我正在做这个 ala MySQL。

drop table if exists stocks cascade;

create table stocks (
  id              serial,
  stock_available int,
  stock_locked    int
);

insert into stocks(stock_available, stock_locked) values 
(150, 10),
(150, 20),
(150, 0),
(100, 0),
(100, 100),
(100, 30),
(100, 0),
(100, 50),
(100, 0);

create or replace function lock_all ()
returns int
language plpgsql as $$
declare
  _amount int;
  _total int;
begin
  -- initialize accumulator
  _total = 0;

  -- update all the stocks table rows
  update stocks
  set    _amount = stock_available,
         stock_locked = stock_locked + _amount,
         _total = _total + _amount;
  
  -- returns the units locked 
  return _total;
end;
$$;

不幸的是,这不是 PostgreSQL 期望的方式。

 SQL Error [42703]: ERROR: column "_amount" of relation "stocks" does not exist
  Where: PL/pgSQL function lock_all() line 10 at SQL statement

这只是一个简单的例子来说明计算/总结update 句子中更新的事物数量的真正问题。我确信这个特定示例可能有技巧或方法,但我对这种必须计算累加器的情况的通用解决方案感兴趣。

有什么想法吗?


编辑

根据@GMB 的建议,我链接了 3 个 ctes

create or replace function lock_all3 ()
returns int
language sql as $$
  with 
    cte1 as (
      select 
        sum(stock_locked)::int as initially_locked 
      from 
        stocks
    ), 
    cte2 as (
      update 
        stocks 
      set 
        stock_locked = stock_locked + stock_available,
        stock_available = 0
      returning 
        0 as dummy
    ),
    cte3 as (
      select 
        sum(stock_locked)::int as finally_locked 
      from 
        stocks
    )
  select 
    (cte3.finally_locked - initially_locked - dummy) 
  from 
    cte1, cte2, cte3;
$$;

这应该可行,但结果值表明两个选择都是在表 stocks 的初始值上执行的,因为差异为 0。

select lock_all3();

lock_all3|
---------|
        0|

但是,cte2 被执行,因为最终情况表明所有可用的股票都被锁定了。

select * from stocks;

id|stock_available|stock_locked|
--|---------------|------------|
 1|              0|         160|
 2|              0|         170|
 3|              0|         150|
 4|              0|         100|
 5|              0|         200|
 6|              0|         130|
 7|              0|         100|
 8|              0|         150|
 9|              0|         100|

这个近似值肯定有问题。

【问题讨论】:

  • 所有 CTEs 在 Postgresql 中总是使用原始数据快照,真正的数据更改是在执行结束时完成的。所以如果你想让第二个 CTE 看到第一个的结果,那么第一个必须带有 RETURNING,第二个必须从第一个中选择。
  • 谢谢!我不知道

标签: postgresql local-variables postgresql-12


【解决方案1】:

我认为诀窍是计算更新之前的总数。

仅使用 SQL

  DROP TABLE x;
SELECT sum(stock_available) as total_moved
  INTO TEMP TABLE x
  FROM stocks as total_moved; 
UPDATE stocks 
   SET stock_locked = stock_available + stock_locked,
       stock_available = 0;
SELECT * from x;

total_moved|
-----------|
       1050|

使用存储过程

create or replace function lock_all ()
returns int
language plpgsql as $$
declare
  _total int;
begin
    --calculate total before update
    SELECT sum(stock_available)
      INTO _total
      FROM stocks;

    UPDATE stocks
       SET stock_locked = stock_locked + stock_available,
           stock_available = 0;

    return _total;
end;
$$;

从 lock_all 中选择 *;

lock_all|
--------|
    1050|

从股票中选择*;

id|stock_available|stock_locked|
--|---------------|------------|
 1|              0|         160|
 2|              0|         170|
 3|              0|         150|
 4|              0|         100|
 5|              0|         200|
 6|              0|         130|
 7|              0|         100|
 8|              0|         150|
 9|              0|         100|

【讨论】:

  • 有趣的方法@bfris。在我看来,创建一个临时表来存储数字听起来很昂贵,但肯定是一个古老的想法,正如我在其他地方读到的那样,PostgreSQL 中的临时表在计算方面很便宜。谢谢!
  • 临时表不是我的首选。但是它们对于某些问题可能非常有用。对于非特权用户,SQL Only 方法可能是解决此问题的少数方法之一。
【解决方案2】:

我不认为这样的结构可以在 Postgres 中工作;即使在 MySQL 中,也不可能——或者至少是安全的——以这种方式使用变量。

认为我了解您希望在执行更新之前跟踪可用的总库存。为此只使用两个不同的查询可能会更简单:

select sum(stock_available) total from stocks returning total into _total;
update stocks set stock_locked = stock_locked + stock_available;

如果您想避免竞争条件,可以将它们包装在事务中,或将其编写为单个语句:

with cte as (update stocks set stock_locked = stock_locked + stock_available)
select sum(stock_available) total from stocks returning total into _total;

【讨论】:

  • 我正在尝试一个 for-in-select-loop,它类似于您的解决方案。
  • 但是,我们的想法是跟踪库存 moved 从可用到锁定,而不是最后的所有 stock_available,应该是 0。
【解决方案3】:

这是一个解决方案,它执行的updates 与表stocks 中的行数一样多。它有效,但这是我试图避免的那种解决方案。

create or replace function lock_all ()
returns int
language plpgsql as $$
declare
  _amount int;
  _total int;
 r record;
begin
  -- initialize counter
  _total = 0;

    -- select stocks rows
    for r in (
        select * from stocks
    ) 
    loop
        _amount = r.stock_available;
    
      -- update the stock_fulfilled column in of_user_order_line_supply
      update stocks
      set        stock_locked = stock_locked + _amount
      where  id = r.id;
     
     _total = _total + _amount;
    end loop;

return _total;
end;
$$;

select lock_all();
select * from stocks;

lock_all|
--------|
    1050|

【讨论】:

    猜你喜欢
    • 2011-06-16
    • 2012-01-18
    • 2022-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-25
    相关资源
    最近更新 更多