简介: MaxCompute 挑战使用SQL进行序列数据处理 --而不是用MR和函数

日常编写数据加工任务,主要的方法就是使用SQL。第一是因为自己对SQL掌握的比较好(十多年数据开发经验,就这几个关键字,也不敢跟别人说自己不行),所以,MR和函数涉及不多。在接触MaxCompute这些年,写过的函数应该不超过10个,主要还是因为自己JAVA水平挫。记得早些年写过一个身份证号码校验函数,当时有个项目反馈一段SQL原来2分钟,使用我的函数就变成12分钟了。当时这个项目组还找到MaxCompute的研发,研发负责人又找到我,让我把我的代码调优下。我很惶恐啊,我是什么渣,我自己心里知道啊。最后还是厚着脸皮求研发帮我优化了下,性能终于改进了。这以后,我更不敢随机作函数了,毕竟MaxCompute官方建议尽可能使用SQL,SQL是优化过的方法,自己用MR和自定义函数性能是很难保障的。这也导致我至今在这方面也是渣渣,当然我认为错不在我,我只是听了“妈妈”的话而已。

最近很奇妙,接连有两个项目遇到了序列值计算的问题,还都是要求不能使用函数和MR。同事把问题送给我,我发现光读懂题都要半天(题目有点绕),不在一线搞开发太久了,有点生疏了。同样的问题,第一次搞了一天,第二次还搞了半天,没说很快能搞出来的,未免有点丢范。所以,总结出来跟大家分享下。

先说下什么是序列值的处理。表中的记录本身是无序的,但是业务上数据都是有序的,一般来说时间就是一个自然的序列。比如利用我一天的作息的时点记录,计算我一天吃了几次饭,吃了多久。乍一看,好像要写个函数。

问题模拟如下:

问题:吃了几次饭,都吃了多久?

条件:1-两个吃饭状态间隔在1小时内,算作一顿饭

2-最后一个吃饭状态后的下一个其他状态的开始时间,是吃饭的结束时间

通过上面的分析,我们可以得到结果:大约吃了四次饭,因为晚上吃饭的时间很长,按照规则算作吃了两次饭(第四次看起来是去撸串了)。我是怎么做的呢?第一步,我先把无关的信息剔除了,第1行、第4行、最后1行。第二步,后我利用数据是连续的时间的特质,找到了状态的结束时间。第三步,我识别了状态间隔1小时这个特征,识别出了一个“吃饭”中混杂的其他无关状态,并且还分析得到第三个“吃饭”和第四个“吃饭”状态是两个独立的状态。

那么用SQL怎么实现?排序是一定的了,要排序还要处理状态,必须使用窗口函数。能选的窗口函数似乎只有lag、lead。

窗口函数:

LAG  按偏移量取当前行之前第几行的值。

LEAD 按偏移量取当前行之后第几行的值。

官方文档:https://help.aliyun.com/document_detail/34994.html

即便有了这个函数,还有一个问题很头疼,函数需要指定偏移量,而这个问题里面并不知道到底会出现多少个状态。是不是也没有用呢?看看再说。

问题分解分解如下:

使用LAG\LEAD函数取到前一条记录和后一条记录的状态和时间,分析记录:

1-当前状态不是“吃饭”,上一个状态也不是“吃饭”,记录不保留。

2-当前状态不是“吃饭”,上一个状态是“吃饭”,为上一个状态提供结束时间,记录不保留。

3-当前状态是“吃饭”,记录上一个和下一个状态都是“吃饭”,记录不保留。

4-当前状态是“吃饭”,记录下一个状态时间,作为当前状态结束时间,记录保留。

如下图:

然后我们就得到了下面一个表格:

很明显,这不是我们最后需要的。虽然我们找到了状态为“吃饭”的行,并且通过窗口函数给它找到了状态的结束实际。但是表格还需要再作一次处理,才能变成我们想要的结果。再次使用LAG\LEAD函数,我们需要把间隔在1小时内的“吃饭”状态进行合并。

 

问题再次分解分解如下:

使用LAG\LEAD函数取到前一条记录和后一条记录的开始和结束时间,分析记录:

1-当前记录的“开始时间”减去上个时点的“结束时间”,如果小于1小时,该行记录不保留。这一行记录的状态需要与上一行合并为一次“吃饭”状态。下图中绿色标注行。

2-下个时点的“开始时间”减去当前记录的“结束时间”,如果小于1小时,该行记录与下一行记录合并。修改当前时点“吃饭”状态的结束时间为下一个时点的结束时间。下图橙色标注行。

然后我们得到了下面的表格:

不管之前我们想的多复杂,需要用什么循环或者递归逻辑实现,但是现在问题解决了。我们通过这个表格回答了最开始题目的问题。这个人吃过4次饭,开始时间分别是7点10分、12点25分、17点40分、19点45分,每次持续的时间大约都在1小时。这个过程就是一个找到需要的信息,剔除无关信息的过程,只不过这个where有点复杂。

其实从分析问题的角度来看,这个问题本身就有点复杂,搞懂问题一般都需要一定的时间。从实现问题的角度来看,使用高级语言JAVA或者python实现更容易点,循环撸一遍有什么解决不了的(一遍不够再来一遍)。用SQL实现,看起来有点复杂(可能是我常年使用SQL语言的原因,我觉得我好像分析问题的过程跟实现的过程是一样的。),但是代码量一定是最少的(性能可能也是最佳的)。再从可维护性上去综合比较,还是使用SQL实现更优。

所以,后面再遇到类似的问题,你应该可以搞定了。如果有点困难,至少你可以再回过头来看下这个例子,毕竟我花了好久来设计。

SQL问题解答:

with ta as(

select*

from values

(1001,'06:05:00','sleep')

,(1001,'07:10:00','eat')

,(1001,'08:15:00','phone')

,(1001,'11:20:00','phone')

,(1001,'12:25:00','eat')

,(1001,'12:40:00','phone')

,(1001,'13:30:00','eat')

,(1001,'13:35:00','sleep')

,(1001,'17:40:00','eat')

,(1001,'18:05:00','eat')

,(1001,'18:25:00','eat')

,(1001,'18:30:00','phone')

,(1001,'19:45:00','eat')

,(1001,'20:55:00','phone')

,(1001,'22:00:00','sleep')

t(id,stime,stat))

-- 5 计算根据前后记录的时间,判断记录是否要被合并

selectid,stime

,case whens2<=60 thenetime2 else etime end asetime,stat

from(

-- 4 计算前后记录的时间差

selectid,stime,etime,stat

,datediff(stime,etime1,'mi') ass1

,datediff(stime2,etime,'mi') ass2

,etime2

from(

-- 3 计算前后记录的时间

selectid,stime,etime,stat

,lag (stime,1) over(partition byid order by stime asc)as stime1

,lag (etime,1) over(partition byid order by stime asc)as etime1

,lead(stime,1) over(partition byid order by stime asc)as stime2

,lead(etime,1) over(partition byid order by stime asc)as etime2

from(

-- 2 识别前后记录状态,找到状态结束时间

selectid,stime,stat

,lead(stime,1) over(partition byid order by stime asc)as etime

,lag (stat,1) over(partition byid order by stime asc)as stat1

,lead(stat,1) over(partition byid order by stime asc)as stat2

from(

-- 1 把字符串转时间

selectid,to_date(concat('2021-06-29 ',stime),'yyyy-mm-dd hh:mi:ss') asstime,stat

fromta)t1)t2

wherestat='eat' and not(stat='eat' andstat1='eat' andstat2='eat'))t3)t4

wheres1 >60 ors1 is null

;

原文链接
本文为阿里云原创内容,未经允许不得转载。

MaxCompute 挑战使用SQL进行序列数据处理的更多相关文章

  1. SQL Server 序列(SEQUENCE)使用

    众所周知,在之前的SQL SERVER版本中,一般采用GUID或者IDENTITY来作为标示符,但是IDENTITY是一个表对象,只能保证在一张表里面的序列,当我们遇到以下情况时, 如上表,我们需要在 ...

  2. SQL 使用序列

    SQL 使用序列 序列是根据需要产生的一组有序整数:1, 2, 3 ... 序列在数据库中经常用到,因为许多应用要求数据表中的的每一行都有一个唯一的值,序列为此提供了一种简单的方法. 本节阐述在 My ...

  3. ORACLE PL/SQL 中序列(sequence)的简易使用方法介绍

    如果我是C罗 原文 ORACLE PL/SQL 中序列(sequence)的简易使用方法介绍 sequence在ORACLE中应用十分广泛,就是序列号的意思,会自动增加指定变数,如逐次增加1或者2或者 ...

  4. SQL基础--序列

    序列是一种数据库对象,用来自动产生一组唯一的序号:序列是一种共享式的对象,多个用户可以共同使用序列中的序号. 序列的创建语法 CREATE SEQUENCE sequencename [INCREME ...

  5. Spark SQL结构化数据处理

    Spark SQL是Spark框架的重要组成部分, 主要用于结构化数据处理和对Spark数据执行类SQL的查询. DataFrame是一个分布式的,按照命名列的形式组织的数据集合. 一张SQL数据表可 ...

  6. 一探究竟:善用 MaxCompute Studio 分析 SQL 作业

    头疼的问题 MaxCompute 用户一个常见的问题是:同一个周期任务,为什么最近几天比之前慢了很多?或者为什么之前都能按时产出的作业最近经常破线? 通常来说,引起作业执行变慢的原因有:quota 组 ...

  7. MaxCompute如何对SQL查询结果实现分页获取

    由于MaxCompute SQL本身不提供类似数据库的select * from table limit x offset y的分页查询逻辑.但是有很多用户希望在一定场景下能够使用获取类似数据库分页的 ...

  8. sql server 大数据处理

    对SQL Server数据表进行分区的过程分为三个步骤: 1)建立分区函数 2)建立分区方案 3)对表格进行分区 第一个步骤:建立分区函数 分区函数定义[u]how[/u],即你想要SQL Serve ...

  9. Oracle SQL之 序列使用限制

    Restrictions on Sequence Values You cannot use CURRVAL and NEXTVAL in thefollowing constructs:■ A su ...

  10. Mybatis 通过动态SQL获取序列值

      配置文件 <select id="getSeq" parameterType="string" resultType="long" ...

随机推荐

  1. (模板)Manacher算法:线性时间求字符串内回文子串的数量

    已通过leetcode647:https://leetcode-cn.com/problems/palindromic-substrings/ void get_d(vector<int> ...

  2. C++ bind函数

    bind()是一个函数适配器,返回一个可调用对象,他可以将一个函数的参数列表做魔改. 设置默认参数 using namespace std::placeholders; void f(int a, i ...

  3. 除gRPC之外的另一个选择,IceRPC-支持QUIC

    作者引言 自从19年开始接处到RPC,当时完全没有相关概念,接触到的都是http,tcp等,当时公司用的是zeroc出品的ice框架,对应rpc非常强大,跨平台,跨语言.可惜的国内并不是主流,主流是g ...

  4. WebStorm 没有找到本地修改记录LocalChanges,怎么查看本地修改记录

    一直用IDEA做后端Java开发,也写过vue+elementUI的前端代码,但前后端都放在一个项目里写,本地修改代码记录,可以在git-LocalChanges中查看修改记录. 最近前后端分离,自己 ...

  5. 记录--vue打印插件

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 网页实现打印 可以选择性的打印某一部分 的vue组件 1.引入 把print.js 下载到本地,然后放在src 下面添加文件夹里. pri ...

  6. C# 通用OCR识别 文字识别 中文识别

    软件说明 基于以下两个开源项目,做了再次封装 https://github.com/paddlepaddle/PaddleOCR PaddleOCRSharp: 本项目是一个基于PaddleOCR的C ...

  7. PSS:你距离NMS-free+提点只有两个卷积层 | 2021论文

      论文提出了简单高效的PSS分支,仅需在原网络的基础上添加两个卷积层就能去掉NMS后处理,还能提升模型的准确率,而stop-grad的训练方法也挺有意思的,值得一看 来源:晓飞的算法工程笔记 公众号 ...

  8. 【Java面试题】SpringMVC

    九.SpringMVC 63)SpringMVC 的流程 a.用户向服务器发送请求,请求被 SpringMVC 前端控制器 DispatchServlet 捕获: b.DispatcherServle ...

  9. Hadoop_05 使用xsync脚本命令分发,手动配置脚本

    在/usr/local/bin 目录下创建 xsync 文件,向里面添加 1 #!/bin/sh 2 # 获取输入参数个数,如果没有参数,直接退出 3 pcount=$# 4 if((pcount== ...

  10. Python 中的数字类型与转换技巧

    Python中有三种数字类型: int(整数) float(浮点数) complex(复数) 当您将值分配给变量时,将创建数字类型的变量: 示例:获取您自己的Python服务器 x = 1 # int ...