美文网首页
记一次——用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进行数据清洗实例

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