使用不同的值更新表中的多个行

时间:2016-06-30 15:09:43

标签: database postgresql sql-update multiple-columns

我有以下架构:

DROP SCHEMA IF EXISTS s CASCADE;  
CREATE SCHEMA s;

CREATE TABLE "s"."t1"
(
    "c1" BigSerial PRIMARY KEY,
    "c2" BigInt NOT NULL,
    "c3" BigInt
)
WITH (OIDS=FALSE);

INSERT INTO s.t1 (c2, c3) VALUES (10, 100);
INSERT INTO s.t1 (c2, c3) VALUES (20, 200);
INSERT INTO s.t1 (c2, c3) VALUES (30, 300);
INSERT INTO s.t1 (c2, c3) VALUES (40, 400);

PREPARE updateplan (BigInt, BigInt) AS 
    update s.t1 
    SET c3 = $2
    WHERE c2 = $1;

EXECUTE updateplan (20, 250);
PREPARE updatearrayplan(BigInt[], BigInt[]) AS
    for i in size($1)
    DO  
        update s.t1
        SET c3 = $2[$i]
        WHERE c2 = $1[$i]
    END FOR    

EXECUTE updatearrayplan({20, 30}, {275, 375})
/* 20, 200 -> 20, 275 */
/* 30, 300 -> 30, 375 */

执行updatearrayplan后,我希望行具有这些值20 - > 275,30> 375

有没有办法更新多个行,并将不同的列值作为数组传入。还可以保证数组的顺序得以维持。

2 个答案:

答案 0 :(得分:1)

尝试:

WITH arrays AS( 
    SELECT * from 
    unnest(
         ARRAY[20, 30], 
         ARRAY[275, 375]
    ) as xy(x,y)
)
UPDATE t1 
SET c3 = a.y 
FROM arrays a
WHERE c2 = a.x;

在此处查看unnest功能的说明:click

修改

  

@kordiroko对不起。我试着整天修改你的解决方案。   无法使其发挥作用。

可能是你有一个较旧的PostgreSQL版本。我在9.5版本上进行了测试,只需几分钟就能完成它,只需复制/粘贴并更改查询中的两个参数:

create table t1(
  c2 BIGINT,
  c3 bigint
);


insert into t1( c2, c3 )
select x, x * 100 
from generate_series( 1,1000000 ) x;

CREATE OR REPLACE FUNCTION updatefunc1(BigInt[], BigInt[])
RETURNS void as $$
BEGIN
    FOR i IN array_lower($1, 1) .. array_upper($1, 1)
    LOOP  
        update t1
        SET c3 = $2[i]
        WHERE c2 = $1[i];
    END LOOP;  
END;    
$$
LANGUAGE plpgsql;


CREATE OR REPLACE FUNCTION updatefunc2(BigInt[], BigInt[])
RETURNS void as $$
BEGIN
    WITH arrays AS( 
        SELECT * from 
        unnest( $1, $2  ) as xy(x,y)
    )
    UPDATE t1 
    SET c3 = a.y 
    FROM arrays a
    WHERE c2 = a.x;
END;    
$$
LANGUAGE plpgsql;


select updatefunc1(ARRAY[20], ARRAY[275]);

select updatefunc2(ARRAY[30], ARRAY[555]);

select * from t1 where c2 in (20,30);
  

如果这是正确的或有更好的解决方案,请告诉我。

这是非常正确的但是......它有点慢。

我测试了你的功能只有100条记录:

select updatefunc1(
    array( select * from generate_series(1,100)),
    array( select 22222 from generate_series(1,100))
);

花了12秒钟:

  

结果(成本= 20.00..20.31行= 1宽度= 0)(实际   时间= 12259.095..12259.096 rows = 1次循环= 1)输出:   updatefunc1(($ 0):: bigint [],($ 1):: bigint [])InitPlan 1(返回$ 0)

现在将它与我的功能进行比较,但是对于100.000记录:

select updatefunc2(
    array( select * from generate_series(1,100000)),
    array( select 22222 from generate_series(1,100000))
);

结果是1秒150毫秒:

  

结果(成本= 20.00..20.31行= 1宽度= 0)(实际   time = 1150.018..1150.123 rows = 1 loops = 1)输出:   updatefunc2(($ 0):: bigint [],($ 1):: bigint [])InitPlan 1(返回$ 0)

以上结果表明,您的功能是:

  

(12/100)/(1.150 / 100000)= 10434,78

次slooooooooooooooooooooooooooweeeeeeeeeeeeeeeeeeeeeeeeer,
在%中,这只是 1043400%更慢

编辑2

  

我的版本是9.2.15。它会引发语法错误

以下版本应该适用于PostgreSQL的早期版本:

CREATE OR REPLACE FUNCTION updatefunc3(BigInt[], BigInt[])
RETURNS void as $$
BEGIN
    WITH arrays AS( 
        SELECT arr1[ rn ] as x, arr2[ rn ] as y 
        FROM (
            SELECT $1 as arr1, $2 as arr2, generate_subscripts($1, 1) As rn
        ) x
    )
    UPDATE t1 
    SET c3 = a.y 
    FROM arrays a
    WHERE c2 = a.x;
END;    
$$
LANGUAGE plpgsql;

select updatefunc3(ARRAY[40,82,77], ARRAY[333,654]);

select * from t1 where c2 in (40,82,77);

uptadint 100,000行的速度测试是:

select updatefunc3(
    array( select * from generate_series(1,100000)),
    array( select 22222 from generate_series(1,100000))
);
  

结果(成本= 20.00..20.31行= 1宽度= 0)(实际   time = 1361.358 .. 1361.460 rows = 1 loops = 1)输出:   updatefunc3(($ 0):: bigint [],($ 1):: bigint [])InitPlan 1(返回$ 0)

更新100k行的时间低于1.5秒

编辑3

   @kordiko:你能告诉我为什么你的查询要好得多。   我的函数遍历每一行并逐个更新元素。   您的功能似乎也是这样做的。这就是全部   在查询中同时更新等效行。

这是因为我的函数运行只有一个更新命令,无论数组中的元素数量是多少,而您的函数逐个更新元素 - 对于100个元素,它运行100个更新命令。对于1000个元素,它运行1000个更新命令 我已经在一个包含1000000行的表上完成了测试,但是没有任何索引。在我的函数中,更新仅读取表内容一次(执行全表扫描),并更新匹配行。您的功能执行100次更新,每次更新都进行全表扫描 如果您在col2上创建并编制索引,那么函数的速度会急剧增加,请参阅下面的测试(请注意,此测试中的许多元素从100增加到100000:

create INDEX t1_c2_ix on t1( c2 );

select updatefunc1(
    array( select * from generate_series(1,100000)),
    array( select 22222 from generate_series(1,100000))
);

Result  (cost=20.00..20.31 rows=1 width=0) (actual time=**3430.536**..3430.636 rows=1 loops=1)
  Output: updatefunc1(($0)::bigint[], ($1)::bigint[])
  InitPlan 1 (returns $0)

现在时间只有约3.5秒。

并在创建索引后测试我的函数:

select updatefunc3(
    array( select * from generate_series(1,100000)),
    array( select 22222 from generate_series(1,100000))
);

结果(成本= 20.00..20.31行= 1宽度= 0)(实际时间= 1270.619。 .1270.724行= 1个循环= 1)   输出:updatefunc3(($ 0):: bigint [],($ 1):: bigint [])   InitPlan 1(返回$ 0)

时间保持不变,但仍然比你的功能快100%。

答案 1 :(得分:0)

我的回答:

CREATE OR REPLACE FUNCTION s.updatefunc1(BigInt[], BigInt[])
RETURNS void as $$
BEGIN
    FOR i IN array_lower($1, 1) .. array_upper($1, 1)
    LOOP  
        update s.t1
        SET c3 = $2[i]
        WHERE c2 = $1[i];
    END LOOP;  
END;    
$$
LANGUAGE plpgsql;

select s.updatefunc1(ARRAY[20], ARRAY[275]);  

这确实有效。我得到了我想要的答案:

   SELECT c2, c3 FROM s.t1;
   c2 | c3  
   ----+-----
   10 | 100
   30 | 300
   40 | 400
   20 | 275  --> Updated

如果这是正确的或有更好的解决方案,请告诉我。