如何充分發揮 SQL 能力?

資料庫工作筆記發表於2023-10-27

如何充分發揮 SQL 能力?

來源:阿里雲開發者

阿里妹導讀

如何充分發揮 SQL 能力,是本篇文章的主題。本文嘗試獨闢蹊徑,強調透過靈活的、發散性的資料處理思維,就可以用最基礎的語法,解決複雜的資料場景。

一、前言


1.1 初衷

如何高效地使用 MaxCompute(ODPS)SQL ,將基礎 SQL 語法運用到極致。

在大資料如此流行的今天,不只是專業的資料人員,需要經常地跟 SQL 打交道,即使是產品、運營等非技術同學,也會或多或少地使用到 SQL ,如何高效地發揮 SQL 的能力,繼而發揮資料的能力,變得尤為重要。
MaxCompute(ODPS)SQL 發展到今天已經頗為成熟,作為一種 SQL 方言,其 SQL 語法支援完備,具有非常豐富的內建函式,支援開窗函式、使用者自定義函式、使用者自定義型別等諸多高階特性,可以高效地應用在各種資料處理場景。
如何充分發揮 SQL 能力,是本篇文章的主題。本文嘗試獨闢蹊徑,強調透過靈活的、發散性的資料處理思維,就可以用最基礎的語法,解決複雜的資料場景。

1.2 適合人群

不論是初學者還是資深人員,本篇文章或許都能有所幫助,不過更適合中級、高階讀者閱讀。

本篇文章重點介紹資料處理思維,並沒有涉及到過多高階的語法,同時為了避免主題發散,文中涉及的函式、語法特性等,不會花費篇幅進行專門的介紹,讀者可以按自身情況自行了解。

1.3 內容結構

本篇文章將圍繞數列生成、區間變換、排列組合、連續判別等主題進行介紹,並附以案例進行實際運用講解。每個主題之間有輕微的前後依賴關係,依次閱讀更佳。

1.4 提示資訊

本篇文章涉及的 SQL 語句只使用到了 MaxCompute(ODPS)SQL 基礎語法特性,理論上所有 SQL 均可以在當前最新版本中執行,同時特意註明,執行環境、相容性等問題不在本篇文章關注範圍內。

二、數列

數列是最常見的資料形式之一,實際資料開發場景中遇到的基本都是有限數列。本節將從最簡單的遞增數列開始,找出一般方法並推廣到更泛化的場景。

2.1 常見數列

2.1.1 一個簡單的遞增數列

首先引出一個簡單的遞增整數數列場景:

  • 從數值 0 開始;
  • 之後的每個數值遞增 1 ;
  • 至數值 3 結束;

如何生成滿足以上三個條件的數列?即 [0,1,2,3] 。

實際上,生成該數列的方式有多種,此處介紹其中一種簡單且通用的方案。







-- SQL - 1select    t.pos as a_nfrom (    select posexplode(split(space(3), space(1), false))) t;

如何充分發揮 SQL 能力?

透過上述 SQL 片段可得知,生成一個遞增序列只需要三個步驟:

1)生成一個長度合適的陣列,陣列中的元素不需要具有實際含義;
2)透過 UDTF 函式 posexplode 對陣列中的每個元素生成索引下標;

3)取出每個元素的索引下標。以上三個步驟可以推廣至更一般的數列場景:等差數列、等比數列。下文將以此為基礎,直接給出最終實現模板。

2.1.2 等差數列

若設首項如何充分發揮 SQL 能力?,公差為 如何充分發揮 SQL 能力?,則等差數列的通項公式為 如何充分發揮 SQL 能力?

SQL 實現:







-- SQL - 2select    a + t.pos * d as a_nfrom (    select posexplode(split(space(n - 1), space(1), false))) t;

2.1.3 等比數列

若設首項 如何充分發揮 SQL 能力?,公比為 如何充分發揮 SQL 能力?,則等比數列的通項公式為如何充分發揮 SQL 能力? 

SQL 實現:







-- SQL - 3select    a * pow(q, t.pos) as a_nfrom (    select posexplode(split(space(n - 1), space(1), false))) t;
提示:亦可直接使用 MaxCompute(ODPS)系統函式 sequence 快速生成數列。





-- SQL - 4select sequence(1, 3, 1);
-- result[1, 2, 3]

2.2 應用場景舉例

2.2.1 還原任意維度組合下的維度列簇名稱

在多維分析場景下,可能會用到高階聚合函式,如 cube 、 rollup 、 grouping sets 等,可以針對不同維度組合下的資料進行聚合統計。

場景描述

現有使用者訪問日誌表 visit_log ,每一行資料表示一條使用者訪問日誌。
















-- SQL - 5with visit_log as (    select stack (        6,        '2024-01-01', '101', '湖北', '武漢', 'Android',        '2024-01-01', '102', '湖南', '長沙', 'IOS',        '2024-01-01', '103', '四川', '成都', 'Windows',        '2024-01-02', '101', '湖北', '孝感', 'Mac',        '2024-01-02', '102', '湖南', '邵陽', 'Android',        '2024-01-03', '101', '湖北', '武漢', 'IOS'    )     -- 欄位:日期,使用者,省份,城市,裝置型別    as (dt, user_id, province, city, device_type))select * from visit_log;
現針對省份 province , 城市 city, 裝置型別 device_type 三個維度列,透過 grouping sets 聚合統計得到了不同維度組合下的使用者訪問量。問:
1)如何知道一條統計結果是根據哪些維度列聚合出來的?

2)想要輸出 聚合的維度列的名稱,用於下游的報表展示等場景,又該如何處理?

解決思路

可以藉助 MaxCompute(ODPS)提供的 GROUPING__ID 來解決,核心方法是對 GROUPING__ID 進行逆向實現。 

如何充分發揮 SQL 能力?

詳細步驟如下:

一、準備好所有的 GROUPING__ID 。

生成一個包含如何充分發揮 SQL 能力?個數值的遞增數列,將每個數值轉為 2 進位制字串,並展開該 2 進位制字串的每個位元位。

GROUPING__ID

bits

0

{ ..., 0, 0, 0 }

1

{ ..., 0, 0, 1 }

2

{ ..., 0, 1, 0 }

3

{ ..., 0, 1, 1 }

...

...

2n


...

其中 如何充分發揮 SQL 能力? 為所有維度列的數量,如何充分發揮 SQL 能力? 即為所有維度組合的數量,每個數值表示一種 GROUPING__ID。

二、準備好所有維度名稱。

生成一個字串序列,依次儲存如何充分發揮 SQL 能力?個維度列的名稱,即


{ dim_name_1, dim_name_2, ..., dim_name_n }
三、將 GROUPING__ID 對映到維度列名稱。

對於 GROUPING__ID 遞增數列中的每個數值,將該數值的 2 進位制每個位元位與維度名稱序列的下標進行對映,輸出所有對應位元位 0 的維度名稱。例如:







GROUPING__ID:3 => { 0, 1, 1 }維度名稱序列:{ 省份, 城市, 裝置型別 }
對映:{ 0:省份, 1:城市, 1:裝置型別 }
GROUPING__ID 為 3 的資料行聚合維度即為:省份

SQL 實現




































-- SQL - 6with group_dimension as (    select -- 每種分組對應的維度欄位        gb.group_id, concat_ws(",", collect_list(case when gb.placeholder_bit = 0 then dim_col.val else null end)) as dimension_name    from (        select groups.pos as group_id, pe.*        from (            select posexplode(split(space(cast(pow(2, 3) as int) - 1), space(1), false))        ) groups -- 所有分組        lateral view posexplode(regexp_extract_all(lpad(conv(groups.pos,10,2), 3, "0"), '(0|1)')) pe as placeholder_idx, placeholder_bit -- 每個分組的bit資訊    ) gb    left join ( -- 所有維度欄位        select posexplode(split("省份,城市,裝置型別", ','))    ) dim_col on gb.placeholder_idx = dim_col.pos    group by gb.group_id)select     group_dimension.dimension_name,    province, city, device_type,    visit_countfrom (    select        grouping_id(province, city, device_type) as group_id,        province, city, device_type,        count(1) as visit_count    from visit_log b    group by province, city, device_type    GROUPING SETS(        (province),        (province, city),        (province, city, device_type)    )) tjoin group_dimension on t.group_id = group_dimension.group_idorder by group_dimension.dimension_name;

dimension_name

province

city

device_type

visit_count

省份

湖北

NULL

NULL

3

省份

湖南

NULL

NULL

2

省份

四川

NULL

NULL

1

省份,城市

湖北

武漢

NULL

2

省份,城市

湖南

長沙

NULL

1

省份,城市

湖南

邵陽

NULL

1

省份,城市

湖北

孝感

NULL

1

省份,城市

四川

成都

NULL

1

省份,城市,裝置型別

湖北

孝感

Mac

1

省份,城市,裝置型別

湖南

長沙

IOS

1

省份,城市,裝置型別

湖南

邵陽

Android

1

省份,城市,裝置型別

四川

成都

Windows

1

省份,城市,裝置型別

湖北

武漢

Android

1

省份,城市,裝置型別

湖北

武漢

IOS

1

三、區間

區間相較數列具有不同的資料特徵,不過在實際應用中,數列與區間的處理具有較多相通性。本節將介紹一些常見的區間場景,並抽象出通用的解決方案。

3.1 常見區間操作

3.1.1 區間分割

已知一個數值區間如何充分發揮 SQL 能力?,如何將該區間均分成 如何充分發揮 SQL 能力? 段子區間?

該問題可以簡化為數列問題,數列公式為 如何充分發揮 SQL 能力? ,其中如何充分發揮 SQL 能力?具體步驟如下

1)生成一個長度為 如何充分發揮 SQL 能力?的陣列;
2)透過 UDTF 函式 posexplode 對陣列中的每個元素生成索引下標;

3)取出每個元素的索引下標,並進行數列公式計算,得出每個子區間的起始值與結束值。

SQL 實現:








-- SQL - 7select    a + t.pos * d as sub_interval_start, -- 子區間起始值    a + (t.pos + 1) * d as sub_interval_end -- 子區間結束值from (    select posexplode(split(space(n - 1), space(1), false))) t;

3.1.2 區間交叉

已知兩個日期區間存在交叉 ['2024-01-01', '2024-01-03'] 、 ['2024-01-02', '2024-01-04']。問:

1)如何合併兩個日期區間,並返回合併後的新區間?

2)如何知道哪些日期是交叉日期,並返回該日期交叉次數?

解決上述問題的方法有多種,此處介紹其中一種簡單且通用的方案。核心思路是結合數列生成、區間分割方法,先將日期區間分解為最小處理單元,即多個日期組成的數列,然後再基於日期粒度做統計。具體步驟如下:

1)獲取每個日期區間包含的天數;
2)按日期區間包含的天數,將日期區間拆分為相應數量的遞增日期序列;

3)透過日期序列統計合併後的區間,交叉次數。

SQL 實現:
























-- SQL - 8with dummy_table as (    select stack(        2,        '2024-01-01', '2024-01-03',        '2024-01-02', '2024-01-04'    ) as (date_start, date_end))select     min(date_item) as date_start_merged,     max(date_item) as date_end_merged,     collect_set( -- 交叉日期計數        case when date_item_cnt > 1 then concat(date_item, ':', date_item_cnt) else null end    ) as overlap_datefrom (    select         -- 拆解後的單個日期        date_add(date_start, pos) as date_item,        -- 拆解後的單個日期出現的次數        count(1) over (partition by date_add(date_start, pos)) as date_item_cnt    from dummy_table    lateral view posexplode(split(space(datediff(date_end, date_start)), space(1), false)) t as pos, val) t;

date_start_merged

date_end_merged

overlap_date

2024-01-01

2024-01-04

["2024-01-02:2","2024-01-03:2"]


?增加點兒難度!

如果有多個日期區間,且區間之間交叉狀態未知,上述問題又該如何求解。即:

1)如何合併多個日期區間,並返回合併後的多個新區間?

2)如何知道哪些日期是交叉日期,並返回該日期交叉次數?

SQL 實現:






























-- SQL - 9with dummy_table as (    select stack(        5,        '2024-01-01', '2024-01-03',        '2024-01-02', '2024-01-04',        '2024-01-06', '2024-01-08',        '2024-01-08', '2024-01-08',        '2024-01-07', '2024-01-10'    ) as (date_start, date_end))select    min(date_item) as date_start_merged,     max(date_item) as date_end_merged,    collect_set( -- 交叉日期計數        case when date_item_cnt > 1 then concat(date_item, ':', date_item_cnt) else null end    ) as overlap_datefrom (    select         -- 拆解後的單個日期        date_add(date_start, pos) as date_item,        -- 拆解後的單個日期出現的次數        count(1) over (partition by date_add(date_start, pos)) as date_item_cnt,        -- 對於拆解後的單個日期,重組為新區間的標記        date_add(date_add(date_start, pos), 1 - dense_rank() over (order by date_add(date_start, pos))) as cont    from dummy_table    lateral view posexplode(split(space(datediff(date_end, date_start)), space(1), false)) t as pos, val) tgroup by cont;

date_start_merged

date_end_merged

overlap_date

2024-01-01

2024-01-04

["2024-01-02:2","2024-01-03:2"]

2024-01-06

2024-01-10

["2024-01-07:2","2024-01-08:3"]

3.2 應用場景舉例

3.2.1 按任意時段統計資料

場景描述

現有使用者還款計劃表 user_repayment ,該表內的一條資料,表示使用者在指定日期區間內 [date_start, date_end] ,每天還款 repayment 元。













-- SQL - 10with user_repayment as (    select stack(        3,        '101', '2024-01-01', '2024-01-15', 10,        '102', '2024-01-05', '2024-01-20', 20,        '103', '2024-01-10', '2024-01-25', 30    )     -- 欄位:使用者,開始日期,結束日期,每日還款金額    as (user_id, date_start, date_end, repayment))select * from user_repayment;
如何統計任意時段內(如:2024-01-15至2024-01-16)每天所有使用者的應還款總額?

解決思路

核心思路是將日期區間轉換為日期序列,再按日期序列進行彙總統計。

SQL 實現















-- SQL - 11select     date_item as day,     sum(repayment) as total_repaymentfrom (    select         date_add(date_start, pos) as date_item,        repayment    from user_repayment    lateral view posexplode(split(space(datediff(date_end, date_start)), space(1), false)) t as pos, val) twhere date_item >= '2024-01-15' and date_item <= '2024-01-16'group by date_itemorder by date_item;

day

total_repayment

2024-01-15

60

2024-01-16

50

四、排列組合

排列組合是針對離散資料常用的資料組織方法,本節將分別介紹排列、組合的實現方法,並結合例項著重介紹透過組合對資料的處理。

4.1 常見排列組合操作

4.1.1 排列

已知字元序列 [ 'A', 'B', 'C' ] ,每次從該序列中可重複地選取出 2 個字元,如何獲取到所有的排列?

藉助多重 lateral view 即可解決,整體實現比較簡單。







-- SQL - 12select     concat(val1, val2) as permfrom (select split('A,B,C', ',') as characters) dummylateral view explode(characters) t1 as val1lateral view explode(characters) t2 as val2;

perm

AA

AB

AC

BA

BB

BC

CA

CB

CC

4.1.2 組合

已知字元序列 [ 'A', 'B', 'C' ] ,每次從該序列中可重複地選取出 2 個字元,如何獲取到所有的組合?

藉助多重 lateral view 即可解決,整體實現比較簡單。








-- SQL - 13select     concat(least(val1, val2), greatest(val1, val2)) as combfrom (select split('A,B,C', ',') as characters) dummylateral view explode(characters) t1 as val1lateral view explode(characters) t2 as val2group by least(val1, val2), greatest(val1, val2);

comb

AA

AB

AC

BB

BC

CC


提示:亦可直接使用 MaxCompute(ODPS)系統函式 combinations 快速生成組合。






-- SQL - 14select combinations(array('foo', 'bar', 'boo'),2);
-- result[['foo', 'bar'], ['foo', 'boo']['bar', 'boo']]

4.2 應用場景舉例

4.2.1 分組對比統計

場景描述

現有投放策略轉化表,該表內的一條資料,表示一天內某投放策略帶來的訂單量。













-- SQL - 15with strategy_order as (    select stack(        3,        '2024-01-01', 'Strategy A', 10,        '2024-01-01', 'Strategy B', 20,        '2024-01-01', 'Strategy C', 30    )     -- 欄位:日期,投放策略,單量    as (dt, strategy, order_cnt))select * from strategy_order;
如何按投放策略建立兩兩對比組,按組對比展示不同策略轉化單量情況?

對比組

投放策略

轉化單量

Strategy A-Strategy B

Strategy A

xxx

Strategy A-Strategy B

Strategy B

xxx


解決思路

核心思路是從所有投放策略列表中不重複地取出 2 個策略,生成所有的組合結果,然後關聯 strategy_order 表分組統計結果。

SQL 實現






















-- SQL - 16select /*+ mapjoin(combs) */    combs.strategy_comb,    so.strategy,    so.order_cntfrom strategy_order sojoin ( -- 生成所有對比組    select         concat(least(val1, val2), '-', greatest(val1, val2)) as strategy_comb,        least(val1, val2) as strategy_1, greatest(val1, val2) as strategy_2    from (        select collect_set(strategy) as strategies        from strategy_order    ) dummy    lateral view explode(strategies) t1 as val1    lateral view explode(strategies) t2 as val2    where val1 <> val2    group by least(val1, val2), greatest(val1, val2)) combs on 1 = 1where so.strategy in (combs.strategy_1, combs.strategy_2)order by combs.strategy_comb, so.strategy;

對比組

投放策略

轉化單量

Strategy A-Strategy B

Strategy A

10

Strategy A-Strategy B

Strategy B

20

Strategy A-Strategy C

Strategy A

10

Strategy A-Strategy C

Strategy C

30

Strategy B-Strategy C

Strategy B

20

Strategy B-Strategy C

Strategy C

30

五、連續

本節主要介紹連續性問題,重點描述了常見連續活躍場景。對於靜態型別的連續活躍、動態型別的連續活躍,分別闡述了不同的實現方案。


5.1 普通連續活躍統計

場景描述

現有使用者訪問日誌表 visit_log ,每一行資料表示一條使用者訪問日誌。
















-- SQL - 17with visit_log as (    select stack (        6,        '2024-01-01', '101', '湖北', '武漢', 'Android',        '2024-01-01', '102', '湖南', '長沙', 'IOS',        '2024-01-01', '103', '四川', '成都', 'Windows',        '2024-01-02', '101', '湖北', '孝感', 'Mac',        '2024-01-02', '102', '湖南', '邵陽', 'Android',        '2024-01-03', '101', '湖北', '武漢', 'IOS'    )     -- 欄位:日期,使用者,省份,城市,裝置型別    as (dt, user_id, province, city, device_type))select * from visit_log;
如何獲取連續訪問大於或等於 2 天的使用者?

上述問題在分析連續性時,獲取連續性的結果以超過固定閾值為準,此處歸類為 連續活躍大於 N 天閾值的普通連續活躍場景統計

SQL 實現

基於相鄰日期差實現( lag / lead 版)

整體實現比較簡單。











-- SQL - 18select user_idfrom (    select         *,        lag(dt, 2 - 1) over (partition by user_id order by dt) as lag_dt    from (select dt, user_id from visit_log group by dt, user_id) t0) t1where datediff(dt, lag_dt) + 1 = 2group by user_id;

user_id

101

102

基於相鄰日期差實現(排序版)

整體實現比較簡單。










-- SQL - 19select user_idfrom (    select *,         dense_rank() over (partition by user_id order by dt) as dr    from visit_log) t1where datediff(dt, date_add(dt, 1 - dr)) + 1 = 2group by user_id;

user_id

101

102

基於連續活躍天數實現

可以視作 基於相鄰日期差實現(排序版) 的衍生版本,該實現能獲取到更多資訊,如連續活躍天數。



















-- SQL - 20select user_idfrom (    select         *,        -- 連續活躍天數        count(distinct dt)             over (partition by user_id, cont) as cont_days    from (        select             *,             date_add(dt, 1 - dense_rank()                 over (partition by user_id order by dt)) as cont        from visit_log    ) t1) t2where cont_days >= 2group by user_id;

user_id

101

102

基於連續活躍區間實現

可以視作 基於相鄰日期差實現(排序版) 的衍生版本,該實現能獲取到更多資訊,如連續活躍區間。



















-- SQL - 21select user_idfrom (    select         user_id, cont,         -- 連續活躍區間        min(dt) as cont_date_start, max(dt) as cont_date_end    from (        select             *,             date_add(dt, 1 - dense_rank()                 over (partition by user_id order by dt)) as cont        from visit_log    ) t1    group by user_id, cont) t2where datediff(cont_date_end, cont_date_start) + 1 >= 2group by user_id;

user_id

101

102

5.2 動態連續活躍統計

場景描述

現有使用者訪問日誌表 visit_log ,每一行資料表示一條使用者訪問日誌。
















-- SQL - 22with visit_log as (    select stack (        6,        '2024-01-01', '101', '湖北', '武漢', 'Android',        '2024-01-01', '102', '湖南', '長沙', 'IOS',        '2024-01-01', '103', '四川', '成都', 'Windows',        '2024-01-02', '101', '湖北', '孝感', 'Mac',        '2024-01-02', '102', '湖南', '邵陽', 'Android',        '2024-01-03', '101', '湖北', '武漢', 'IOS'    )     -- 欄位:日期,使用者,省份,城市,裝置型別    as (dt, user_id, province, city, device_type))select * from visit_log;
如何獲取最長的 2 個連續活躍使用者,輸出使用者、最長連續活躍天數、最長連續活躍日期區間?

上述問題在分析連續性時,獲取連續性的結果不是且無法與固定的閾值作比較,而是各自以最長連續活躍作為動態閾值,此處歸類為 動態連續活躍場景統計

SQL 實現

基於 普通連續活躍場景統計 的思路進行擴充套件即可,此處直接給出最終 SQL :


















-- SQL - 23select    user_id,     -- 最長連續活躍天數    datediff(max(dt), min(dt)) + 1 as cont_days,    -- 最長連續活躍日期區間    min(dt) as cont_date_start, max(dt) as cont_date_endfrom (    select         *,         date_add(dt, 1 - dense_rank()             over (partition by user_id order by dt)) as cont    from visit_log) t1group by user_id, contorder by cont_days desclimit 2;

user_id

cont_days

cont_date_start

cont_date_end

101

3

2024-01-01

2024-01-03

102

2

2024-01-01

2024-01-02

六、擴充套件

引申出更復雜的場景,是本篇文章前面章節內容的結合與變種。

6.1 區間連續(最長子區間切分)

場景描述

現有使用者掃描或連線 WiFi 記錄表 user_wifi_log ,每一行資料表示某時刻使用者掃描或連線 WiFi 的日誌。



















-- SQL - 24with user_wifi_log as (    select stack (        9,        '2024-01-01 10:01:00', '101', 'cmcc-Starbucks', 'scan', -- 掃描        '2024-01-01 10:02:00', '101', 'cmcc-Starbucks', 'scan',        '2024-01-01 10:03:00', '101', 'cmcc-Starbucks', 'scan',        '2024-01-01 10:04:00', '101', 'cmcc-Starbucks', 'conn', -- 連線        '2024-01-01 10:05:00', '101', 'cmcc-Starbucks', 'conn',        '2024-01-01 10:06:00', '101', 'cmcc-Starbucks', 'conn',        '2024-01-01 11:01:00', '101', 'cmcc-Starbucks', 'conn',        '2024-01-01 11:02:00', '101', 'cmcc-Starbucks', 'conn',        '2024-01-01 11:03:00', '101', 'cmcc-Starbucks', 'conn'    )     -- 欄位:時間,使用者,WiFi,狀態(掃描、連線)    as (time, user_id, wifi, status))select * from user_wifi_log;
現需要進行使用者行為分析,如何劃分使用者不同 WiFi 行為區間?滿足:
1)行為型別分為兩種:連線(scan)、掃描(conn);
2)行為區間的定義為:相同行為型別,且相鄰兩次行為的時間差不超過 30 分鐘;

3)不同行為區間在滿足定義的情況下應取到最長;

user_id

wifi

status

time_start

time_end

備註

101

cmcc-Starbucks

scan

2024-01-01 10:01:00

2024-01-01 10:03:00

使用者掃描了 WiFi

101

cmcc-Starbucks

conn

2024-01-01 10:04:00

2024-01-01 10:06:00

使用者連線了 WiFi

101

cmcc-Starbucks

conn

2024-01-01 11:01:00

2024-01-01 11:02:00

距離上次連線已經超過 30 分鐘,認為是一次新的連線行為


上述問題稍顯複雜,可視作
動態連續活躍統計 中介紹的 最長連續活躍 的變種。可以描述為 結合連續性閾值與行為序列中的上下文資訊,進行最長子區間的劃分 的問題

SQL 實現

核心邏輯:以使用者、WIFI 分組,結合連續性閾值與行為序列上下文資訊,劃分行為區間。

詳細步驟:

1)以使用者、WIFI 分組,在分組視窗內對資料按時間正序排序;
2)依次遍歷分組視窗內相鄰兩條記錄,若兩條記錄之間的時間差超過 30 分鐘,或者兩條記錄的行為狀態(掃描態、連線態)發生變更,則以該臨界點劃分行為區間。直到遍歷所有記錄;

3)最終輸出結果:使用者、WIFI、行為狀態(掃描態、連線態)、行為開始時間、行為結束時間;






















-- SQL - 25select     user_id,     wifi,    max(status) as status,    min(time) as start_time,     max(time) as end_timefrom (    select *,        max(if(lag_status is null or lag_time is null or status <> lag_status or datediff(time, lag_time, 'ss') > 60 * 30, rn, null))             over (partition by user_id, wifi order by time) as group_idx    from (        select *,            row_number() over (partition by user_id, wifi order by time) as rn,            lag(time, 1) over (partition by user_id, wifi order by time) as lag_time,            lag(status, 1) over (partition by user_id, wifi order by time) as lag_status        from user_wifi_log    ) t1) t2group by user_id, wifi, group_idx;

user_id

wifi

status

start_time

end_time

101

cmcc-Starbucks

scan

2024-01-01 10:01:00

2024-01-01 10:03:00

101

cmcc-Starbucks

conn

2024-01-01 10:04:00

2024-01-01 10:06:00

101

cmcc-Starbucks

conn

2024-01-01 11:01:00

2024-01-01 11:03:00


該案例中的連續性判別條件可以推廣到更多場景,例如基於日期差值、時間差值、列舉型別、距離差值等作為連續性判別條件的資料場景。

結語

透過靈活的、散發性的資料處理思維,就可以用基礎的語法,解決複雜的資料場景 是本篇文章貫穿全文的思想。文中針對數列生成、區間變換、排列組合、連續判別等常見的場景,給出了相對通用的解決方案,並結合例項進行了實際運用的講解。

本篇文章嘗試獨闢蹊徑,強調靈活的資料處理思維,希望能讓讀者覺得眼前一亮,更希望真的能給讀者產生幫助。同時畢竟個人能力有限,思路不一定是最優的,甚至可能出現錯誤,歡迎提出意見或建議。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70027826/viewspace-2991418/,如需轉載,請註明出處,否則將追究法律責任。

相關文章