在PostgreSQL

时间:2017-05-19 10:15:16

标签: 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         8
1    B         1
1    B         1
2    C         2
2    C         2
2    C         4
2    C         4
2    C         4

按之前的非缺失值复制金额。

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

update tableA set amount = lag(amount); 

使用PostgreSQL可以做什么?

2 个答案:

答案 0 :(得分:2)

你可以SELECT想要UPDATE,但没有(简单)方法来实际执行UPDATE,因为表狐狸没有主键(但是)。

CREATE TABLE fox (
    id integer NOT NULL,
    product text NOT NULL,
    amount integer
);

用一些数据填充狐狸。

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;  

产生之前有NULL的行,现在拥有相应的amount

 id | product | amount 
----+---------+--------
  1 | A       |      8
  1 | B       |      1
  2 | C       |      2
  2 | C       |      4
  2 | C       |      4

但是您无法使用此数据进行更新,因为(id, product)仍未对行进行唯一标识 - 这意味着您无法编写唯一标识行的WHERE条件。 WHERE子句如何知道是否将UPDATE中的金额更改为2或4?在(id, product) = (2, 'C')的{​​{1}}中无法区分WHERE的多行。

让我们给狐狸一把钥匙。

UPDATE

现在我们可以通过ALTER TABLE fox ADD COLUMN IF NOT EXISTS pkey serial ; ALTER TABLE fox ADD PRIMARY KEY (pkey) ; 确定行。

PRIMARY KEY 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 中使用pkey

UPDATE

检查结果是否正常:

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

添加PRIMARY KEY的替代方法

每个表都应该始终有一个主键。 如果您坚持不拥有,那么您也可以使用当时非空数量和而不是ROLLBACK 计算行,您可以 {{1} } 进入您的表格,然后UPDATE 删除没有金额的行。通过这种方式,您可以添加一个唯一的主键。当然INSERTDELETE FROM fox WHERE amount IS NULL打包成UPDATE,以免干扰同时运行的其他事务。例如,在您使用DELETETRANSACTION所有NULL之前计算数据为INSERT之后,另一个事务添加SELECT数量的行。在这种情况下,您会错过同时添加的DELETE行(由于并发导致数据丢失;请考虑ACID)。 但是,无论如何,丢失的主键可能会在以后咬你。

答案 1 :(得分:0)

不知道什么定义&#34;之前的行&#34;一切都是猜测。但您可以使用anonymous block做您想做的事情,只需进行更改:

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;