暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

SQL优化:如何避免扫描历史明细高效获取长周期用户集

数据打工人的自我修养 2025-07-21
20
需求场景:拉取全量或者长周期的数据,比如下面的案例,拉取历史指定产品的用户订单及近1年的订单和订单天数,日频率更新。
    select user_id
      ,product_code
       -- 总订单数
      ,count(order_no) as order_cnt   -- 历史总订单数
      ,count(case when dt>='${1_year_ago_day}' then order_no endas order_cnt_1year
        -- 近一年订单天数
        ,count(distinct case when dt>='${1_year_ago_day}' then dt endas order_days_1year    -- 订单天数
    from order_info   -- 订单明细表
    where dt >= '2017-05-01' and dt <= '${1_day_ago}'  -- 假设公司业务从17年开始的,dt为分区字段
    group by user_id,product_code  -- 产品编码

    问题分析:明细表拉取数据体量太大,随着时间推移,作业计算资源消耗越来越大。此外在数仓一般库表有特定的生命周期配置,超过一定时长,历史分区会被清理掉。如果是遇到拉取全量明细先join再groupby聚合,基于埋点日志明细计算全量(一般日志数据体量很大),其计算消耗难以估量。

    优化分析:搭建全量中间表,近1年365天的数据 =  T-2近1年的数据 + T-1增量 - T-366增量
    优化前后对比:优化前需要拉取n个分区数据(这可能是历史全量明细),优化后只需要取三个分区数据。如必要,对于天表也可以先进行天粒度的聚合。 

    表字段结构:
      create table lyy_gz.dws_lyy_shangchuang_product_tag_df(
          user_id string comment '用户id'
          ,product_code TINYINT comment '产品码值:码值映射xxxxx'
          ,min_date string comment '最早订单日期'
          ,max_date string comment '最近订单日期'
          ,cnt bigint comment '总订单或者关注次数'
          ,near_1year_cnt bigint comment '近一年订单或者关注次数:总订单cnt不能反映最近情况,故添加该字段'
          ,near_1year_active_days smallint comment '近一年活跃天数'
      ) comment '用户商创产品订单|关注标识;一分购20年1月开始统计,其他最早数据从2023年6月开始计'
      partitioned by (day string comment '分区日期yyy-mm-dd')

      处理步骤:
      1. 新建目标表,设计表结构如上(按需设计,此处仅为样例)
      2. 将历史查询截止T-2日期一次性写入到表T-2分区
      3. 新建累全量作业,截止T-1 n日累计 = 截止T-2 n日累计 + T-1增量  -  T-n-1增量
      4. 补录作业T-1周期实例刷新T-1数据

      如下为T-1代码样例:
        insert OVERWRITE  table lyy_gz.dws_lyy_shangchuang_product_tag_df partition(day='${1_day_ago}')
        select user_id   -- 用户id
            ,product_code   -- 产品id
            ,min(min_date) as min_date   -- 最早订单日期
            ,max(max_date) as max_date   -- 最近订单日期
            ,sum(cnt) as cnt     -- 订单数量
            ,sum(near_1year_cnt) as near_1year_cnt   -- 近1年累计订单数
            ,sum(near_1year_active_days) as near_1year_active_days  -- 近1年订单天数
        from 
        (   -- 截止昨日累计 + 当日增量 = 截止当日累计;截止昨日近1年 + 当日增量 - 一年前那天 = 截止当日近1年数据
            -- 截止前日累计数据
            select user_id 
                ,product_code
                ,min_date 
                ,max_date 
                ,cnt 
                ,near_1year_cnt
                ,near_1year_active_days
            from lyy_gz.dws_lyy_shangchuang_product_tag_df
            where day='${2_day_ago}'  -- 昨天的全量
            union all 
            select lyy_user_id as user_id 
                    -- 将不同的产品编码统一转化为顺序码值
                    ,case channel when 101 then 1
                    when 100 then 2
                    when 102 then 3
                    when 2 then 9
                    end as product_code   --  产品编码
                -- created是datetime创建时间,时间上个跟表ds分区保持一致
                ,substr(min(created),1,10as min_date 
                ,substr(max(created),1,10as max_date  
                ,count(case when ds='${1dayago}' then out_trade_no endas cnt -- 订单数量- 当日增量
                -- 加上今天增量-一年前那天的数据=截止今天近1年的数据
                ,count(case when ds='${1dayago}' then out_trade_no end)-count(case when ds='${1yearago}' then out_trade_no endas near_1year_cnt   
                ,,max(case when day='${1dayago}' then 1 else 0 end- max(case when day='${1yearago}' then 1 else 0 endas near_1year_active_days
            from lyy_gz.dwd_third_redeem_order t1 
            where ds in ('${1dayago}','${1yearago}')
                and channel in (   -- 产品编码
                    2    -- 移动积分
                    ,100   -- 美团一分购
                    ,101  -- 京东一分购
                    ,102  -- 抖音一分购
                )
                and status=2  -- 产品状态码
        ) uu  
        group by user_id,product_code

        以上的${2_day_ago},${1_day_ago},${1dayago},${1yearago}为事先计算好的参数。不同计算引擎的SQL语法及函数可能会有些许差异,以上仅供参考。




        更多内容,长按下方二维码关注我,感谢!


        文章转载自数据打工人的自我修养,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

        评论