• 一招教你数据仓库如何高效批量导入与更新数据


    摘要:GaussDB(DWS)支持的MERGE INTO功能,可以同时进行大数据量的更新与插入。对于数据仓库是一项非常重要的技术。

    本文分享自华为云社区《一招教你如何高效批量导入与更新数据》,原文作者:acydy。

    前言

    如果有一张表,我们既想对它更新,又想对它插入应该如何操作? 可以使用UPDATE和INSERT完成你的目标。

    如果你的数据量很大,想尽快完成任务执行,可否有其他方案?那一定不要错过GaussDB(DWS)的MERGE INTO功能。

    MERGE INTO 概念

    MERGE INTO是SQL 2003引入的标准。

    If a table T, as well as being updatable, is insertable-into, then rows can be inserted into it (subject to applicable Access Rules and Conformance Rules). The primary effect of an <insert statement> on T is to insert into T each of the zero or more rows contained in a specified table. The primary effect of a <merge statement> on T is to replace zero or more rows in T with specified rows and/or to insert into T zero or more specified rows, depending on the result of a <search condition> and on whether one or both of <merge when matched clause> and <merge when not matched clause> are specified.

    一张表在一条语句里面既可以被更新,也可以被插入。是否被更新还是插入取决于search condition的结果和指定的merge when matched clause(当condition匹配时做什么操作)和merge when not matched clause(当condition不匹配时做什么操作)语法。

    SQL 2008进行了扩展,可以使用多个MATCHED 和NOT MATCHED 。

    MERGE has been extended to support multiple MATCHED and NOT MATCHED clauses, each accompanied by a search condition, that gives much greater flexibility in the coding of complex MERGE statements to handle update conflicts.

    MERGE INTO 命令涉及到两张表。目标表:被插入或者更新的表。源表:用于跟目标表进行匹配的表,目标表的数据来源。

    MERGE INTO语句将目标表和源表中数据针对关联条件进行匹配,若关联条件匹配时对目标表进行UPDATE,无法匹配时对目标表执行INSERT。

    使用场景:当业务中需要将一个表中大量数据添加到现有表时,使用MERGE INTO 可以高效地将数据导入,避免多次INSERT+UPDATE操作。

    MERGE INTO 语法

    GaussDB(DWS) MERGE INTO 语法如下:

    MERGE INTO table_name [ [ AS ] alias ]
    USING { { table_name | view_name } | subquery } [ [ AS ] alias ]
    ON ( condition )
    [
      WHEN MATCHED THEN
      UPDATE SET { column_name = { expression | DEFAULT } |
              ( column_name [, ...] ) = ( { expression | DEFAULT } [, ...] ) } [, ...]
      [ WHERE condition ]
    ]
    [
      WHEN NOT MATCHED THEN
      INSERT { DEFAULT VALUES |
      [ ( column_name [, ...] ) ] VALUES ( { expression | DEFAULT } [, ...] ) [, ...] [ WHERE condition ] }
    ];
    • INTO 指定目标表。
    • USING 指定源表。源表可以是普通表,也可以是子查询。
    • ON 关联条件,用于指定目标表和源表的关联条件。
    • WHEN MATCHED 当源表和目标表中数据可以匹配关联条件时,选择WHEN MATCHED子句执行UPDATE操作。
    • WHEN NOT MATCHED 当源表和目标表中数据无法匹配关联条件时,选择WHEN NOT MATCHED子句执行INSERT操作。
      • WHEN MATCHED,WHEN NOT MATCHED 可以缺省一个,不能指定多个。
      • WHEN MATCHED,WHEN NOT MATCHED 可以使用WHERE进行条件过滤。
      • WHEN MATCHED,WHEN NOT MATCHED 顺序可以交换。

    实战应用

    首先创建好下面几张表,用于执行MREGE INTO 操作。

    gaussdb=# CREATE TABLE dst (
      product_id INT,
      product_name VARCHAR(20),
      category VARCHAR(20),
      total INT
    ) DISTRIBUTE BY HASH(product_id);
    
    gaussdb=# CREATE TABLE dst_data (
      product_id INT,
      product_name VARCHAR(20),
      category VARCHAR(20),
      total INT
    ) DISTRIBUTE BY HASH(product_id);
    
    gaussdb=# CREATE TABLE src (
      product_id INT,
      product_name VARCHAR(20),
      category VARCHAR(20),
      total INT
    ) DISTRIBUTE BY HASH(product_id);
    
    gaussdb=# INSERT INTO dst_data VALUES(1601,'lamaze','toys',100),(1600,'play gym','toys',100),(1502,'olympus','electrncs',100),(1501,'vivitar','electrnc',100),(1666,'harry potter','dvd',100);
    gaussdb=# INSERT INTO src VALUES(1700,'wait interface','books',200),(1666,'harry potter','toys',200),(1601,'lamaze','toys',200),(1502,'olympus camera','electrncs',200);
    gaussdb=# INSERT INTO dst SELECT * FROM dst_data;

    同时指定WHEN MATCHED 与WHEN NOT MATCHED

    • 查看计划,看下MERGE INTO是如何执行的。

    MERGE INTO转化成JOIN将两个表进行关联处理,关联条件就是ON后指定的条件。

    gaussdb=# EXPLAIN (COSTS off)
    MERGE INTO dst x
    USING src y
    ON x.product_id = y.product_id
    WHEN MATCHED THEN
      UPDATE SET product_name = y.product_name, category = y.category, total = y.total
    WHEN NOT MATCHED THEN
      INSERT VALUES (y.product_id, y.product_name, y.category, y.total);
     
                        QUERY PLAN
    --------------------------------------------------
      id |                operation
    -----+--------------------------------------------
       1 | ->  Streaming (type: GATHER)
       2 |    ->  Merge on dst x
       3 |       ->  Streaming(type: REDISTRIBUTE)
       4 |          ->  Hash Left Join (5, 6)
       5 |             ->  Seq Scan on src y
       6 |             ->  Hash
       7 |                ->  Seq Scan on dst x
    
      Predicate Information (identified by plan id)
     ------------------------------------------------
       4 --Hash Left Join (5, 6)
             Hash Cond: (y.product_id = x.product_id)
    (14 rows)

    为什么这里转化成了LEFT JOIN?

    由于需要在目标表与源表匹配时更新目标表,不匹配时向目标表插入数据。也就是源表的一部分数据用于更新目标表,另一部分用于向目标表插入。与LEFT JOIN语义是相似的。

       5 --Seq Scan on public.src y
             Output: y.product_id, y.product_name, y.category, y.total, y.ctid
             Distribute Key: y.product_id
       6 --Hash
             Output: x.product_id, x.product_name, x.category, x.total, x.ctid, x.xc_node_id
       7 --Seq Scan on public.dst x
             Output: x.product_id, x.product_name, x.category, x.total, x.ctid, x.xc_node_id
             Distribute Key: x.product_id
    • 执行MERGE INTO,查看结果。

    两张表在product_id是1502,1601,1666时可以关联,所以这三条记录被更新。src表product_id是1700时未匹配,插入此条记录。其他未修改。

    gaussdb=# SELECT * FROM dst ORDER BY 1;
     product_id | product_name | category  | total
    ------------+--------------+-----------+-------
           1501 | vivitar      | electrnc  |   100
           1502 | olympus      | electrncs |   100
           1600 | play gym     | toys      |   100       
           1601 | lamaze       | toys      |   100
           1666 | harry potter | dvd       |   100      
    (5 rows)
    
    gaussdb=# SELECT * FROM src ORDER BY 1;
     product_id |  product_name  | category  | total
    ------------+----------------+-----------+-------
           1502 | olympus camera | electrncs |   200
           1601 | lamaze         | toys      |   200       
           1666 | harry potter   | toys      |   200
           1700 | wait interface | books     |   200       
    (4 rows)
    
    gaussdb=# MERGE INTO dst x
    USING src y
    ON x.product_id = y.product_id
    WHEN MATCHED THEN
      UPDATE SET product_name = y.product_name, category = y.category, total = y.total
    WHEN NOT MATCHED THEN
      INSERT VALUES (y.product_id, y.product_name, y.category, y.total);
    MERGE 4
    gaussdb=# SELECT * FROM dst ORDER BY 1;
     product_id |  product_name  | category  | total
    ------------+----------------+-----------+-------
           1501 | vivitar        | electrnc  |   100  -- 未修改
           1502 | olympus camera | electrncs |   200  -- 更新
           1600 | play gym       | toys      |   100  -- 未修改
           1601 | lamaze         | toys      |   200  -- 更新
           1666 | harry potter   | toys      |   200  -- 更新
           1700 | wait interface | books     |   200  -- 插入
    (6 rows)
    • 查看具体UPDATE、INSERT个数

    可以通过EXPLAIN PERFORMANCE或者EXPLAIN ANALYZE查看UPDATE、INSERT各自个数。(这里仅显示必要部分)

    在Predicate Information部分可以看到总共插入一条,更新三条。

    在Datanode Information部分可以看到每个节点的信息。datanode1上更新2条,datanode2上插入一条,更新1条。

    gaussdb=# EXPLAIN PERFORMANCE
    MERGE INTO dst x
    USING src y
    ON x.product_id = y.product_id
    WHEN MATCHED THEN
      UPDATE SET product_name = y.product_name, category = y.category, total = y.total
    WHEN NOT MATCHED THEN
      INSERT VALUES (y.product_id, y.product_name, y.category, y.total);
     
      Predicate Information (identified by plan id)
     ------------------------------------------------
       2 --Merge on public.dst x
             Merge Inserted: 1
             Merge Updated: 3
     
                          Datanode Information (identified by plan id)
     ---------------------------------------------------------------------------------------
       2 --Merge on public.dst x
             datanode1 (Tuple Inserted 0, Tuple Updated 2)
             datanode2 (Tuple Inserted 1, Tuple Updated 1)  

    省略WHEN NOT MATCHED 部分。

    • 这里由于没有WHEN NOT MATCHED部分,在两个表不匹配时不需要执行任何操作,也就不需要源表这部分的数据,所有只需要inner join即可。
    gaussdb=# EXPLAIN (COSTS off)
    MERGE INTO dst x
    USING src y
    ON x.product_id = y.product_id
    WHEN MATCHED THEN
      UPDATE SET product_name = y.product_name, category = y.category, total = y.total;
                        QUERY PLAN
    --------------------------------------------------
      id |             operation
     ----+-----------------------------------
       1 | ->  Streaming (type: GATHER)
       2 |    ->  Merge on dst x
       3 |       ->  Hash Join (4,5)
       4 |          ->  Seq Scan on dst x
       5 |          ->  Hash
       6 |             ->  Seq Scan on src y
    
      Predicate Information (identified by plan id)
     ------------------------------------------------
       3 --Hash Join (4,5)
             Hash Cond: (x.product_id = y.product_id)
    (13 rows)
    • 执行后查看结果。MERGE INTO只操作了3条数据。
    gaussdb=# truncate dst;
    gaussdb=# INSERT INTO dst SELECT * FROM dst_data;
    gaussdb=# MERGE INTO dst x
    USING src y
    ON x.product_id = y.product_id
    WHEN MATCHED THEN
      UPDATE SET product_name = y.product_name, category = y.category, total = y.total;
    MERGE 3
    gaussdb=# SELECT * FROM dst;
     product_id |  product_name  | category  | total
    ------------+----------------+-----------+-------
           1501 | vivitar        | electrnc  |   100  -- 未修改
           1502 | olympus camera | electrncs |   200  -- 更新
           1600 | play gym       | toys      |   100  -- 未修改
           1601 | lamaze         | toys      |   200  -- 更新
           1666 | harry potter   | toys      |   200  -- 更新
    (5 rows)

    省略WHEN NOT MATCHED

    • 只有在不匹配时进行插入。结果中没有数据被更新。
    gaussdb=# EXPLAIN (COSTS off)
    MERGE INTO dst x
    USING src y
    ON x.product_id = y.product_id
    WHEN NOT MATCHED THEN
      INSERT VALUES (y.product_id, y.product_name, y.category, y.total);
                        QUERY PLAN
    --------------------------------------------------
      id |                operation
     ----+-----------------------------------------
       1 | ->  Streaming (type: GATHER)
       2 |    ->  Merge on dst x
       3 |       ->  Streaming(type: REDISTRIBUTE)
       4 |          ->  Hash Left Join (5, 6)
       5 |             ->  Seq Scan on src y
       6 |             ->  Hash
       7 |                ->  Seq Scan on dst x
    
      Predicate Information (identified by plan id)
     ------------------------------------------------
       4 --Hash Left Join (5, 6)
             Hash Cond: (y.product_id = x.product_id)
    (14 rows)
    
    gaussdb=# truncate dst;
    gaussdb=# INSERT INTO dst SELECT * FROM dst_data;
    gaussdb=# MERGE INTO dst x
    USING src y
    ON x.product_id = y.product_id
    WHEN NOT MATCHED THEN
      INSERT VALUES (y.product_id, y.product_name, y.category, y.total);
    MERGE 1
    gaussdb=# SELECT * FROM dst ORDER BY 1;
     product_id |  product_name  | category  | total
    ------------+----------------+-----------+-------
           1501 | vivitar        | electrnc  |   100  -- 未修改
           1502 | olympus        | electrncs |   100  -- 未修改
           1600 | play gym       | toys      |   100  -- 未修改
           1601 | lamaze         | toys      |   100  -- 未修改
           1666 | harry potter   | dvd       |   100  -- 未修改
           1700 | wait interface | books     |   200  -- 插入
    (6 rows)

    WHERE过滤条件

    语义是在进行更新或者插入前判断当前行是否满足过滤条件,如果不满足,就不进行更新或者插入。如果对于字段不想被更新,需要指定过滤条件。

    下面例子在两表可关联时,只会更新product_name = 'olympus’的行。在两表无法关联时且源表的product_id != 1700时才会进行插入。

    gaussdb=# truncate dst;
    gaussdb=# INSERT INTO dst SELECT * FROM dst_data;
    gaussdb=# MERGE INTO dst x
    USING src y
    ON x.product_id = y.product_id
    WHEN MATCHED THEN
      UPDATE SET product_name = y.product_name, category = y.category, total = y.total
      WHERE x.product_name = 'olympus'
    WHEN NOT MATCHED THEN
      INSERT VALUES (y.product_id, y.product_name, y.category, y.total) WHERE y.product_id != 1700;
    MERGE 1
    gaussdb=# SELECT * FROM dst ORDER BY 1;
    SELECT * FROM dst ORDER BY 1;
     product_id |  product_name  | category  | total
    ------------+----------------+-----------+-------
           1501 | vivitar        | electrnc  |   100
           1502 | olympus camera | electrncs |   200
           1600 | play gym       | toys      |   100
           1601 | lamaze         | toys      |   100
           1666 | harry potter   | dvd       |   100
    (5 rows)

    子查询

    在USING部分可以使用子查询,进行更复杂的关联操作。

    • 对源表进行聚合操作的结果再与目标表匹配
    MERGE INTO dst x
    USING (
      SELECT product_id, product_name, category, sum(total) AS total FROM src group by product_id, product_name, category
    ) y
    ON x.product_id = y.product_id
    WHEN MATCHED THEN
        UPDATE SET product_name = x.product_name, category = x.category, total = x.total
    WHEN NOT MATCHED THEN
        INSERT VALUES (y.product_id, y.product_name, y.category, y.total + 200);
    • 多个表UNION后的结果再与目标表匹配
    MERGE INTO dst x
    USING (
      SELECT 1501 AS product_id, 'vivitar 35mm' AS product_name, 'electrncs' AS category, 100 AS total UNION ALL
      SELECT 1666 AS product_id, 'harry potter' AS product_name, 'dvd' AS category, 100 AS total
    ) y
    ON x.product_id = y.product_id
    WHEN MATCHED THEN
        UPDATE SET product_name = x.product_name, category = x.category, total = x.total
    WHEN NOT MATCHED THEN
        INSERT VALUES (y.product_id, y.product_name, y.category, y.total + 200);

    存储过程

    gaussdb=# CREATE OR REPLACE PROCEDURE store_procedure1()
    AS
    BEGIN
      MERGE INTO dst x
      USING src y
      ON x.product_id = y.product_id
      WHEN MATCHED THEN
        UPDATE SET product_name = y.product_name, category = y.category, total = y.total;
    END;
    /
    
    CREATE PROCEDURE
    gaussdb=# CALL store_procedure1();

    MERGE INTO背后原理

    上文提到了MREGE INTO转化成LEFT JOIN或者INNER JOIN将目标表和源表进行关联。那么如何知道某一行要进行更新还是插入?

    通过EXPLAIN VERBOSE查看算子的输出。扫描两张表时都输出了ctid列。那么ctid列有什么作用呢?

     5 --Seq Scan on public.src y
             Output: y.product_id, y.product_name, y.category, y.total, y.ctid
             Distribute Key: y.product_id
       6 --Hash
             Output: x.product_id, x.product_name, x.category, x.total, x.ctid, x.xc_node_id
       7 --Seq Scan on public.dst x
             Output: x.product_id, x.product_name, x.category, x.total, x.ctid, x.xc_node_id
             Distribute Key: x.product_id

    ctid标识了这一行在存储上具体位置,知道了这个位置就可以对这个位置的数据进行更新。GaussDB(DWS)作为MPP分布式数据库,还需要知道节点的信息(xc_node_id)。UPDATE操作需要这两个值。

    在MREGE INTO这里ctid还另有妙用。当目标表匹配时需要更新,这是就保留本行ctid值。如果无法匹配,插入即可。就不需要ctid,此时可认识ctid值是NULL。根据LEFT JOIN输出的ctid结果是否为NULL,最终决定本行该被更新还是插入。

    这样在两张表做完JOIN操作后,根据JOIN后输出的ctid列,更新或者插入某一行。

    注意事项

    使用MERGE INTO时要注意匹配条件是否合适。如果不注意,容易造成数据被非预期更新,可能整张表被更新。

    总结

    GAUSSDB(DWS)提供了高效的数据导入的功能MERGE INTO,对于数据仓库是一项非常关键的功能。可以使用MERGE INTO 同时更新和插入一张表,在数据量非常大的情况下也能很快完成地数据导入。


    想了解GuassDB(DWS)更多信息,欢迎微信搜索“GaussDB DWS”关注微信公众号,和您分享最新最全的PB级数仓黑科技,后台还可获取众多学习资料哦~

     

    点击关注,第一时间了解华为云新鲜技术~

  • 相关阅读:
    2020软件工程作业02
    2020软件工程作业01
    为什么需要平衡二叉树?
    手机号码和邮箱等联系地址,为什么不明文显示?
    请把重要的事看轻 ——2017年终总结
    万事皆空:随缘而定
    微服务:微服务架构模式译文说明
    Mysql 查询—按位运算
    解决:spring security 登录页停留时间过长 跳转至 403页面
    excel模板解析—桥接模式:分离解析模板和业务校验
  • 原文地址:https://www.cnblogs.com/huaweiyun/p/15029185.html
Copyright © 2020-2023  润新知