美文网首页
记一次——用sql进行数据清洗实例

记一次——用sql进行数据清洗实例

作者: 大美mixer | 来源:发表于2020-03-29 21:15 被阅读0次

数据库中有某段用户上报日志,记录着用户的一些操作行为。现要将其清洗后,并筛选出我们感兴趣的用户后,并整理为算法要求的格式。要求过程中只能用sql。

0 引言

源数据表介绍和分析

数据规模:每天2亿+条数据;
含义:每人每天登陆一次app则产生一条记录;
原始数据表字段如下所示:
Original_table

utdid user_id content ds
zxcvb {userid:;behavior:...} 20200101
asdfg user2 {userid:user2;behavior:...} 20200101
asdfg user2 {userid:user2;behavior:...} 20200101
  • utdid(string):设备id

    • 全不为空,后期可以用它来join其他表提取感兴趣用户。
  • user_id(string):用户id

    • 经过观察,发现有些为空,可能是一些未注册用户,但是我们有utdid已经足够了。
  • content(string):是一个字符串,记录着用户的行为,看上去比较像json。经探查,发现有重复数据,因此后续需要去除。

    • 最外层:{userid:user3;behavior:...} 。可以看到这里是一个map,仅有两个键user_id和behavior;我们对于userid不感兴趣,因为已经被提取为列user_id了。因此我们只对behavior感兴趣,里面记录着用户的操作行为。

    • behavior内层:

      "[{'locate':b1;'behavior':go;'timestamp':123451},

      {'locate':a1;'behavior':leave;'timestamp':123451},

      {'x':10;'y':8;'behavior':start;'timestamp':123456},

      {'x':11;'y':4;'behavior':end;'timestamp':123457},

      {'x':15;'y':12;'behavior':tap;'timestamp':123458},

      {'behavior':begin;'timestamp':123460}...]"

      • 可以发现内层记录用户按时间排序后的行为;
      • 看上去像是一个array,每个元素(一条数据)为一个行为,用map表示的,map里面则有行为的参数。
      • 但是很奇怪的是有很多双引号单引号,检测出来的数据类型也是字符串不是数组。
      • 总共有3种类型的数据:
        • 第一种类型为第1~2行带locate的,是位置记录数据,他们的时间戳相同。表示在某个地点,go表示到达某处,leave表达离开某处;可以发现他们的timestamp的值是一样的,因此表示用户在离开某处则立刻到达某处,在这个时间戳同时上报两条数据,于是我们就得到了用户在该时间从哪里来到哪里去的信息。
        • 第二种类型是3~5行带x和y的,是行为记录数据,他们的时间戳不同,但是在相邻两行。x和y为坐标。start表示动作开始,end表示动作结束;tap表示动作开始即结束。这些数据都是在第1行的locate=a1的位置进行的,也就是说在下一次遇到第一种类型的数据前,这些行为数据都在a1处发生。
        • 第三种类型是behavior为未知,只有时间戳的第6行数据;这可以视为用户在这个时间点没有任何行为,后台仅仅在这个时间点上报一条信息而已。因此后续需要清除。
  • ds(string):日期,格式为yyyymmdd

问题分析和结果预想

经过以上分析,我们发现本身content并不易读,因此我们希望将其变为易读模式。

那么我们大致可以有以下分析思路:(1)首先应将content数据解析出来,让其分行、分列;(2)有了最粗糙的原始数据后,那么应该进行一些简单的空值、异常值、重复值的清除;(3)得到清洗后的数据之后,我们发现用户的行为会上报两条数据,那么可能需要对其进行一个合并,让数据的展现形式更为简洁;(4)没有locate的操作信息是无用的,我们无法对其进行任何判断,因此还需要将其的locate信息补充上去;(5)除此之外,我们的用户还需要筛选,并且可能还需要加入其他的维度信息。

因此,根据以上分析,预想结果如下:

utdid 该用户其他维度信息(省略) go_locate leave_locate x_beg x_end y_beg y_end behavior timestamp Ds
asdfg ... b1 a1 go 123451 20200101
asdfg ... b1 a1 10 8 11 4 move 123456 20200101
asdfg ... b1 z1 15 12 15 12 tap 123468 20200101

其中第一条代表了第1~2行数据,表达去到了新位置,behavior=go;第二条代表了第3~4行数据,表达在某位置进行了移动,behavior=move;第三条代表了第5行数据,表示在某位置原地行动,behavior=tap;第6行数据不要。

这样就可以清晰的看到用户在某个时间点(when)在哪里(where)做了什么动作(what)都可以一目了然。也方便后续进行其他整合。

解决方案以及所涉及的知识点

上文分析会给我们一个大致方向,但是过程中可能还会碰到许多问题,以及还需考虑如何写使性能较好。因此根据分析,重新安排我们的解决方案如下:

  1. 步骤一:解析content内容并筛选用户

    1. 从content中得到behavior内容

    2. 筛选感兴趣用户以及获得用户的其他信息

    3. 将behabior数据分行分列

  2. 步骤二:剔除重复值、异常值、未知值

  3. 步骤三:数据合并与信息填充

    1. 给信息按时间顺序进行编号

    2. 多条数据合并为一条

    3. locate信息填充

  4. 步骤四:生成算法格式

这些解决步骤中所涉及知识点如下:

  1. 将json数据分行分列【1】

  2. 三值逻辑的坑【2】【更多知识:链接进入搜索“三值逻辑”】

  3. 重复值剔除【2】【更多知识:链接进入搜索“重复值”】

  4. 如何使用分区函数获得数据的行号【3.1】

  5. 如何使用做连接将两行数据并为一行【3.2.1】

  6. 如何解决on后不能跟“<>”和“or”【3.2.2】

  7. 如何找到排序后每行小于它的某字段的最大的最小值【3.2.2】

  8. 如何获得数据清洗思路【通读全文并体会】

  9. 隐藏:如何设计数仓架构【通读全文找彩蛋】

1 步骤一:解析content内容并筛选用户

注:这里,每张临时表都应是一个dag节点,且用ds分区。

如上文分析,content应是一个map{array[map1{}, map2{}....]}的格式,直接解析即可。但是之前说了,这条语句别识别是字符串,且有乱七八糟的引号,不能直接解析。这时候应去找对应的表开发人员。与其自己分析引号有什么规律,还不如直接问表开发人员数据是怎么合成的,解铃还须系铃人。这样一定可以找到准确的可逆操作,少采很多坑。很好的事,表开发人员直接给我了对应数据解析udf函数(说明他们开发时就已经想到解析的问题),可以直接将content分行。

自己想将其变成正常格式的数据可以参考:

class trace_json_parser(object):
    def evaluate(self, content): 
              // 让字符串变成可以解析的字符串(就是引号替换掉,这里注意转义字符的双引号问题)
        raw_data = content.replace('"[','[').replace(']"',']').replace('\\"','"').replace('\\\"','"')
          
        // 解析
        try:
            js = json.loads(raw_data)  
        except:
            return 'error' 
        
        behavior_track = js['behavior_track'] 
        return str(len(behavior_track))

1.1 从content中得到behavior的内容

select  DISTINCT utdid
                -- 自行开发的函数,用于得到content内容
                , get_json_object_object(a.content, '$.behavior') as (content)
                , ds
from    orginal_table
WHERE   ds = '${date}'
  • 第一步:使用distinct清除上报重复的数据
  • 第二步:用udf函数获得正确格式的content内容

1.2 筛选感兴趣用户以及获得用户的其他信息

要知道,后面将content分行后,数据量会变大,同一个用户会从几条数据变为很多很多条数据。因此这时join其他的表最划算。这里表user_info中有我们感兴趣用户的基本信息,此时用Inner join得到两张表用户的交集。

SELECT      a.ds
          , b.*
          , get_json_object_object(a.content, '$.behavior') as (content)
FROM 
(
    select  DISTINCT utdid, content, ds
    from    orginal_table
    WHERE   ds = '${date}'
) a
inner JOIN 
(
    SELECT  first_utdid as utdid
            , user_id
            , login_nick
            , os
            , os_version
            , app_version
            , device_model
            , resolution
    FROM    user_info --用户信息表  
    WHERE   ds = '${date}'
) b
on a.utdid = b.utdid --使用设备信息做关联

1.3 将behabior数据分行分列

在“源数据表的介绍和分析中”,我们提到数据分为3种类型,我们可知用户上报的行为信息总共只有locate, x, y, behavior, timestamp五种类型。因此便将其化为五列,数据中没有的列则为NULL。例如对于第一类数据,有locate, behavior, timestamp,则x和y则为NULL;同理,第二类数据locate为NULL。

这样一方面可以解决不同类型上报数据内容不统一的情况,另一方面我们确实也想知道第二类数据的locate是什么,为后续填补留个位置。

CREATE TABLE ods_gesture_point_test AS 
SELECT  utdid
        -- 一些用户信息
        , user_id
        , login_nick
        , os
        , os_version
        , app_version
        , device_model
        , resolution
        -- 按解析出来的内容分列
        , get_json_object_object(t1, '$.locate') as locate
        , get_json_object_object(t1, '$.x') as x
        , get_json_object_object(t1, '$.y') as y
        , get_json_object_object(t1, '$.behavior') as behavior
        , get_json_object_object(t1, '$.timestamp') as local_timestamp
        -- 分区信息
        , ds
FROM 
(
    SELECT  ds
            , user_id
            , login_nick
            , os
            , os_version
            , app_version
            , utdid
            , device_model
            , resolution
            , t1
    FROM    
    (
        SELECT  
                a.ds
                , b.*
                , get_json_object_object(a.content, '$.behavior') as (content)
        FROM 
        (
            select  DISTINCT utdid, content, ds
            from    orginal_table
            WHERE   ds = '${date}'
        ) a
        inner JOIN 
        (
            SELECT  utdid
                    , user_id
                    , login_nick
                    , os
                    , os_version
                    , app_version
                    , device_model
                    , resolution
            FROM    user_info --用户信息表  
            WHERE   ds = '${date}'
        ) b
        on a.utdid = b.utdid --使用设备信息做关联
    )
    -- json_array_to_str为自定义udf函数
    LATERAL VIEW EXPLODE(json_array_to_str(content)) t AS t1
) 
;

1.4 结果

这时,应该得到的表格式为:

utdid 该用户其他维度信息(省略) locate x y behavior local_timestamp ds
asdfg ... b1 go 123451 20200101
asdfg ... a1 leave 123451 20200101
asdfg ... 10 8 start 123456 20200101
asdfg ... 11 4 end 123457 20200101
asdfg ... 15 12 tap 123458 20200101
asdfg ... 123460 20200101

2 步骤二:剔除重复值、异常值、未知值

经过数据探查后,发现3个问题:

  1. 同一数据多次上报;

  2. behavior的数据因为业务原因,变成了locate数据;例如{behavior:a1; timestamp:12370;};

  3. 数据上报延迟,导致start和end行为直接夹杂了其他数据;例如

    utdid 该用户其他维度信息(省略) locate x y behavior local_timestamp ds
    asdfg ... 10 8 start 123471 20200101
    asdfg ... b1 go 123472 20200101
    asdfg ... a1 leave 123472 20200101
    asdfg ... 11 4 end 123473 20200101
  4. behavior不仅有NULL还有空值

那么我们分别解决:

  1. distinct
  2. 根据业务情况,若长度大于10则为locate, behavior=other
  3. 这个预测与后面的loacte填充操作差不多,一起做会节约资源,因此放到后面操作
  4. where筛选
CREATE TABLE ods_gesture_point_clean AS 
select  DISTINCT  -- 解决问题1
        login_nick
        , user_id
        , os
        , os_version
        , app_version
        , utdid
        , revolution
        , device_model
        , IF(locate is NULL AND LENGTH(behavior) > 10 and x is NULL AND y is NULL, behavior, page) as page --解决问题2
        , x
        , y
        , IF(locate is NULL AND LENGTH(behavior) > 10 and x is NULL AND y is NULL, 'other', behavior) as behavior --解决问题2
        , local_timestamp
        , ds
FROM    ods_gesture_point_test
WHERE   ds = '${date}'
                -- 解决问题4
        and behavior IS NOT NULL 
        and behavior <> ''
;

3 步骤三:数据合并与信息填充

这里我们需要将一个操作中的2行变成1行,并将locate信息填充到操作中去。

3.1 给信息按时间顺序进行编号

因为编号这个操作较耗时,单独建立一张表。

CREATE TABLE dwd_gesture_point_order_test AS 
SELECT  *
        , ROW_NUMBER() OVER(PARTITION BY login_nick ORDER BY local_timestamp) AS row_num
FROM    ods_gesture_point_clean_test
order   by login_nick, local_timestamp

得到:

utdid 该用户其他维度信息(省略) locate x y behavior local_timestamp row_num ds
asdfg ... b1 go 123451 1 20200101
asdfg ... a1 leave 123451 2 20200101
asdfg ... 10 8 start 123456 3 20200101
asdfg ... 11 4 end 123457 4 20200101
asdfg ... 15 12 tap 123458 5 20200101
asdfg ... 123460 6 20200101

3.2 多条数据合并为一条

我们希望将如下数据格式:

utdid 该用户其他维度信息(省略) locate x y behavior local_timestamp ds

变为:

utdid 该用户其他维度信息(省略) go leave x_beg x_end y_beg y_end behavior timestamp Ds

这样做一方面可以使数据表示更加简洁,一条数据包含了更多信息;另一方面方便数据查找与分类汇总;

3.2.1 第一类数据二条合一条

首先我们处理第一类数据,原因有二:一方面,后续的信息填充需要使用到第一类数据,而先将其进行合并,则可以在后续处理中省去很多功夫;另一方面,由于时间戳相同,处理起来相对比第二类数据较简单。

对于第一类数据而言,同一时间上报两条数据,这里我们把其合成一条;

utdid 该用户其他维度信息(省略) locate x y behavior local_timestamp ds
asdfg ... b1 go 123451 20200101
asdfg ... a1 leave 123451 20200101

这里为了后续方便处理,x和y拓展成了x_beg,x_end,y_beg,y_end,当然,这里的值都是null

SELECT  a.login_nick
        , a.user_id
        , a.os
        , a.os_version
        , a.app_version
        , a.utdid
        , a.resolution
        , a.device_model
        , a.locate as go_locate --现地点
        , b.locate as leave_locate --上一个地点
        , a.x as x_beg
        , a.x as x_end
        , a.y as y_beg
        , a.y as y_end
        , a.behavior
        , a.local_timestamp
        , a.row_num
        , a.ds
FROM 
(
    SELECT  *
    FROM    ods_gesture_point_order_test
    WHERE   page is not null AND behavior IN ('go', 'other' )
)a
LEFT JOIN 
(
    SELECT  *
    FROM    ods_gesture_point_order_test
    WHERE   page is not null AND behavior = 'leave'
)b
on a.ds = b.ds and a.utdid = b.utdid AND a.local_timestamp = b.local_timestamp
;

结果如下:

utdid 该用户其他维度信息(省略) go_locate leave_locate x_beg x_end y_beg y_end behavior timestamp Ds
asdfg ... b1 a1 go 123451 20200101

3.2.2 第二类数据二条合一条

对于第二类数据,时间戳不同,因此需要用到row_num;除此之外,存在数据上报延迟的情况;

在本步骤,我们暂时不将其填充locate信息,但是为了保持格式相同,因此让go=locate, leave=locate,其实为NULL;

同理,我们也由易到难介绍。

1. 对于位置不变的数据

这类数据最简单,在同一时间戳的位置不变,因此x_beg=x, x_end=x , y_beg=y, y_end=y;

create table t1 as
select  login_nick
        , user_id
        , os
        , os_version
        , app_version
        , utdid
        , resolution
        , device_model
        -- locate为NULL
        , locate as go_locate
        , locate as leave_locate
        -- 位置不变
        , x as x_beg
        , x as x_end
        , y as y_beg
        , y as y_end
        , behavior
        , local_timestamp
        , row_num
        , ds
FROM    ods_gesture_point_order_test
WHERE   page is null AND behavior = 'tap'
2. 对于位置改变的数据

在这一步我们发现了2个问题:

  1. behavior=start和behavior=end两条数据对应的x和y相同,这时我们将其归类为一个新的behavior=click;

  2. 由于sql在表与表连接中不可以使用不等式和或,只能用“=”;否则我们可以使用如下语句做来解决数据延迟上报的问题。就是因为下列语句不可行,因此我们要对其进行特殊的清洗。

    -- 逻辑如下,但是这条语句是不可运行的
    select   ...
    from     xxx as a
    left join xxx as b
    on           (a.row_num = (b.row_num - 1)) -- 相邻两行
                 OR 
                 (a.row_num = (b.row_num - 3) --要么中间有2行间隔
             and 
             a.row_num <> (b.row_num - 1) -- 排除start,end,start,end的情况,这时也会有2行间隔
            )
    

因此,我们先对相邻两行数据进行合并。这里,我们以(1)behavior=start的x和y分别作为x_beg和y_beg,behavior=end的x和y分别作为x_end和y_end;(2)按照行号做left join。除此之外,(3)local_timestamp和row_num取b表(即behavior = 'end'的数据),原因为当数据延迟时(accb),做left join后,由于a找不到符合条件的b,则x_end,y_end,local_timestamp,row_num均为NULL,后续用WHERE语句排除这条数据,以免又产生无效数据。

create table t2 as
SELECT  a.login_nick
        , a.user_id
        , a.os
        , a.os_version
        , a.app_version
        , a.utdid
        , a.resolution
        , a.device_model
        , a.locate as go_locate
        , a.locate as leave_locate
        , a.x as x_beg
        , b.x as x_end
        , a.y as y_beg
        , b.y as y_end
        , if(a.x = b.x AND a.y = b.y, 'click', 'move') as behavior
        , b.local_timestamp
        , b.row_num
        , a.ds
FROM 
(
    SELECT  *
    FROM    ods_gesture_point_order_test_1
    WHERE   locate is null AND behavior = 'start'
)a
LEFT JOIN 
(
    SELECT  *
    FROM    ods_gesture_point_order_test_1
    WHERE   locate is null AND behavior = 'end'
)b
on a.ds = b.ds AND a.utdid = b.utdid AND (a.row_num = (b.row_num - 1)) --相邻两行
WHERE b.local_timestamp is NOT NULL
3. 数据上报延迟解决方案

如第2章,可知数据延迟的情况如下;经过统计后,其一般中间间隔2行。

utdid 该用户其他维度信息(省略) locate x y behavior local_timestamp ds
asdfg ... 10 8 start 123471 20200101
asdfg ... b1 go 123472 20200101
asdfg ... a1 leave 123472 20200101
asdfg ... 11 4 end 123473 20200101

这里又出现一个问题,如何排除start,end,start,end的情况?他们的第1个start和第2个end也相差两行,同时on后也不能写不等式。对于这个问题,只好使用where后嵌套子查询办法了。

create table t3 as
SELECT  a.login_nick
        , a.user_id
        , a.os
        , a.os_version
        , a.app_version
        , a.utdid
        , a.resolution
        , a.device_model
        , a.locate as go_locate
        , a.locate as leave_locate
        , a.x as x_beg
        , b.x as x_end
        , a.y as y_beg
        , b.y as y_end
        , if(a.x = b.x AND a.y = b.y, 'click', 'swipe') as behavior
        , b.local_timestamp
        , b.row_num
        , a.ds
FROM 
(
    SELECT  *
    FROM    ods_gesture_point_order_test
    WHERE   locate is null AND behavior = 'begin'
                    -- 排除start,end,start,end的情况
            and (row_num + 1) NOT in (SELECT row_num FROM ods_gesture_point_order_test WHERE  locate is null AND behavior = 'end')
)a
LEFT JOIN 
(
    SELECT  *
    FROM    ods_gesture_point_order_test
    WHERE   locate is null AND behavior = 'end'
)b
on a.ds = b.ds AND a.utdid = b.utdid AND (a.row_num = (b.row_num - 3)) --行数相差3
4. 代码合并和结果

最终将1、2、3的代码使用union合并起来

CREATE TABLE ods_gesture_point_behavior_test AS
t1
union
t2
union
t3

结果:

utdid 该用户其他维度信息(省略) go_locate leave_locate x_beg x_end y_beg y_end behavior timestamp ds
asdfg ... 1 2 1 2 click 123449 20200101
asdfg ... 10 8 11 4 move 123456 20200101
asdfg ... 15 12 15 12 tap 123468 20200101

3.3 locate信息填充

对于每个操作,我们想知道时间小于它(row_num小于它)且离它最近的loacte(小于它的row_num的最大值)。即对每行数据找寻在row_num小于它的最大的最小值

实现步骤

  1. 获得其小于该行的locate的row_num;
  2. 求出得到row_num的最大值;
  3. 根据x和y的变化情况,丰富behavior的形式;
  4. 与合并,得到完整的用户行为数据。
CREATE TABLE dwd_gesture_point_ans_test AS 
SELECT * FROM ods_gesture_point_page_test

UNION  --步骤4

SELECT login_nick
        , user_id
        , os
        , os_version
        , app_version
        , utdid
        , resolution
        , device_model
        , go_locate
        , leave_locate
        , x_beg
        , x_end
        , y_beg
        , y_end
        -- 步骤3
        -- 后期可用behavior like '%move%'来筛选
        , CASE WHEN (((ABS(x_beg - x_end) > ABS(y_beg - y_end))) and ((x_beg - x_end) > 0)) THEN 'leftMove'
               WHEN (((ABS(x_beg - x_end) > ABS(y_beg - y_end))) and ((x_beg - x_end) < 0)) THEN 'rightMove'
               WHEN (((ABS(x_beg - x_end) < ABS(y_beg - y_end))) and ((y_beg - y_end) > 0)) THEN 'upMove'
               WHEN (((ABS(x_beg - x_end) < ABS(y_beg - y_end))) and ((y_beg - y_end) < 0)) THEN 'downMove'
            END AS behavior
        , local_timestamp
        , row_num
        , ds      
from
(
    SELECT  login_nick
                , user_id
            , os
            , os_version
            , app_version
            , utdid
            , resolution
            , device_model
            , go_locate
            , leave_locate
            , x_beg
            , x_end
            , y_beg
            , y_end
            , behavior
            , local_timestamp
            , row_num
            , locate_num
            , ds 
            , MAX(locate_num) OVER (PARTITION BY utdid, x_beg, x_end, y_beg, y_end, behavior, local_timestamp, row_num) as max_locate_num --步骤2
    FROM 
    (
        SELECT  a.go_locate
                , a.leave_locate
                , a.row_num as locate_num
                , b.login_nick
                , b.user_id
                , b.os
                , b.os_version
                , b.app_version
                , b.utdid
                , b.resolution
                , b.device_model
                , b.x_beg
                , b.x_end
                , b.y_beg
                , b.y_end
                , b.behavior
                , b.local_timestamp
                , b.row_num
                , b.ds 
        FROM  
        (
            SELECT  *
            FROM    ods_gesture_point_behavior_test
        ) b
        LEFT JOIN 
        (
            SELECT  utdid
                    , go_locate
                    , leave_locate
                    , row_num
                    , ds
            FROM    ods_gesture_point_page_test
        ) a
        on  a.ds = b.ds and a.utdid = b.utdid
        WHERE a.row_num < b.row_num --步骤1
    )
    having locate_num = max_locate_num --步骤2
)
;

3.4 结果

这样就生成了需求的结果:

utdid 该用户其他维度信息(省略) go_locate leave_locate x_beg x_end y_beg y_end behavior timestamp Ds
asdfg ... b1 a1 go 123451 20200101
asdfg ... b1 a1 10 8 11 4 move 123456 20200101
asdfg ... b1 z1 15 12 15 12 tap 123468 20200101

4 步骤四:生成算法格式

最后,根据算法同学的要求,再生成他需求的格式。这一步可根据不同需求进行改变。

要求:每天,分locate,将数据聚合起来并写成一条字符串。字符串包括utdid,坐标,位置,时间戳;

分析:他所需的数据格式大概如下

ds locate info
20200101 a1 user1:behavior,x_beg,x,end,y_beg,y_end,timestamp;behavior,x_beg,x,end,y_beg,y_end,timestamp;user2:...
20200101 a2 ...
20200202 a1 ...

步骤:

  1. 首先将behavior,x_beg,x,end,y_beg,y_end,timestamp用“,”隔开做成一条字符串,这样一个用户就会有多条包含行为信息的字符串str1=”behavior,x_beg,x,end,y_beg,y_end,timestamp“;
  2. 将该用户的这些字符串用“;”拼接成新的字符串str2="str1;str1;...";
  3. 将用户名和str2用":"连接起来,变格式为“用户名:str2”的字符串str3;
  4. 最后将不同用户之间的str3使用";;"拼接为info="str3;;str3;;....."
CREATE TABLE dws_gesture_point_sf_test AS
SELECT page, wm_concat(';;',info) as info -- 步骤4
FROM 
(
    SELECT ds, locate, CONCAT_WS(':', utdid, info) OVER (PARTITION BY ds, locate, ORDER BY row_num) as info -- 步骤3
    FROM 
    (
        SELECT locate, utdid, wm_concat(';',info) as info -- 步骤2
        FROM 
        (
            SELECT ds, locate, utdid, CONCAT_WS(',',behavior,x_beg,x_beg,y_beg,y_end,loca_timestamp,row_num) as info -- 步骤1
            FROM 
            (
                SELECT  ds, locate, utdid, behavior, x_beg, x_beg, y_beg, y_end, loca_timestamp, row_num
                FROM    dwd_gesture_point_ans_test
                WHERE   behavior <> 'locate' and behavior <> 'other'
            )
        )
        GROUP BY ds, page, utdid
    )
)
GROUP BY page
;

5 完成,撒花!

数据清洗是一个探索的过程,先有一个大的方向,然后走一步看一步,每次可能会发现新的问题需要处理。

但是这次比较顺利,没有进行任何返工。

不过暂时还没做性能优化,后续要研究并完成。

相关文章

  • 记一次——用sql进行数据清洗实例

    数据库中有某段用户上报日志,记录着用户的一些操作行为。现要将其清洗后,并筛选出我们感兴趣的用户后,并整理为算法要求...

  • sql学会这几步,数据分析就能立马掌握

    什么,sql也能做分析? 常见的数据清洗,预处理,,数据分类,数据筛选,分类汇总,以及数据透视等操作,用SQL一样...

  • pandas常用函数

      说起pandas这个是python数据清洗的利器,它可以让你像sql一样操作数据,同时可以对数据进行各种计算,...

  • 数据库与网络学习记忆部分

    记是不会记住的,这辈子都不会记住的!!! 1、数据库建表sql语句 create TABLE 实例名字 ( XX ...

  • 2019-08-14 分析lianjia数据(二)——SPSS数

    前面写了用python分析lianjia数据,实际上也可用SPSS进行处理。 使用SPSS进行数据清洗,继续以Li...

  • BD第4课:数据清洗

    如果说抓取数据是数据分析的第1步,那么数据清洗就是数据分析的第2步,那么为什么要进行数据清洗呢?如何进行数据清洗呢...

  • 机器学习-数据清洗

    本文由brzhang发表 数据清洗 首先,为何需要对数据进行清洗 数据清洗的工作绝壁是非常枯燥的,做数据研究的的人...

  • 数据库优化 - 实例优化

    从网上去搜数据库优化基本都是从SQL层次进行优化的,很少有提及到数据库本身的实例优化。就算有也都是基于某个特定数据...

  • 数据库优化 - 实例优化

    从网上去搜数据库优化基本都是从SQL层次进行优化的,很少有提及到数据库本身的实例优化。就算有也都是基于某个特定数据...

  • 502 stata 如何读取SQL数据

    本文主要介绍用Stata读取SQL数据的常用命令 odbc list 查看当前有哪些可用的odbc驱动实例odbc...

网友评论

      本文标题:记一次——用sql进行数据清洗实例

      本文链接:https://www.haomeiwen.com/subject/wqcpuhtx.html