SELECT语法相关

Posted 灯火落星桥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SELECT语法相关相关的知识,希望对你有一定的参考价值。

命令格式

[with <cte>[, ...] ]
select [all | distinct] <select_expr>[, <except_expr>)][, <replace_expr>] ...
       from <table_reference>
       [where <where_condition>]
       [group by <col_list>|rollup(<col_list>)]
       [having <having_condition>]
       [order by <order_condition>]
       [distribute by <distribute_condition> [sort by <sort_condition>]|[ cluster by <cluster_condition>] ]
       [limit <number>]
       [window <window_clause>]

with子句

cte充当一个临时表的角色

列表达式

在使用select时,可以将字段名理解为特殊的正则列表达式,即所有待查询列均适用正则。
需要声明使用正则表达式匹配:set hive.support.quoted.identifiers=None;

  • *:代表查询所有列
  • xxx.*:代表查询表中所有列名以xxx开头的列
  • (xxx.*|name)?+.+:代表查询除了xxx开头以及name的其他列(tips:量词?+是占有优先,匹配到值后不交还,.+表示必须有1个以上的字符,所有连接起来就可以达到一个非xxx的查询)
在排除多个列时,如果col2是col1的前缀,则需保证col1写在col2的前面(较长的col写在前面)。例如,一个表有2个分区无需被查询,一个分区名为ds,另一个分区名为dshh,由于前者是后者的前缀,正确表达式为select `(dshh|ds)?+.+` from t;;错误表达式为select `(ds|dshh)?+.+` from t; 
  • distinct修饰列表达式,默认为all。当去重多列时,它的作用域是列的集合,并不是单个列。

排除列

通过select * except(col1_name, col2_name, …) from …;语句实现,表示读取表数据时会排除指定列(col1、col2)的数据。
hive似乎不支持!!!

select * except(col1,col2) from table;

修改列

可以通过select * replace(exp1 as col1_name, exp2 as col2_name, …) from …;实现,表示读取表数据时会将col1的数据修改为exp1,将col2的数据修改为exp2。
hive似乎不支持!!!

select * replace(col1*2 as col1,'col2' as col2) from table; 

where

  • 主要注意分区裁剪是否生效。
  • join中分区裁剪的生效需要在where条件里面;
  • 如果被重命名的列字段(赋予了列别名)使用了函数,则不能在where子句中引用列别名
  • UDF是可能会导致分区裁剪不生效的,需要通过explain查看。

group by

group by 后不跟select 列的别名
group by中的整型常量会被当做select的列序号处理

set hive.groupby.orderby.position.alias=true;
select col1,sum(col2) from table group by 1;

order by

  • order by 后可跟select 列的别名
select total_price as t from sale_detail order by t limit 3;
  • 可跟limit m,n。
    m代表从m+1行读取数据,n代表共返回多少行数据。

DISTRIBUTE BY哈希分片

对数据按照某几列的值做hash分片,发生在select之后,所以可以使用别名

Sort By

  • 通常与distribute by 联合使用,用作分片内局部排序。
  • 保证每个reducer内局部有序,增加存储压缩率。

Split Size Hint

调整Split Size来控制并发度,调整计算性能。Split Size功能可以作用到表级别。指定的值单位为MB,默认值为256MB。
使用示例:

--设置split size大小为1MB,此hint会在读表src时,按照1M的大小来切分task
select a.key from src a /*+split_size(1)*/ join src2 b on a.key=b.key;

执行语序

常见有两种场景:

  • from --> where --> group by --> having --> select --> order by --> limit
  • from --> where --> select --> distribute by --> sort by

子查询

其余子查询不多做介绍!!!

关联子查询
  • in subquery
    相当于left semi join
实现semi join
select * from t1 where col1 in (select col1 from t2 where col2=t1.col2);
值得注意的是:当where后跟and关键词时,子查询不会转换为semi join,而是单独启动作业。另外当where条件为分区列判断时,也不会转换为semi join。
  • not in subquery
    相当于left anti join。但是当目标表的指定列有一行为null时,结果为null,导致where条件不成立。此时区别left anti jion。
select * from t1 where col1 not in (select col1 from t2 where col2=t1.col2)
可转换为
select * from t1 where (col1,col2) not in (select col1,col2 from t2)
或者
select * from t1 left semi join t2 on t1.col1=t2.col2 and t1.col2=t2.col2
  • exists subquery
    相当于left semi join。exists后只能使用correlated条件
select * from t1 where exists (select * from t2 where col1=t1.col1);
可转换为:
select * from t1 left semi join t2 on t1.col1=t2.col1;
  • not exists subquery
    相当于left anti join。当子查询中无数据时,返回True,否则返回False。获取t1中存在在t2不存在的数据
select * from t1 where not exists (select * from t2 where col1=t1.col1);
  • scalar subquery
    当子查询的输出结果为单行单列时,可以做为标量使用,即可以参与标量运算。
单列运算:
select * from t1 where (select count(1) from t2 where col1=t1.col1)>=1;

多列运算仅支持等值运算:
该sql类似与t1 left join t2,不关心t2的所有记录,之关系通过col3关联到的t2记录以及t1的其他记录
select (select col1,col2 from t2 where col3=t1.col3),col1,col2 from t1; --最后的结果行数应与t1保持一致;t2的col3不允许有重复值sql表示t1 left join t2 on col1,col2,col3
select * from t1 where col3>xxx and (col1,col2)=(select col1,col2 from t2 where col3=t1.col3);

left semi join/anti join 半连接

  • 只会返回左表数据
  • semi join:匹配左表在右表中存在的记录
  • anti join:匹配左表在右表中不存在的记录

mapjoin

  • 使用left join时大表在左,小表在右
  • 使用right join时大表在右,小表在左
  • 使用自然连接join时都可以

distributed mapjoin

适用于小表join大表

  • Join两侧的表数据量要求不同,大表侧数据在10 TB以上,小表侧数据在[1 GB, 100 GB]范围内。
  • 小表侧的数据需要均匀分布,没有明显的长尾,否则单个分片会产生过多的数据,导致OOM(Out Of Memory)及RPC(Remote Procedure Call)超时问题。
  • SQL任务运行时间在20分钟以上,建议使用Distributed MapJoin进行优化。
  • 由于在执行任务时,需要占用较多的资源,请避免在较小的Quota组运行。

语法:

select /*+ distmapjoin(table_name(shard_count=m,replica_count=n)),mapjoin(table_name_other)*/
  • shard_count=:设置小表数据的分片数,小表数据分片会分布至各个计算节点处理。n即为分片数,一般按奇数设置。
    • shard_count值建议手动指定,shard_count值可以根据小表数据量来大致估算,预估一个分片节点处理的数据量范围是[200 MB, 500 MB]。
    • shard_count设置过大,性能和稳定性会受影响;shard_count设置过小,会因内存使用过多而报错。
  • replica_count=:设置小表数据的副本数。m即为副本数,默认为1。
    • 为了减少访问压力以及避免单个节点失效导致整个任务失败,同一个分片的数据,可以有多个副本。当并发过多,或者环境不稳定导致运行节点频繁重启,可以适当提高replica_count,一般建议为2或3

Skewjoin Hint

使用场景:大表join大表
当两表join存在长尾问题时,通过取出热点key,将数据分为热点与非热点数据拆分处理最后合并,提高效率。
使用示例:

--方法1:hint表名,注意是别名
select /*+ skewjoin(a) */ * from T0 a join T1 b on a.c0 = b.c0 and a.c1 = b.c1;
--方法2:hint表名和表中可能倾斜的列
select /*+ skewjoin(a(c0,c1))* / * from T0 a join T1 b on a.c0 = b.c0 and a.c1 = b.c1;
--方法3:hint表名和列,并提供发生倾斜的key值。如果是String类型,需要加上引号
select /*+ skewjoin(a(c0,c1),((1,"2"),(3,"4"))) */ * from T0 a join T1 b on a.c0 = b.c0 and a.c1 = b.c1;
原理

在没有skewjoin的情况下,两张大表进行join,只可以使用merge join,从而相同热点key会被shuffle到同一节点,导致数据倾斜。加上skewjion hint后,优化器会运行一个aggregate动态获取重复行数前20的热值,将左表的热值与非热值拆分,将右表的能与左表热值join的与不能join的拆分,然后热值部分进行mapjoin,非热值部分进行mergejoin,最后将mapjion与mergejoin合并,生成最后的结果。

  • 建议只对一定会出现数据倾斜的Join添加Hint,因为Hint会运行一个Aggregate,存在一定代价。
  • 被Hint的Join的Left Side Join Key的类型需要与Right Side Join Key的类型一致,否则SkewJoin Hint不生效。例如上例中的a.c0与b.c0的类型需要一致,a.c1与b.c1的类型需要一致。您可以通过在子查询中将Join key进行Cast从而保持一致。
    • 如下示例:
    select /*+ skewjoin(a) */ * from T0 a join T1 b on cast(a.c0 as string) = cast(b.c0 as string) and a.c1 = b.c1;
    

动态过滤器Dynamic Filter

hive未发现该过滤器,还需再研究下。
基于join等值的特性,通过左表生成一个过滤器(用来判断b表的元素是否存在于a表中,如不存在就过滤掉),在shuffle或join之前过滤掉数据,提高查询效率。

适用场景:维度表join事实表

动态过滤器包含bloon filter、range filter(利用[min,max]过滤数据),是个典型的生产者消费者模式。有DFP和DFC两个算子。

  • DFP算子:利用小表侧的数据生成bloom filter及获取koin key对应的min、max值,发送给C
  • DFC:利用收到的bloom以及range过滤大表侧数据,range filter尽可能将条件下推到底层存储,达到从源头过滤数据。

那么如何确定谁是生产者谁是消费者?

  • join:左右表均可以做生产者消费者
  • A left join B :A做生产者,B做消费者
  • A right join B :A做消费者,B做生产者
  • A full join B :无法使用过滤器

过滤器使用存在一个特殊情况:当使用分区列作join key时,这时候组件会读取分区所有数据再做过滤,此时可以启用一个动态分区裁剪的功能,减少参与计算的分区数达到一种优化。具体原理就是maxcompute去采集小表侧数据生成bloom filter,过滤大表的分区列表,裁剪掉不需要扫描的分区。

有三种方式打开动态过滤器:

  • 强制打开:set odps.optimizer.force.dynamic.filter=true;
  • 智能打开:set odps.optimizer.enable.dynamic.filter=true; 使用该方式时,优化器会智能地估计插入动态过滤器是否有足够的资源或时间获益,如果有收益则插入动态过滤器,否则不会插入。
  • Hint打开:select /+ dynamicfilter(A,B)/ * from (table1) A join (table2) B on A.a= B.b;

Having

where关键字无法与聚合函数一起使用,此时使用having来解决。

Grouping Set

根据不同的维度组合进行聚合,等价于将不同维度的GROUP BY结果集进行 UNION ALL
首先声明,由于网上博客很多人口径都不一致,仅依据官网数据进行推算出来以下结论,链接:https://cwiki.apache.org/confluence/display/Hive/Enhanced+Aggregation%2C+Cube%2C+Grouping+and+Rollup
主要是有几种用法:

  • grouping sets
    • HIVE
      • group by col1,col2,col3… grouping sets(col1,(col1,col2),col2,());
    • ODPS
      • group by grouping sets(col1,(col1,col2),col2,());
  • cube()或者with cube
    • HIVE
      • group by col1,col2,col3 with cube
    • ODPS
      • group by cube(col1,col2,col3);
  • rollup()或者 with rollup
    • HIVE
      • group by col1,col2,col3 with rollup
    • ODPS
      • group by rollup(col1,col2,col3)
  • grouping
    • grouping(col1):当结果数据行使用了此列做聚合,则该函数值为0,没有使用则值为1。
  • grouping__id(根据sql运行的环境以及组件版本,该值运行有差异,以下总结几种)
    • HIVE
      • 2.3.0版本之前:grouping_id是该列不出现则为0,出现则为1。另外紧跟group by的字段要放在最低位。
      •   eg:group by id,name,age grouping sets ((id,name)); 此时grouping_id生成的二进制数字为0,1,1,转换为十进制为3。
        
      • 2.3.0及之后:grouping_id与odps一致。
    • ODPS
      • 根据group by 后声明的字段顺序进行二进制转换十进制数值。如果一列参与计算为0,反之为1。group by后紧跟的列排在高位。
      •   eg: group by id,name,age grouping sets ((id,name)); 此时grouping_id生成的二进制数字为0,0,1,转换为十进制为1
        
  • grouping_id(odps使用)
    • 此处只有一个_,使用方法为grouping_id(col1,col2,col3),计算与grouping__id一致。

Lateral View

适用于单行拆分多行。

命令格式

1、lateralView: lateral view [outer] <udtf_name>(<expression>) <table_alias> as <columnAlias> (',' <columnAlias>) 
2、fromClause: from <baseTable> (lateralView) [(lateralView) ...]
  • udtf_name:将一行拆分成多行的UDTF
  • expression:待拆分行数据所属列名,可以理解为待拆分字段
  • table_alias:UDTF结果集的别名
  • columnAlias:拆分后得到的列的别名
  • baseTable:数据源表
  • from后可以有多个Lateral View语句,后面的Lateral View语句能够引用它前面的所有表和列名,实现对不同列的行数据进行拆分。

引用阿里云官方案例:

pageidcol1col2
front_page[1,2,3][“a”,“b”,“c”]
contact_page[3,4,5][“d”,“e”,“f”]
--拆分一行数据为多行
select pageid, col1_new, col2 from pageAds lateral view explode(col1) adTable as col1_new; 
--拆分多行后进行聚合
select col1_new, count(1) as count from pageAds lateral view explode(col1) adTable as col1_new group by col1_new;
--多个lateral view,相当于拆分所有数据,单个数据均成一行
select pageid,mycol1, mycol2 from pageAds 
    lateral view explode(col1) myTable1 as mycol1 
    lateral view explode(col2) myTable2 as mycol2;

以上是关于SELECT语法相关的主要内容,如果未能解决你的问题,请参考以下文章

SQL 之相关语法及操作符

MaxCompute(ODPS):Hive的进阶者

sql记录之表的创建与删除相关操作

ADO.NET操作Oracle问题

ODPS SQL优化总结

使用 MaxCompute(原ODPS) java sdk 运行安全相关命令