Sql 正在更新一个表中有1个匹配项、多个匹配项或其他表中有0个匹配项的所有行

Sql 正在更新一个表中有1个匹配项、多个匹配项或其他表中有0个匹配项的所有行,sql,oracle,Sql,Oracle,我有两张桌子: CREATE TABLE huge ( id INT PRIMARY KEY, name VARCHAR2(100), day INT, errno INT, error_message VARCHAR2(100) ); CREATE TABLE smallish ( id INT PRIMARY KEY, name VARCHAR2(100), day int ); -- Note the lack of a for

我有两张桌子:

CREATE TABLE huge ( id INT PRIMARY KEY, name VARCHAR2(100), day INT, errno INT, error_message VARCHAR2(100) ); CREATE TABLE smallish ( id INT PRIMARY KEY, name VARCHAR2(100), day int ); -- Note the lack of a foreign key between huge and smallish on name -- This is intentional 原件:

-- Problem #1 UPDATE huge h SET (day) = ( SELECT MIN(day) FROM smallish s WHERE h.name = s.name GROUP BY s.name HAVING count(1) = 1 ) WHERE EXISTS ( SELECT null FROM smallish s WHERE s.name = h.name ); -- Problem #2 Explain plan shows a full table scan on huge UPDATE huge h_out SET (errno, error_message) = ( select 1, h_out.name || ' not in smallish' AS error_message FROM DUAL ) WHERE NOT EXISTS ( SELECT NULL FROM smallish s WHERE s.name = h_out.name ); -- Problem #3 Explain plan shows a full table scan on huge UPDATE huge h SET (errno, error_message) = ( SELECT 2, h.name || ' has multiple rows' FROM dual ) WHERE EXISTS ( SELECT s.name FROM smallish s WHERE h.name = s.name GROUP BY s.name HAVING count(1) > 1 ); 复制:

DROP TABLE huge; DROP TABLE smallish; CREATE TABLE huge ( id INT PRIMARY KEY, name VARCHAR2(100), day INT, errno INT, error_message VARCHAR2(100) ); CREATE TABLE smallish ( id INT PRIMARY KEY, name VARCHAR2(100), day int ); create index huge_name_indx ON huge (name); create index smallish_name_indx ON smallish (name); insert into huge values (1, 'good1', null, 0, null); insert into huge values (2, 'good1', null, 0, null); insert into huge values (3, 'good1', null, 0, null); insert into huge values (4, 'good1', null, 0, null); insert into huge values (5, 'good2', null, 0, null); insert into huge values (6, 'good2', null, 0, null); insert into huge values (7, 'good2', null, 0, null); insert into huge values (8, 'good2', null, 0, null); insert into huge values (9, 'double1', null, 0, null); insert into huge values (10, 'double1', null, 0, null); insert into huge values (11, 'double1', null, 0, null); insert into huge values (12, 'double1', null, 0, null); insert into huge values (13, 'double2', null, 0, null); insert into huge values (14, 'double2', null, 0, null); insert into huge values (15, 'double2', null, 0, null); insert into huge values (16, 'double2', null, 0, null); insert into huge values(17, 'notin1', null, 0, null); insert into huge values(18, 'notin1', null, 0, null); insert into huge values(19, 'notin1', null, 0, null); insert into huge values(20, 'notin1', null, 0, null); insert into huge values(21, 'notin2', null, 0, null); insert into huge values(22, 'notin2', null, 0, null); insert into huge values(23, 'notin2', null, 0, null); insert into huge values(24, 'notin2', null, 0, null); insert into smallish values (1, 'good1', 1); insert into smallish values (2, 'good2', 2); insert into smallish values (3, 'double1', 3); insert into smallish values (4, 'double1', 4); insert into smallish values (5, 'double2', 5); insert into smallish values (6, 'double2', 6); commit;
要在一条语句中完成此操作,可以使用merge:


上面的Merge看起来很有希望,但您不能将其用于不匹配名称的语法限制-这需要单独的更新,并且可能无法避免完整表扫描。 我喜欢在子查询中使用,因为它通常提供非常好的解释计划。 试试这些:

UPDATE huge SET ...
WHERE name IN (SELECT name FROM smallish GROUP BY name HAVING count(*) = 1);

UPDATE huge SET ...
WHERE name IN (SELECT name FROM smallish GROUP BY name HAVING count(*) > 1);

UPDATE huge SET ...
WHERE name NOT IN (SELECT name FROM smallish);
制作一个巨大的位图索引也会有所帮助


顺便说一句,Tom Kyte建议使用更新的数据创建一个新表,而不是更新现有的大型表

在Oracle中,由于使用了两次更新,它似乎给出了一个错误。蟾蜍说找到了“更新”,需要“插入”。有什么想法吗?我想你是想把h.name-s.name括在括号中,并在case语句中为匹配的错误消息添加一个END;有多少行在被更新?@BobC我想随着时间的推移,可能会有数亿行在被更新,尽管对于给定的更新,应该只有100-1000万行。我列举的例子有点做作;实际上,where子句中将使用另一个名为batch_id的列来限制行数为1-1000万行。我问的原因是,有时可以考虑转换策略,而不是修改策略。更新是一项非常昂贵的操作。不仅有更新本身,还有撤消和重做。所以一切都是三重书写。但是,如果可以使用新数据重新写入表或分区,则可以利用直接路径加载、并行性等技术。这通常比执行更新快几个数量级。
merge into huge h
    using (select name, count(*) as cnt, max(day) as day
           from smallish
           group by name
          ) s
    on h.name = s.name
when matched then update
    set day = (case when s.cnt = 1 then s.day else h.day end),
        errno = (case when s.cnt > 1 then 2 else h.errno end),
        error_message = (case when s.cnt > 1 then s.name || ' has multiple rows in smallish' else error_message end)
when not matched then update
    set errno = 1,
        error_message = h.name || ' not in smallish';
UPDATE huge SET ...
WHERE name IN (SELECT name FROM smallish GROUP BY name HAVING count(*) = 1);

UPDATE huge SET ...
WHERE name IN (SELECT name FROM smallish GROUP BY name HAVING count(*) > 1);

UPDATE huge SET ...
WHERE name NOT IN (SELECT name FROM smallish);