Advertisement

MaxCompute SQL

阅读量:

MaxCompute SQL

知识点结构图
在这里插入图片描述

本文初衷是为了学习归纳,若有错误,请指出。

修改记录

时间 内容
2020年9月13日 第一次发布

一、概述

1.1 定义

​ MaxCompute(原 ODPS) SQL 适用于海量数据(TB 级别),实时性要求不高的场合,比如离线批量计算,它的每个作业的准备,提交等阶段要花费较长时间。

​ 采用的是类似与SQL的语法,可以看作是标准SQL的子集,但和数据库也有很多不同,比如没有事务,主键约束,索引等。

1.2 优缺点

  • 优点:

    • MaxCompute SQL的优点是学习成本低,不需要了解复杂的分布式计算概念,只需要会sql便可以操作;
    • MaxCompute主要用于大规模的数据计算,最快支持在分钟或秒钟级别完成查询返回结果,但无法在毫秒级别返回结果。
  • 缺点:

    • 一次作业批量处理海量数据,不适合直接对接需要每秒处理几千至数万笔事务的业务系统。
    • MaxCompute的SQL语法与Oracle、MySQL有一定差别,无法将其他数据库中的SQL语句无缝迁移至MaxCompute中。
    • MaxCompute SQL不支持事务、索引,也不支持Update或Delete操作

1.3 支持的数据类型

​ 支持的数据类型有:Bigint、Double、String、Datetime、Boolean、Decimal,Float。

​ 目前MaxCompute SQL中使用到新数据类型(TINYINT、SMALLINT、INT、FLOAT、VARCHAR、TIMESTAMP、BINARY)时,需要用set命令开启:

Session级别:要使用新数据类型,需在SQL语句前加上set语句set odps.sql.type.system.odps2=true;,并与SQL语句一起提交执行。

Project级别:支持对Project级别进行新类型打开。Project Owner可根据需要对Project进行设置,命令为:

复制代码
    set odps.sql.type.system.odps2=true;

    CREATE TABLE test_newtype (
    c1 tinyint
    ,c2 smallint
    ,c3 int
    ,c4 BIGINT
    ,c5 float
    ,c6 DOUBLE
    ,c7 decimal
    ,c8 binary
    ,c9 timestamp
    ,c10 ARRAY<map<BIGINT,BIGINT>>
    ,c11 map<STRING,ARRAY<BIGINT>>
    ,c12 STRUCT<s1:STRING,s2:BIGINT>
    ,c13 varchar(20))
    LIFECYCLE 1
    ;
    
    
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        

1.4 运算符

关系运算符注意点:

在进行部分关系运算之前,需要首先进行类型转换,否则可能返回NULL。如下举例,'2019-02-16 00:00:01'为DATETIME类型,而'2019-02-16'为STRING类型,在进行比较关系运算前需首先完成显示类型转换:

复制代码
    select cast('2019-02-16 00:00:01' AS string) > '2019-02-16';

    select cast('2019-02-16 00:00:02' AS datetime) > '2019-02-16 00:00:01';
    
        
              
              

由于DOUBLE值存在一定的精度差,因此建议两个DOUBLE类型相减,取绝对值的方式进行判断。当绝对值足够小时,认为两个DOUBLE数值相等,比如:

复制代码
    abs(0.9999999999 - 1.0000000000) < 0.000000001

     -- 0.9999999999和1.0000000000为10位精度,而0.000000001为9位精度。
     -- 此时可以认为0.9999999999和1.0000000000相等。
    
    
        
        
        
  • 在将String类型和Bigint类型数据进行比较时,这两个数据都将转换成Double类型,比较过程中可能丢失精度,所以需要将String用cast显示转换成Bigint类型运算。

算术运算符注意点:

复制代码
* A/B中,如果A、B都是Bigint类型,那么结果会是Double类型。
* 只有参数是STRING、BIGINT或DOUBLE类型才能参与算术运算,日期型和布尔型不允许参与运算。
* STRING类型在参与运算前会进行隐式类型转换,转换为DOUBLE类型。

位运算符注意点

复制代码
* 位运算符不支持隐式转换,只允许BIGINT类型。

逻辑运算符注意点:

复制代码
* 逻辑运算符只允许BOOLEAN类型参与运算,不支持隐式类型转换。
* Null And False = False,Null and True = Null;True or Nnll=True,False or Null = Null;

1.5 MaxCompute的ACID特性

  • 原子性(Atomicity) * 任何时候MaxCompute会保证在冲突时只会一个作业成功,其它冲突作业失败。

    • 对于单个表或分区的CREATE、OVERWRITE、DROP操作,可以保证其原子性。
    • 跨表操作时不支持原子性(例如MULTI-INSERT)。
    • 在极端情况下,以下操作可能不保证原子性:
      • DYNAMIC INSERT OVERWRITE多于一万个分区,不支持原子性。
      • INTO类操作:这类操作失败的原因是事务回滚时数据清理失败,但不会造成原始数据丢失。
  • 一致性(Consistency) * OVERWRITE类作业可保证一致性。

    • INTO类作业在冲突失败后可能存在失败作业的数据残留。
  • 隔离性(Isolation) * 非INTO类操作保证读已提交。

    • INTO类操作存在读未提交的场景。
  • 持久性(Durability) * MaxCompute保证数据的持久性。

二、类型转换

​ MaxCompute SQL允许数据类型之间的转换,类型转换方式包括显式类型转换隐式类型转换

2.1 显式转换

​ 显式类型转换是通过cast()函数将一种数据类型的值转换为另一种类型的值,在MaxCompute SQL中支持的显式类型转换,如下表所示。
在这里插入图片描述

  • 注意点:
    • 将DOUBLE类型转为BIGINT类型时,小数部分会被截断,例如cast(1.6 as BIGINT) = 1
    • 同样,满足DOUBLE格式的STRING类型转换为BIGINT时,会先将STRING转换为DOUBLE,再将DOUBLE转换为BIGINT,因此,小数部分会被截断。
    • 满足BIGINT格式的STRING类型可以被转换为DOUBLE类型,小数点后保留一位,例如cast(“1” as DOUBLE) = 1.0
    • 日期类型转换时采用默认格式yyyy-mm-dd hh:mi:ss。
    • MaxCompute支持复杂类型的类型转换功能,规则同上。

2.2 隐式转换

​ 隐式类型转换是指在运行时,由MaxCompute依据上下文使用环境及类型转换规则自动进行的类型转换。MaxCompute支持的隐式类型转换规则,如下表所示。
在这里插入图片描述
在这里插入图片描述

  • 注意点:
    • 这里的Y是指两者之间支持隐式转换,而没有说明转换成谁。
2.2.1 关系运算符作用下的隐式转换

​ 关系运算符包括=、<>、<、<=、>、>=、IS NULL、IS NOT NULL、LIKE、RLIKE、IN。由于LIKE、RLIKE、IN的隐式类型转换规则不同于其他关系运算符,将单独对其进行说明。此处的说明不包含这三种特殊的关系运算符。

​ 当不同类型的数据共同参与关系运算时,按照下述原则进行隐式类型转换。
在这里插入图片描述

  • 总结:
    • Bigint、String、Double,三者Double最大,Bigint和String互转也会变成Double。
    • 有Decimal的情况,Decimal最大。
2.2.2 特殊的关系运算符作用下的隐式转换

​ 特殊的关系运算符包括LIKE、RLIKE、IN

  • LIKE和RLIKE的source和pattern参数均仅接受STRING类型。
  • 其他类型不允许参与运算,也不能进行到STRING类型的隐式类型转换。
  • IN右侧的VALUE值列表中的数据类型必须一致。
  • key in (value1, value2, …)中,当KEY与VALUES之间比较时,如果数据类型包含BIGINT、DOUBLE、STRING,建议统一转为DOUBLE类型;如果数据类型包含DATETIME、STRING,建议统一转为DATETIME类型。除此之外不允许其它类型之间的转换。
2.2.3 算术运算符作用下的隐式转换
  • 只有STRING、BIGINT、DOUBLE和DECIMAL才能参与算术运算。
  • STRING在参与运算前会进行隐式类型转换到DOUBLE。
  • BIGINT和DOUBLE共同参与计算时,会将BIGINT隐式转换为DOUBLE。
  • 日期型和布尔型不允许参与算数运算。
2.2.4 逻辑运算符作用下的隐式转换

​ 只有BOOLEAN才能参与逻辑运算。

2.3 内建函数涉及到的隐式转换

​ 在调用函数时,如果输入参数的数据类型与函数定义的参数数据类型不一致,把输入参数的数据类型转换为函数定义的数据类型。

2.4 CASE WHEN作用下的隐式转换

​ 针对case when中有不同结果类型的情况。

  • 如果返回类型只有BIGINT、DOUBLE,统一转换为DOUBLE。
  • 如果返回类型中有STRING类型,统一转换为STRING,如果不能转换(如BOOLEAN类型)则报错。
  • 除此之外不允许其它类型之间的转换。

2.5 String和Datetime之间的转换

​ 这两者之间转换要遵循下面的格式:

注意

复制代码
* 以下将转换失败,没有严格按照格式来。可以先用TO_DATE函数调整格式再转
复制代码
      cast("2013/12/31 02/34/34" as datetime)  
      cast("20131231023434" as datetime)  
      cast("2013-12-31 2:34:34" as datetime)
    
    
        
        
        
  • 如果超出对应月份实际拥有的天数,将会导致异常退出。

三、分区表

​ MaxCompute 用户在创建表时,允许指定表的某些列为分区列,从而决定数据的存储流向。指定分区列(在 select 语句的 where 条件过滤中使用分区列作为过滤条件)会给用户带来诸多便利,例如:提高 SQL 运行效率,减少计费 等。

​ 一张表最多允许60000个分区,单表的分区层次不能超过6级。。

​ 但在使用 输出到动态分区(DYNAMIC PARTITION),SQL 的运行效率较低,并且会带来较高的计费。

四、SQL操作

4.1 表操作

4.1.1 创建表

​ 创建表的语法格式,如下所示,但一般有很多用不到,用不到的加“–”号注释了:

复制代码
    #完整版
    CREATE [EXTERNAL] TABLE IF NOT EXISTS table_name
    [(col_name 数据类型 [DEFAULT value] [COMMENT 字段注释], ...)]
    [COMMENT 表中文注释]
    [PARTITIONED BY (col_name 数据类型 [COMMENT col_comment], ...)]
    --[CLUSTERED BY (col_name [, col_name, ...]) [SORTED BY (col_name [ASC | DESC] [, col_name --[ASC | DESC] ...])] INTO number_of_buckets BUCKETS] -- 用于创建Hash Clustering表时设置表的----Shuffle和Sort属性。
    --[STORED BY StorageHandler] -- 仅限外部表。
    --[WITH SERDEPROPERTIES (Options)] -- 仅限外部表。
    --[LOCATION OSSLocation]; -- 仅限外部表。
    [LIFECYCLE days]
    
    
    # 第二种建表:
     CREATE TABLE [IF NOT EXISTS] table_name
     LIKE existing_table_name
     
    # 第三种建表
    create table if not exists table_name 
    AS select_statement;
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
  • 注意点:
    • 1.partitioned by的字段必须不是建表里面的字段。
    • 2.不加EXTERNAL代表内部表,加EXTERNAL代表外部表,外部表、内部表和Hive的差不多,如下:
    • 3.partitioned by指定表的分区字段,目前支持TINYINT、SMALLINT、INT、BIGINT、VARCHAR和STRING类型。
    • 4.LIFECYCLE是表的生命周期,单位:天。注意,create table like语句不会复制源表的生命周期属性。
    • 5.如果是用create table… as select …的方式来建表,那么在建表的同时将数据复制到新表中,但是不会复制分区属性 ,只会把源表的分区列作为目标表的一般列处理,也就是新表会多一列。
    • 6.如果是用第二种建表create table …like …的方式来建表,那么源表和目标表具有相同的表结构 ,即列名、列注释以及表注释等均相同(生命周期不会)。但源表中的数据不会被复制到目标表

hive外部表和内部表的区别:

  • 1.创建表时:创建内部表时,会将数据移动到数据仓库指向的路径;创建外部表时需要加上external关键字,它仅记录数据所在的路径,不对数据的位置做任何改变。
  • 2.删除表时:删除表后,内部表的元数据和真实数据会被一起删除,而外部表仅删除元数据,不删除真实数据,这样外部表相对来说更加安全些,数据组织也比较灵活,方便共享原始数据。(直接重建原来的表后,数据就自动导入到原来的表去了,location直接指向原来存储的位置)
  • 外部表保障底层数据的安全性,内部表适用于管理中间表和结果表。

clustered by指定Hash Key。MaxCompute将对指定列进行Hash运算,按照Hash值分散到各个Bucket中。

为避免数据倾斜和热点,取得较好的并行执行效果,clustered by列适宜选择取值范围大,重复键值少的列。此外,为了达到join优化的目的,也应该考虑选取常用的Join/Aggregation Key,即类似于传统数据库中的主键。

sorted by用于指定在Bucket内字段的排序方式。建议sorted byclustered by一致,以取得较好的性能。此外,当sorted by子句指定之后,MaxCompute将自动生成索引,并且在查询的时候利用索引来加快执行。

复制代码
>     INTO number_of_buckets BUCKETS

>  
>  
>             
> ```
>
>
>
> 指定了哈希桶的数目。这个数字必须填写,且由数据量大小来决定。此外,缺省条件下MaxCompute只能支持最多1111个Reducer,所以此处最多也只支持1111个哈希桶。您可以使用
>
>
复制代码
set odps.sql.reducer.instances=xxx;
复制代码
来提升这个限制,但最大不得超过4000,否则会影响性能。



选择哈希桶数目时,请您遵循以下两个原则:


    * 哈希桶大小适中:经验值是每个Bucket的大小在500M左右比较合理。例如,分区大小估计为500G,初略估算Bucket数目应该设为1000,这样平均每个Bucket大小约为500M。对于特别大的表,500M的限制可以突破,每个Bucket在2-3G左右比较合适。同时,可以结合`set odps.sql.reducer.instances=xxx;`来突破1111个桶的限制。
    * 对于需要经常`join`的两个表,哈希桶数目应设为一样,这样才能够优化`join`,省略掉Shuffle和Sort步骤。如果按照上述原则计算两个表的哈希桶数不一致,建议统一使用数字大的Bucket Number,保证合理的并发度和执行效率。

  *

Hash Clustering表的优点:


    * 优化Bucket Pruning。
    * 优化Aggregation。
    * 优化存储。

  *

Hash Clustering表的限制:


    * 不支持`insert into`,只能通过`insert overwrite`来添加数据。
    * 不支持Tunnel直接Upload到Range Cluster表,因为Tunnel上传数据是无序的。

  *

案例:
复制代码
>     CREATE TABLE T1 (a string, b string, c bigint) CLUSTERED BY (c) SORTED by (c) INTO 1024 BUCKETS; --创建Hash Clustering非分区表。
>     CREATE TABLE T1 (a string, b string, c bigint) PARTITIONED BY (dt string) CLUSTERED BY (c) SORTED by (c) INTO 1024 BUCKETS; --创建Hash Clustering分区表。
>  
>  
>       
>       
>  
> ```
>
>

##### 4.1.2 表其他操作
复制代码
-- 查看表
DESC <table_name>;
DESC extended <table_name>; --查看外部表信息。

-- 查看建表语句
SHOW CREATE TABLE <table_name>;

-- 删除表
-- 删除外部表时,OSS上的数据不会被删除。跟Hive一样意思。
DROP TABLE [IF EXISTS] table_name;

-- 重命名表
ALTER TABLE table_name RENAME TO new_table_name;

-- 修改表Owner
ALTER TABLE table_name CHANGEOWNER TO 'ALIYUN$xxx@aliyun.com';

-- 修改表的注释
ALTER TABLE table_name SET COMMENT 'new coments for table sale_detail';

-- 修改表的修改时间
-- 此时,MaxCompute会认为表的数据有变动,生命周期的计算会重新开始。
ALTER TABLE table_name TOUCH;

-- 修改表的Hash Clustering属性
ALTER TABLE table_name     
[CLUSTERED BY (col_name [, col_name, ...]) [SORTED BY (col_name [ASC | DESC] [, col_name [ASC | DESC] ...])] INTO number_of_buckets BUCKETS]
-- 去除表的Hash Clustering属性的语法格式如下
ALTER TABLE table_name NOT CLUSTERED;

-- 清空非分区表里的数据
TRUNCATE TABLE table_name;

-- 清空分区表里的数据
ALTER TABLE table_name DROP PARTITION(dt = '??????');

-- 强制删除表数据(分区数据),可以加purge
DROP TABLE table_name PURGE;
ALTER TABLE table_name DROP PARTITION(dt = '??????') PURGE;

-- 备份表
复制代码
##### 4.1.3 备份表的数据

​ 如果project 里的空间比较紧张,在想办法进行删除数据或者压缩数据,那么可以考虑 MaxCompute 里对表 的 archive 功能,效果是可以将存储空间压缩 50%左右,而且在这个过程中会将多个小文件自动的合并掉

​ 代价是如果某个数据块 损坏或某台机器损坏,恢复数据块的时间要比原来的方式长了,读的性能会有一定损失。

​ 所以现在这种功能可以用在一些冷数据的压缩存储上,比如一些非常大的日志数据,超过一定时间期限后使用 的频率非常低,但是又需要长期保存,则可以考虑用 raid file 来存储。

​ 语法:
复制代码
ALTER TABLE [table_name] <PARTITION(partition_name='partition_value')> ARCHIVE;

-- 案例
alter table my_log partition(ds='20140101') archive;
复制代码
​

#### 4.2 生命周期操作

​ MaxCompute提供了数据生命周期管理功能,**方便释放存储空间,简化回收数据的流程** 。

##### 4.2.1 注意事项

  * 在MaxCompute中,每当表的数据被修改后,表的`LastDataModifiedTime`将会被更新,非分区表和分区表的数据是否要回收都是根据每张表的LastDataModifiedTime和设置的生命周期天数来判断的,**从最后一次数据被修改开始计算,如果经过days后仍没有被改动,就会被系统自动回收** (类似drop table操作)
  * 不同的是非分区表不支持取消生命周期的设置,只能修改生命周期;而分区表可以取消某个具体分区的生命周期设置,并且**就算最后一个分区被回收了,这张分区表也不会被删除** 。
  * 生命周期只能在表级别设置,不能在分区级别设置,但分区表设置生命周期后,生命周期是在分区级别生效。
  * create table …like …的方式来建表,会复制列名、列注释以及表注释等,**但生命周期不会** 。

##### 4.2 2 禁止生命周期

​ 在某些情况下有些特定的分区可能不希望被生命周期功能自动回收掉,比如一个月的月初,或者双 11 期间的数 据,在这种情况下可以禁止该分区被生命周期功能回收。

​ 语法:
复制代码
-- 禁止生命周期
ALTER TABLE table_name [partition_spec] ENABLE|DISABLE LIFECYCLE;

-- 示例
ALTER TABLE trans PARTITION(dt='20141111') DISABLE LIFECYCLE;
复制代码
##### 4.2.3 修改表的生命周期属性

​ MaxCompute 提供数据生命周期管理功能,方便用户释放存储空间。

​ 语法:
复制代码
-- 修改表的生命周期属性
ALTER TABLE table_name SET LIFECYCLE DAYS;

-- 案例
ALTER TABLE test_lifecycle SET lifecycle 50;
复制代码
* 第一种情况,如果表是非分区表,自最后一次数据被修改开始计算,经过 days 天后数据仍未被改动,则此表无需 用户干预, 将会被 MaxCompute 自动回收(类似 drop table 操作)。在 MaxCompute 中,每当表的数据被修改后,表的LastDataModifiedTime 将会被更新,因此, MaxCompute 会根据每张表的LastDataModifiedTime 以及 lifecycle 的设 置来判断是否要回收此表。
  * 第二中情况,如果是分区表,则根据各分区的 LastDataModifiedTime 判断该分区是否该被回收。不同于非分区表,分区表的最后一个分区被回收后,该表不会被删除。

#### 4.3 分区和列操作

  * 注意: 
    * MaxCompute SQL仅支持新增分区,不支持新增分区字段;
    * 不能删除列;
    * 添加的新列不支持指定顺序,默认在最后一列。

##### 4.3.1 分区操作
复制代码
-- 添加分区
-- 对于多级分区的表,如果需要添加新的分区,必须指明全部的分区值。
ALTER TABLE table_name ADD [IF NOT EXISTS] PARTITION(par1 = '',par2 = ''...);

-- 删除分区操作
ALTER TABLE table_name DROP [IF EXISTS] PARTITION(par1 = '',par2 = ''...);

-- 修改分区值
ALTER TABLE table_name PARTITION (par1 = '旧值',par2 = '旧值', ...) RENAME TO PARTITION (par1 = '新值',par2 = '新值', ...);

-- 修改表、分区的更新时间
ALTER TABLE table_name TOUCH PARTITION(par1 = '',...);
复制代码
##### 4.3.2 列操作
复制代码
-- 添加列
ALTER TABLE table_name ADD COLUMNS (col_name1 数据类型,col_name2 数据类型...);

-- 修改列名操作
ALTER TABLE table_name CHANGE COLUMN old_col_name RENAME TO new_col_name;

-- 修改列的注释
ALTER TABLE table_name CHANGE COLUMN col_name COMMENT comment_string;

-- 同时修改列名及列注释
ALTER TABLE table_name CHANGE COLUMN old_col_name new_col_name column_type COMMENT column_comment;
复制代码
##### 4.3.3 合并分区

​ MaxCompute SQL提供`MERGE PARTITION`对分区进行合并,即同一个表下多个分区数据合并成一个分区,同时删除被合并的分区维度的信息,把数据移动到指定分区。

  * 注意: 
    * 如果运行过程中出现源数据被并发修改(包括`INSERT`、`RENAME`或`DROP`)时,即使指定`IF EXISTS`也会报错。
    * 不支持外部表和SHARD表,对于CLUSTERED表合并后的分区文件会消除CLUSTERED属性。
    * 一次性合并分区数量限制为4000个。

​ 语法:
复制代码
ALTER TABLE <tableName> MERGE [IF EXISTS] PARTITION(<predicate>) [, PARTITION(<predicate2>) ...] OVERWRITE PARTITION(<fullPartitionSpec>) [PURGE];

-- 示例:
+------------+------------+------------+------------+

|value|ds|hh|mm|

复制代码
+------------+------------+------------+------------+
1 20181101 00 00
1 20181101 10 00
1 20181101 10 10
复制代码
+------------+------------+------------+------------+
-- 合并所有满足hh='00' 的分区到hh='00',mm='00'中。
ALTER TABLE intpstringstringstring MERGE PARTITION(hh='00') OVERWRITE PARTITION(ds='20181101', hh='00', mm='00');
-- 合并后:
ds=20181101/hh=00/mm=00
ds=20181101/hh=10/mm=00
ds=20181101/hh=10/mm=10

-- MERGE PARTITIONS允许指定多个谓词条件,示例如下,指定到具体分区下,合并剩余分区。
ALTER TABLE intpstringstringstring MERGE IF EXISTS 
PARTITION(ds='20181101', hh='00', mm='00'), partition(ds='20181101', hh='10', mm='00'),  PARTITION(ds='20181101', hh='10', mm='10') 
OVERWRITE PARTITION(ds='20181101', hh='00', mm='00') PURGE;
复制代码
#### 4.4 视图操作

##### 4.4.1 创建视图

  * 注意: 
    * 不允许向视图写入数据,例如使用`insert into`或者`insert overwrite`操作视图。
    * 创建视图时,您必须有对视图所引用表的读权限。

​ 语法:
复制代码
CREATE [OR REPLACE] VIEW [IF NOT EXISTS] view_name
[(col_name [COMMENT col_comment], ...)]		-- 没有数据类型
[COMMENT view_comment]
[AS select_statement]

-- 创建视图sale_detail_view。
create view if not exists sale_detail_view
(store_name, customer_id, price, sale_date, region)
comment 'a view for table sale_detail'
as select * from sale_detail;
复制代码
##### 4.4.2 视图其他操作
复制代码
-- 重命名视图
ALTER VIEW view_name RENAME TO new_view_name;

-- 删除视图
DROP VIEW [IF EXISTS] view_name;
复制代码
#### 4.5 更新表数据(Insert OverWrite 和 Insert Into)

语法:
复制代码
INSERT OVERWRITE|INTO TABLE tablename [PARTITION (partcol1=val1, partcol2=val2 ...)] [(col1,col2 ...)]

select_statement
FROM from_statement;
复制代码
* 

注意

    * MaxCompute的`INSERT`语法与通常使用的MySQL或Oracle的`INSERT`语法有差别,在`INSERT OVERWRITE/INTO`后需要加入`TABLE`关键字,而非直接使用`TABLENAME`。
    * 当遇到并发写入时,MaxCompute会保证在冲突时只会一个作业成功,其它冲突作业失败。
    * OVERWRITE类作业可保证一致性,但INTO类作业在冲突失败后可能存在失败作业的数据残留。
    * Insert后面的partition参数不允许使用函数等表达式,只能是常量;
    * 源表与目标表的对应关系依赖于在`select`子句中列的顺序,而不是表与表之间列名的对应关系。
    * 向某个分区插入数据时,分区列只能出现在select的条件中。

##### 4.5.1 功能说明

  * **insert into** :直接向表或表的分区中追加数据。不支持`INSERT INTO`到Hash Clustering表。如果您需要插入少量测试数据,可以配合[VALUES](https://help.aliyun.com/document_detail/73778.html#concept-uhn-rdb-wdb)使用。
  * **insert overwrite** :先清空表中的原有数据,再向表或分区中插入数据。目前`INSERT OVERWRITE`不支持指定插入列的功能,暂时只能用`INSERT INTO`

##### 4.5.2 Insert 动态分区注意事项

  * `insert into partition`时,如果分区不存在,会自动创建分区。
  * 多个`insert into partition`作业并发时,如果分区不存在,会自动创建分区,但只会成功创建一个分区。(这点和并发写入保证只有一个作业成功一样)
  * 如果不能控制`insert into partition`作业并发,则只能通过预创建分区避免问题。

#### 4.6 多路输出-Multi Insert

​ MaxCompute SQL支持在一个语句中将数据插入不同的目标表或者分区中实现多路输出。

​ 语法:就是一个From开头,后面多个insert语句。
复制代码
FROM from_statement
INSERT OVERWRITE | INTO TABLE tablename1 [PARTITION (partcol1=val1, partcol2=val2 ...)]
select_statement1 [FROM from_statement]
[INSERT OVERWRITE | INTO TABLE tablename2 [PARTITION (partcol1=val3, partcol2=val4 ...)]
select_statement2 [FROM from_statement]]
复制代码
* 注意: 
    * 通常,单个SQL中最多可以写255路输出。超过255路,则报语法错误。
    * 在一个`multi insert`中,对于分区表,同一个目标分区不允许出现多次。
    * 在一个`multi insert`中,对于未分区表,该表不能出现多次。
    * 对于同一张分区表的不同分区,不能同时有`insert overwrite`和`insert into`操作,否则报错返回

  * 示例:
复制代码
--创建表sale_detail_multi。
create table sale_detail_multi like sale_detail;

--将表sale_detail中的数据插入到表sale_detail_multi。
set odps.sql.allow.fullscan=true; //开启全表扫描,仅此session有效。
from sale_detail
insert overwrite table sale_detail_multi partition (sale_date='2010', region='china' ) 
select shop_name, customer_id, total_price 
insert overwrite table sale_detail_multi partition (sale_date='2011', region='china' )
select shop_name, customer_id, total_price ;
复制代码
#### 4.7 输出到动态分区

##### 4.7.1 说明

​ 在使用INSERT OVERWRITE语句将数据插入到分区表时,MaxCompute提供了如下两种方式:

  * 输出到静态分区:在INSERT语句中**直接指定分区值** ,将数据插入指定的分区。
  * 输出到动态分区:在INSERT语句中**不直接指定分区值** ,只指定分区列名。分区列的值在SELECT子句中提供,系统自动根据分区字段的值将数据插入到相应分区。(在SQL运行之前,您无法得知会产生哪些分区。只有在语句运行结束后,才能通过分区字段产生的值确定产生的分区)

##### 4.7.2 动态分区语法
复制代码
INSERT OVERWRITE|INTO TABLE tablename PARTITION (partcol1, partcol2 ...) --也就是这里不指定具体值
select_statement FROM from_statement;
复制代码
##### 4.7.3 动态分区注意事项

  * 使用INSERT INTO最多可以生成10000个动态分区,使用INSERT OVERWRITE最多可以生成60000个动态分区。
  * 分布式环境下,使用动态分区功能的SQL中,单个进程最多只能输出512个动态分区,否则会引发运行时异常。
  * 动态生成的分区值不允许为NULL,也不支持含特殊字符和中文,否则会引发运行时异常`FAILED: ODPS-0123031:Partition exception - invalid dynamic partition value: province=xxx`。
  * 如果目标表有多级分区,在运行INSERT语句时允许指定部分分区为静态,但是静态分区必须是高级分区。
  * 如果目标表为Hash Clustering Table,则不支持动态分区。
  * 动态分区中,**select_statement** 字段和目标表动态分区的对应是按字段顺序决定,并不是按照列名称决定的。 
    * 动态分区插入时, 。这点和普通插入分区表不一样。

  * 在动态分区进行时,如果分区列的数据类型和select列表中类型不一致,MaxCompute能支持隐式类型转换。

### 五、SELECT语句

#### 5.1 Select语法注意事项

SELECT语句查询分区表时禁止全表扫描,屏显目前最多只能显示10000行结果。

    * 如果想要执行全表扫描,需要执行以下开关:set odps.sql.allow.fullscan=true;

`select_expr`支持这样的正则表达式:
复制代码
SELECT `abc.*` FROM t;选出t表中所有列名以abc开头的列。

SELECT `(ds)?+.+` FROM t;选出t表中列名不为ds的所有列。
SELECT `(ds|pt)?+.+` FROM t;选出t表中排除ds和pt两列的其它列。
SELECT `(d.*)?+.+` FROM t;选出t表中排除列名以d开头的其它列。
复制代码
* 

在SQL解析中,`GROUP BY`操作先于`SELECT`操作,因此`GROUP BY`的取值是`SELECT`输入表的列名或者由输入表的列构成的表达式,不允许是`SELECT`语句的输出列的别名。

OFFSET 和 ORDER BY LIMIT语句配合,可以指定跳过OFFSET数目的行。
复制代码
--将src按照key从小到大排序后,输出第11到第30行(OFFSET 10指定跳过前10行,LIMIT 20指定最多输出20行)。

SELECT * FROM src ORDER BY key LIMIT 20 OFFSET 10;
复制代码
* 

子查询必须要有别名

#### 5.2 select语序

案例一:
复制代码
SELECT  key
    ,MAX(value)
FROM    src t
WHERE   value > 0
GROUP BY key
HAVING  SUM(value) > 100
ORDER BY key
LIMIT   100
;

-- 以上语句的逻辑执行顺序是FROM->WHERE->GROUY BY->HAVING->SELECT->ORDER BY->LIMIT

-- 案例二
SELECT  shop_name
    ,total_price
    ,region
FROM    sale_detail
WHERE   total_price > 150
DISTRIBUTE BY region
SORT BY region
;

-- 以上语句的逻辑执行顺序是FROM->WHERE->SELECT->DISTRIBUTE BY->SORT BY。
复制代码
* `ORDER BY`中只能引用`SELECT`列表中生成的列,而非访问`FROM`的源表中的列。
  * `HAVING`可以访问`GROUP BY key`和聚合函数。
  * `SELECT`时,如果语句中有`GROUP BY`,便只能访问`GROUP BY key`和聚合函数,而不是`FROM`中源表中的列。

#### 5.3 交集、并集、补集

交集:Intersect & Intersect distinct(交集后去重)

并集:Union All & Union

补集:Except All & Except distinct (即第一个数据集减去第二个数据集的结果,默认是EXCEPT DISTINCT)

​ 这里EXCEPT等同于MINUS
复制代码
-- EXCEPT ALL 如果第一个数据集有重复的,有多少个减多少个
SELECT * FROM VALUES (1, 2), (1, 2), (3, 4), (3, 4), (5, 6), (7, 8) t(a, b) 
EXCEPT ALL 
SELECT * FROM VALUES (3, 4), (5, 6), (5, 6), (9, 10) t(a, b);

-- EXCEPT DISTINCT 如果第一个数据集有重复的,那么只当做去重后的一个来减
SELECT * FROM VALUES (1, 2), (1, 2), (3, 4), (3, 4), (5, 6), (7, 8) t(a, b) 
EXCEPT
SELECT * FROM VALUES (3, 4), (5, 6), (5, 6), (9, 10) t(a, b);
复制代码
​ 注意,集合操作左右两个分支要求列个数必须一致。如果数据类型不一致,可能会进行隐式类型转换。

​ MaxCompute最多允许256个分支的集合操作,超出256个将报错。

#### 5.4 Join操作

  * `LEFT OUTER JOIN`:左连接,可简写为`LEFT JOIN`。返回左表中的所有记录,右表中没有与之匹配的记录显示为空。 
    * 如果右表值不唯一,建议不要连续使用过多`LEFT JOIN`,会造成数据重复,数据膨胀导致作业停止。

  * `RIGHT OUTER JOIN`:右连接,可简写为`RIGHT JOIN`。返回右表中的所有记录,左表中没有与之匹配的记录显示为空
  * `FULL OUTER JOIN`:全连接,可简写为`FULL JOIN`。返回左右表中的所有记录。
  * `INNER JOIN`:内连接。关键字`INNER`可以省略。返回表之间相互都匹配的数据行
  * 隐式连接,即不指定`JOIN`关键字执行连接。就普通的写法
  * 自然连接,natural join

#### 5.5 Semi Join

参考:https://help.aliyun.com/document_detail/73784.html?spm=a2c4g.11186623.6.700.2f0d3dd6JPv77w

#### 5.6 MapJoin Hint

​ 当一个大表和一个或多个小表JOIN时,可以在SELECT语句中显式指定MAPJOIN以提升查询性能。

##### 5.6.1 原理

​ 在大表和一个或多个小表JOIN的场景下,**MAPJOIN会将指定的小表全部先加载到的内存中,然后在Map阶段完成大表和小表的连接从而加快JOIN的执行速度** 。

​ MAPJOIN在Map阶段执行表连接,而不用等到Reduce阶段才执行表连接。这样就节省了大量数据传输的时间以及系统资源,从而起到了优化作业的作用。

​ 指定的表仅能为小表,且表被加载到内存后占用的总内存不得超过512 MB。

>
>
> 通常情况下,JOIN操作在Reduce阶段执行表连接。整个JOIN过程包含Map、Shuffle和Reduce三个阶段。
>
>

##### 5.6.2 使用方法

​ 在SELECT语句中使用Hint提示`/*+ MAPJOIN(table) */`,如下:
复制代码
SELECT /* + MAPJOIN(a) */
    a.shop_name,
    b.customer_id,
    b.total_price
FROM shop a JOIN sale_detail b
ON a.shop_name = b.shop_name;
复制代码
##### 5.6.3 使用(限制)条件

  * MAPJOIN支持小表为子查询,在引用小表或子查询时,需要引用别名。
  * LEFT OUTER JOIN的左表必须是大表,RIGHT OUTER JOIN的右表必须是大表,INNER JOIN的左表或右表均可以作为大表,FULL OUTER JOIN不能使用MAPJOIN。
  * MaxCompute在MAPJOIN中最多支持指定128张小表,否则报语法错误。MAPJOIN中多个小表用逗号隔开,例如`/*+MAPJOIN(a,b,c)*/`。(实际开发一般是8张)
  * 如果使用MAPJOIN,则小表占用的总内存不得超过512 MB。由于MaxCompute是压缩存储,因此小表在被加载到内存后,数据大小会急剧膨胀。此处的512 MB是指加载到内存后的空间大小。
复制代码
SELECT /* + MAPJOIN(a) */
    a.total_price,
    b.total_price
FROM shop a JOIN sale_detail b
ON a.total_price < b.total_price OR a.total_price + b.total_price < 500;
复制代码
#### 5.7 Laterval View

#### 5.8 Grouping Sets

-待补充。

参考:https://help.aliyun.com/document_detail/92807.html?spm=a2c4g.11186623.6.704.625e8030Qazc5p

#### 5.9 SELECT TRANSFORM语法

-待补充。

参考:https://help.aliyun.com/document_detail/73719.html?spm=a2c4g.11186623.6.705.4ba67830goviwc

#### 5.10 With AS语法(CTE)

​ MaxCompute支持SQL标准的CTE,提高SQL语句的可读性与执行效率。

​ 语法格式:
复制代码
WITH 
 cte_name AS
(
    cte_query
)
[,cte_name2  AS 
 (
 cte_query2
 )
,……]
复制代码
示例:
复制代码
-- 这样写看起来复杂
INSERT OVERWRITE TABLE srcp PARTITION (p='abc')
SELECT * FROM (
SELECT a.key, b.value
FROM (
    SELECT * FROM src WHERE key IS NOT NULL    ) a
JOIN (
    SELECT * FROM src2 WHERE value > 0    ) b
ON a.key = b.key
) c
UNION ALL
SELECT * FROM (
SELECT a.key, b.value
FROM (
    SELECT * FROM src WHERE key IS NOT NULL    ) a
LEFT OUTER JOIN (
    SELECT * FROM src3 WHERE value > 0    ) b
ON a.key = b.key AND b.key IS NOT NULL
)d;

-- 可以改成with as的形式比较直观。也不用反复嵌套
with 
  a as (select * from src where key is not null),
  b as (select  * from src2 where value>0),
  c as (select * from src3 where value>0),
  d as (select a.key,b.value from a join b on a.key=b.key),
  e as (select a.key,c.value from a left outer join c on a.key=c.key and c.key is not null)
insert overwrite table srcp partition (p='abc')
select * from d union all select * from e;
复制代码
#### 5.11 Clone Table 复制表

-待补充。

#### 5.12 Explain执行计划

-待补充。

### 六、 MaxCompute和其他数据库的区别

    1. MaxCompute适合海量存储和大数据分析,不适合在线服务;

    2. MaxCompute表不支持主键、索引和字段约束,不支持事务操作。

    3. MaxCompute表不⽀持UPDATE操作和DELECT操作,只能DROP 整个表或者某一分区数据,然后用INSERT OVERWRITE/INSERT INTO写入数据。

    4. MaxCompute可以在select中使用mapjoin Hint,将指定的小表全部先加载到的内存中,然后在Map阶段完成大表和小表的连接从而加快JOIN的执行速度,提高查询速度;

    5. SELECT操作输出屏显的数据行数受限制,最大为10000条。不支持通过SQL下载数据。

    6. MaxCompute表可以指定生命周期,生命周期结束后自动清除数据;

    7. 插入语句有insert into | overwrite into,后面需加Table关键字,overwrite into写入前会清空表数据;

    8. 子查询必须要有别名,select输入表的数量不能超过16张。

    9. 数据插入表的字段映射不是根据Select的别名做的,而是根据Select的字段的顺序和表里的字段的顺序。

    10. 不能修改分区列列名,只能修改分区列对应的值,支持增加列,但是不支持删除列以及修改列的数据类型(数据类型应该可以吧)。

    11. 在MaxCompute中需要通过Tunnel、Dship工具或MaxCompute Tunnel SDK导出数据。

    12. select语句的查询结果默认使用了limit,如果希望数据量很大时返回所有记录,需要设置

>
>
> 其他区别参考:https://help.aliyun.com/document_detail/51823.html?spm=a2c4g.11186623.2.19.68516f416InQIc#concept-yjp-crl-vdb --SQL使用限制
>
>

### 七、函数

#### 7.1 内建函数

-待补充。

#### 7.2 自定义函数

-待补充。

### 八、MaxCompute SQL优化

​ 首先优化SQL的过程,实际上就是要尽可能减少IO读取,尽可能减少计算资源的使用,尽可能减少SQL复杂度,尽可能提升运行速度。

  * **(1) 建分区表** :但建议分区层数不超过3层,后续查询时为了避免全表扫描需要分区裁剪,分区值尽量常量化,避免不可确定值;插入数据时尽量采用写入静态分区的方式,优化数据存储,提高运行效率;如果用动态分区,底层来说会生成较多的小文件和多个mapreduce任务,增加系统负担。
  * **(2) 只select有效列,并用limit限制返回的条数** 。考虑这两种情况:(1)有些事实表的字段很多,多到有四五十个字段,这种表实际是可能存在的,比如企业报税的申报表,那么select * from整张表的所有字段时,数据量一大就会消耗更多查询时间;(2)第二种情况是假设事实表中存有某个复杂格式的字段,比如json格式,那么在select * from时也会耗时严重,因此select有效列是较好的习惯。
  * **(3) mapjoin优化** :mapjoin的原理简单说就是先将小表加载到内存中,后续大表与内存中的小表直接join连接计算(底层是大表与内存中的小表的计算在map阶段直接完成,避免了reduce阶段的计算耗时)
  * **(4) 避免一些消耗资源的操作** ,比如: 
    * **少用distinct** ,容易触发数据倾斜
    * count(distinct)处理的时间会很长可以转换成**count()+ group by** 。因为如果原先只用count(distinct)来处理表的数据,只会开启一个reduce任务来完成所有的计算,而改成count/sum + group by后,除了count/sum会开启一个reduce计算之外,group by也会单独开启另一个reduce来计算,因此数据量大时可以明显提升查询速度。
    * **尽量避免Order by** ,order by会触发全局排序,只能单点运行,效率低,如果业务允许,可以改成distribute by + sort by
    * 将full outer join 改为left outer join + union all 并对小表使用上mapjoin。
    * 多个表join时,join顺序很重要,优先选择join结果输出小的表先关联,能有效减少中间数据量,节省IO和计算资源。
    * 读取相同源表时可以合并成一条sql,系统会优化只读取一次。

  * **(5) 尽量使用内置的UDF函数和窗口函数** :内置UDF在实现时做了很多优化,运行快,省资源,窗口函数本身能处理很多复杂问题。
  * **(6) 尽可能保证表达式两边的数据类型一致** :如果发生隐式转换容易造成精度问题,比如string和bigint都转成double来相等比较,悲观情况下,可能触发数据倾斜。这时要cast显式转换一下
复制代码
工作中还用到的:
- 数据量达到千万级别的,尽量固化分区,不在查询sql里面进行动态分区,减少资源使用
- sql行数较长的,尽量拆分成工作流任务,可以优化性能和排查问题的难易度。
- 维度表要放到mapjoin里,加大资源利用率
- 数据倾斜较高的表,先落地成临时表
- 每日全量推送任务的,如果表级数据量较高的,分析是否可以改成增量模式。
复制代码
### 九、问题案例收集

​ 待补充

参考:https://help.aliyun.com/knowledge_detail/150534.html#section-iok-hsp-b2z

全部评论 (0)

还没有任何评论哟~