使用一个子查询更新多列

Updating multiple columns with one subquery

我在 sqlite 中有一个经典的父子关系:

create table tq84_parent (
  id                int primary key,
  avg_val           float,
  min_val           float,
  max_val           float
);

create table tq84_child (
  id_parent         int references tq84_parent,
  val               int
);

其中填写如下

insert into tq84_parent (id) values(1);
insert into tq84_parent (id) values(2);

insert into tq84_child values (1,  1);
insert into tq84_child values (1,  2);
insert into tq84_child values (1,  3);

insert into tq84_child values (2,  6);
insert into tq84_child values (2,  7);
insert into tq84_child values (2,  8);
insert into tq84_child values (2,  9);
insert into tq84_child values (2, 10);

现在,我想更新 tq84_parent 其列 avg_valmin_valmax_val 反映其子项 [=39= 的相应聚合值].

我可以用这个语句来实现:

update 
  tq84_parent
set
  avg_val = (select avg(val) from tq84_child where id_parent = id),
  min_val = (select min(val) from tq84_child where id_parent = id),
  max_val = (select max(val) from tq84_child where id_parent = id);

结果如愿:

.mode columns

select * from tq84_parent;

1           2.0         1.0         3.0
2           8.0         6.0         10.0

然而,我担心随着 tq84_parenttq84_child 中记录数量的增长,这种语句的性能不是最佳的,因为几乎相同的查询被进行了三次而不是一次。

所以,我想选择

update
  tq84_parent
set
 (
  avg_val,
  min_val,
  max_val
 )
= (select
     avg(val),
     min(val),
     max(val)
   from
     tq84_child
   where
     id_parent = id
);

这行不通。

那么,有没有办法重写我的查询?

由于 idid_parent 是主键和外键,这意味着 id 是唯一的并且 id_parent 总是引用现有的 ID。

这意味着您可以使用 REPLACE 来更改 table,因为唯一键总是被违反(也就是不会进行任何插入),并且 REPLACE 可以采用子查询和一次设置多个列;

REPLACE INTO tq84_parent (id, avg_val, min_val, max_val)
  SELECT id_parent, AVG(val), MIN(val), MAX(val)
  FROM tq84_child
  GROUP BY id_parent;

An SQLfiddle to test with.

注意:正如 CL 指出的那样,未在查询中设置的字段将具有其默认值,因为这些行是 replaced,而不是 updated。这意味着如果您在 table 中有其他字段需要保持不变,您的旧方法仍然是这样做的方式。

查询执行的实际速度主要取决于需要完成的磁盘数量I/O。

对于上面显示的数据库架构,每个子查询都需要扫描整个 table。 如果在计算聚合值所需的所有列上都有一个覆盖索引,则每个子查询只需要扫描索引的一小部分,因此在实践中,在为相同的 ID:

CREATE INDEX xxx ON tq84_child(id_parent, val);