更新一个表中具有1个匹配项,多个匹配项或其他表中的0个匹配项的所有行

时间:2017-03-04 01:19:04

标签: 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 foreign key between huge and smallish on name
-- This is intentional

我想做三件事:

  1. 更新huge中的所有行,将day设置为smallishhuge中的行name中只有smallish的{​​{1}} }}。
  2. 更新huge中的所有行,使errno1error_message:name not in smallish huge中的行在smallish
  3. 中有一个名字
  4. 更新huge中的所有行,使errno 2error_message :name has multiple rows in smallish huge中的行name smallish
  5. 中的一个{{1}}

    我使用下面的DML似乎可以正常工作,但其中两个DML在输出中提供了全表扫描,否则看起来不直观。

    此外,在一个陈述中完成所有这一切将非常酷,而不是三个。

    更新

    以下似乎有效,看起来有些直观,但解释计划显示了巨大的全表扫描:

    UPDATE huge h_out
    SET (day, errno, error_message) = (
        select CASE WHEN DAY IS NOT NULL AND count = 1 THEN day ELSE NULL END as bill_day,
               CASE WHEN day IS NULL THEN 1 WHEN count > 1 THEN 2 ELSE NULL END AS errno,
               CASE WHEN day IS NULL THEN name || ' not in smallish' WHEN count > 1 THEN name || ' has multiple rows in smallish' ELSE NULL END as error_message
        FROM (
            select dhuge.name, max(smallish.day) as day, count(dhuge.name) as count
            from (select distinct huge.name from huge) dhuge left join smallish on dhuge.name = smallish.name
            group by dhuge.name
        ) h_in
        WHERE h_out.name = h_in.name
    );
    

    原件:

    -- 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;
    

2 个答案:

答案 0 :(得分:2)

要在一个语句中执行此操作,您可以使用merge

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';

答案 1 :(得分:0)

上面的合并看起来很有希望,但是您不能将其用于名称不匹配(语法限制)-为此需要单独的UPDATE,并且可能无法避免全表扫描。 我喜欢使用“ IN(子查询)”,因为它通常会提供很好的解释计划。 试试这些:

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);

为huge_name_indx设置BITMAP索引也可能有所帮助。

顺便说一句,汤姆·凯特(Tom Kyte)建议用更新的数据创建一个新表,而不是更新现有的大表(https://asktom.oracle.com/pls/asktom/asktom.search?tag=how-to-update-millions-or-records-in-a-table-200211)。