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,());
- HIVE
- cube()或者with cube
- HIVE
- group by col1,col2,col3 with cube
- ODPS
- group by cube(col1,col2,col3);
- HIVE
- rollup()或者 with rollup
- HIVE
- group by col1,col2,col3 with rollup
- ODPS
- group by rollup(col1,col2,col3)
- HIVE
- 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
- HIVE
- 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语句能够引用它前面的所有表和列名,实现对不同列的行数据进行拆分。
引用阿里云官方案例:
pageid | col1 | col2 |
---|---|---|
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语法相关的主要内容,如果未能解决你的问题,请参考以下文章