在PostgreSQL中将值从一行复制到另一行

在PostgreSQL中将值从一行复制到另一行,postgresql,Postgresql,我有一张这样的桌子: id product amount 1 A 6 1 A 8 1 A 1 B 1 1 B 2 C 2 2 C 2 C 4 2 C 2 C id product amount 1 A 6 1 A 8 1 A

我有一张这样的桌子:

id   product   amount
1    A         6
1    A         8
1    A         
1    B         1
1    B         
2    C         2
2    C       
2    C         4
2    C    
2    C 
id   product   amount
1    A         6
1    A         8
1    A         8
1    B         1
1    B         1
2    C         2
2    C         2
2    C         4
2    C         4
2    C         4
我需要这样做:

id   product   amount
1    A         6
1    A         8
1    A         
1    B         1
1    B         
2    C         2
2    C       
2    C         4
2    C    
2    C 
id   product   amount
1    A         6
1    A         8
1    A         8
1    B         1
1    B         1
2    C         2
2    C         2
2    C         4
2    C         4
2    C         4
按以前的非缺失值复制金额

我尝试使用
lag()
函数。但是,更新中不允许使用聚合函数
lag()

update tableA set amount = lag(amount); 

使用PostgreSQL我能做什么?

在不知道“前几行”的定义的情况下,一切都是猜测。但您可以使用a来做您想做的事情,只需进行更改:

CREATE TEMPORARY TABLE test_lag AS
    SELECT column1 AS id, column2 AS product, column3 AS amount FROM (
        VALUES (1, 'A', 6), 
        (1, 'A', 8),
        (1, 'A', NULL),
        (1, 'B', 1),
        (1, 'B', NULL),
        (2, 'C', 2),
        (2, 'C', NULL),
        (2, 'C', 4),
        (2, 'C', NULL),
        (2, 'C', NULL)) AS tmp;

DO $$
BEGIN
    --Loop until update all null amounts
    --Why we need this? It's because PostgreSQL don't supports IGNORE NULLS clause on lag()
    LOOP
        WITH tmp AS (
            SELECT ctid, lag(amount) OVER() AS last_amount FROM test_lag ORDER BY id, product -- You MUST change this ORDER to right columns (What's previous row?)
        )
        UPDATE test_lag SET amount = tmp.last_amount FROM tmp WHERE test_lag.ctid = tmp.ctid AND amount IS NULL;
        IF NOT FOUND THEN
            EXIT;
        END IF;    
    END LOOP;

END $$;

SELECT * FROM test_lag ORDER BY id, product, amount;

如果不知道“前几行”的定义,一切都只是猜测。但您可以使用a来做您想做的事情,只需进行更改:

CREATE TEMPORARY TABLE test_lag AS
    SELECT column1 AS id, column2 AS product, column3 AS amount FROM (
        VALUES (1, 'A', 6), 
        (1, 'A', 8),
        (1, 'A', NULL),
        (1, 'B', 1),
        (1, 'B', NULL),
        (2, 'C', 2),
        (2, 'C', NULL),
        (2, 'C', 4),
        (2, 'C', NULL),
        (2, 'C', NULL)) AS tmp;

DO $$
BEGIN
    --Loop until update all null amounts
    --Why we need this? It's because PostgreSQL don't supports IGNORE NULLS clause on lag()
    LOOP
        WITH tmp AS (
            SELECT ctid, lag(amount) OVER() AS last_amount FROM test_lag ORDER BY id, product -- You MUST change this ORDER to right columns (What's previous row?)
        )
        UPDATE test_lag SET amount = tmp.last_amount FROM tmp WHERE test_lag.ctid = tmp.ctid AND amount IS NULL;
        IF NOT FOUND THEN
            EXIT;
        END IF;    
    END LOOP;

END $$;

SELECT * FROM test_lag ORDER BY id, product, amount;

您可以
选择
您想要
更新的内容
,但是没有(简单的)方法来实际执行
更新
,因为表fox还没有主键

用一些数据填充狐狸

INSERT INTO fox VALUES 
(1, 'A', 6),
(1, 'A', 8),
(1, 'A', NULL),
(1, 'B', 1),
(1, 'B', NULL),
(2, 'C', 2),
(2, 'C', NULL),
(2, 'C', 4),
(2, 'C', NULL),
(2, 'C', NULL), 
(3, 'What does the fox say?', 5);
查询

WITH ranks (rank, id, product, amount) AS (  
  SELECT ROW_NUMBER() OVER (), id, product, amount FROM foo  
)  
SELECT r.id, r.product,  
  (SELECT amount FROM ranks  
    WHERE id = r.id AND product = r.product  
      AND rank < r.rank AND amount IS NOT NULL  
    ORDER BY amount DESC LIMIT 1  
  )  
FROM ranks r WHERE r.amount IS NULL ORDER BY 1, 2, 3;  
但是您不能使用此数据进行更新,因为行仍然不是由
(id,product)
唯一标识的-这意味着您无法编写一个
,其中
条件唯一标识您的行。
WHERE
子句如何知道在
更新中是将金额更改为2还是4?
(id,product)=(2,'C')
的多行在
更新的
中的
中无法区分

BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE ;

WITH nulls AS ( 
      SELECT pkey, id, product  
      FROM fox  
      WHERE amount IS NULL 
    ), changes AS ( 
    SELECT pkey,  
      (SELECT amount FROM fox  
       WHERE id = n.id AND product = n.product  
         AND n.pkey > pkey AND amount IS NOT NULL  
       ORDER BY pkey DESC LIMIT 1)  
    FROM nulls n 
) UPDATE fox f SET amount = c.amount FROM changes c WHERE f.pkey = c.pkey ;
让我们给狐狸一个主键

ALTER TABLE fox ADD COLUMN IF NOT EXISTS pkey serial ;  
ALTER TABLE fox ADD PRIMARY KEY (pkey) ;  
现在我们可以通过
主键pkey
来识别行

WITH nulls AS ( 
  SELECT pkey, id, product  
  FROM fox  
  WHERE amount IS NULL 
) 
SELECT pkey,  
  id, product,  -- you can leave these out in your UPDATE: pkey is UNIQUE
  (SELECT amount FROM fox  
   WHERE id = n.id AND product = n.product  
     AND n.pkey > pkey AND amount IS NOT NULL  
   ORDER BY pkey DESC LIMIT 1)  
FROM nulls n ORDER BY 1, 2, 3, 4;
显示要进行的更改

 pkey | id | product | amount 
------+----+---------+--------
    3 |  1 | A       |      8
    5 |  1 | B       |      1
    7 |  2 | C       |      2
    9 |  2 | C       |      4
   10 |  2 | C       |      4
我们可以在
UPDATE
中使用
pkey

BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE ;

WITH nulls AS ( 
      SELECT pkey, id, product  
      FROM fox  
      WHERE amount IS NULL 
    ), changes AS ( 
    SELECT pkey,  
      (SELECT amount FROM fox  
       WHERE id = n.id AND product = n.product  
         AND n.pkey > pkey AND amount IS NOT NULL  
       ORDER BY pkey DESC LIMIT 1)  
    FROM nulls n 
) UPDATE fox f SET amount = c.amount FROM changes c WHERE f.pkey = c.pkey ;
检查结果是否正常:

SELECT * FROM fox ORDER BY 1, 2, 3, 4;
并相应地使用
COMMIT
ROLLBACK
接受

添加主键的备选方案

每个表都应该始终有一个主键。 如果您坚持不拥有一个,那么您也可以使用其当时的非空金额计算行,并而不是
更新
它们,您可以
将它们插入表中
然后
从福克斯删除金额为空的行
删除没有金额的行。这样您就可以添加一个唯一的主键。当然,
UPDATE
DELETE
被打包成一个
事务
,这样就不会干扰同时运行的其他事务。例如,在使用
SELECT
计算要插入的
数据之后,以及在
DELETE
之前,另一笔交易添加了
NULL
金额的行。在这种情况下,您将错过同时添加的
NULL amount
行(并发导致的数据丢失;请考虑ACID)。
但是丢失的主键以后可能会咬到你。

你可以选择你想要更新的内容,但是没有(简单的)方法来实际执行更新,因为fox表还没有主键

用一些数据填充狐狸

INSERT INTO fox VALUES 
(1, 'A', 6),
(1, 'A', 8),
(1, 'A', NULL),
(1, 'B', 1),
(1, 'B', NULL),
(2, 'C', 2),
(2, 'C', NULL),
(2, 'C', 4),
(2, 'C', NULL),
(2, 'C', NULL), 
(3, 'What does the fox say?', 5);
查询

WITH ranks (rank, id, product, amount) AS (  
  SELECT ROW_NUMBER() OVER (), id, product, amount FROM foo  
)  
SELECT r.id, r.product,  
  (SELECT amount FROM ranks  
    WHERE id = r.id AND product = r.product  
      AND rank < r.rank AND amount IS NOT NULL  
    ORDER BY amount DESC LIMIT 1  
  )  
FROM ranks r WHERE r.amount IS NULL ORDER BY 1, 2, 3;  
但是您不能使用此数据进行更新,因为行仍然不是由
(id,product)
唯一标识的-这意味着您无法编写一个
,其中
条件唯一标识您的行。
WHERE
子句如何知道在
更新中是将金额更改为2还是4?
(id,product)=(2,'C')
的多行在
更新的
中的
中无法区分

BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE ;

WITH nulls AS ( 
      SELECT pkey, id, product  
      FROM fox  
      WHERE amount IS NULL 
    ), changes AS ( 
    SELECT pkey,  
      (SELECT amount FROM fox  
       WHERE id = n.id AND product = n.product  
         AND n.pkey > pkey AND amount IS NOT NULL  
       ORDER BY pkey DESC LIMIT 1)  
    FROM nulls n 
) UPDATE fox f SET amount = c.amount FROM changes c WHERE f.pkey = c.pkey ;
让我们给狐狸一个主键

ALTER TABLE fox ADD COLUMN IF NOT EXISTS pkey serial ;  
ALTER TABLE fox ADD PRIMARY KEY (pkey) ;  
现在我们可以通过
主键pkey
来识别行

WITH nulls AS ( 
  SELECT pkey, id, product  
  FROM fox  
  WHERE amount IS NULL 
) 
SELECT pkey,  
  id, product,  -- you can leave these out in your UPDATE: pkey is UNIQUE
  (SELECT amount FROM fox  
   WHERE id = n.id AND product = n.product  
     AND n.pkey > pkey AND amount IS NOT NULL  
   ORDER BY pkey DESC LIMIT 1)  
FROM nulls n ORDER BY 1, 2, 3, 4;
显示要进行的更改

 pkey | id | product | amount 
------+----+---------+--------
    3 |  1 | A       |      8
    5 |  1 | B       |      1
    7 |  2 | C       |      2
    9 |  2 | C       |      4
   10 |  2 | C       |      4
我们可以在
UPDATE
中使用
pkey

BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE ;

WITH nulls AS ( 
      SELECT pkey, id, product  
      FROM fox  
      WHERE amount IS NULL 
    ), changes AS ( 
    SELECT pkey,  
      (SELECT amount FROM fox  
       WHERE id = n.id AND product = n.product  
         AND n.pkey > pkey AND amount IS NOT NULL  
       ORDER BY pkey DESC LIMIT 1)  
    FROM nulls n 
) UPDATE fox f SET amount = c.amount FROM changes c WHERE f.pkey = c.pkey ;
检查结果是否正常:

SELECT * FROM fox ORDER BY 1, 2, 3, 4;
并相应地使用
COMMIT
ROLLBACK
接受

添加主键的备选方案

每个表都应该始终有一个主键。 如果您坚持不拥有一个,那么您也可以使用其当时的非空金额计算行,并而不是
更新
它们,您可以
将它们插入表中
然后
从福克斯删除金额为空的行
删除没有金额的行。这样您就可以添加一个唯一的主键。当然,
UPDATE
DELETE
被打包成一个
事务
,这样就不会干扰同时运行的其他事务。例如,在使用
SELECT
计算要插入的
数据之后,以及在
DELETE
之前,另一笔交易添加了
NULL
金额的行。在这种情况下,您将错过同时添加的
NULL amount
行(并发导致的数据丢失;请考虑ACID)。
但是丢失的主键以后可能会咬到你。

这里“上一行”的定义是什么?
按id、产品、随机()或ctid排序是的,类似于lag()函数。但是,我不能在更新中使用聚合函数lag()。您可以在CTE中使用lag()-问题是缺少排序列什么是CTE?我想我不能按id和product排序,因为id=2,product=C对应两个值:2和4这里“previous”行的定义是什么?
按id、product、random()或ctid排序我相信是的,类似于lag()函数。但是,我不能在更新中使用聚合函数lag()。您可以在CTE中使用lag()-问题是缺少排序列什么是CTE?我想我不能按id和product排序,因为id=2,product=C对应两个值:2和4函数只会用相邻的值填充
NULL
。存在多个
NULL
值。因此,您的查询需要运行
n
次,以填充一个值
x
后跟
n
NULL
值的序列。其他