介紹:如何充分利用 SQL 是本文的主題。 本文試圖找到一種獨特的方法,強調通過靈活和發散的資料處理思維,可以用最基本的語法解決複雜的資料場景。
一、引言
一、初衷
如何高效使用MaxCompute(ODPS)SQL,並充分利用SQL基本語法。
在如此火爆的大資料的今天,不僅專業的資料人員,還需要經常與SQL打交道,即使是非技術類的學員,如產品和運營人員都會或多或少地使用SQL,如何高效發揮SQL的能力,進而充分發揮資料的能力,就變得尤為重要。
MaxCompute(ODPS)SQL作為SQL方言,具有完整的SQL語法支援、豐富的內建函式,以及開窗函式、使用者自定義函式、使用者自定義型別等諸多高階特性,可以高效應用於各種資料處理場景。
如何充分利用 SQL 是本文的主題。 本文試圖找到一種新方法它強調通過靈活和發散的資料處理思維,可以用最基本的語法解決複雜的資料場景。
2.適合人群
本文可能對初學者和老手有所幫助,但更適合中級和高階讀者。
同時,為避免話題分歧,文章中涉及的功能和語法特點不會以特殊方式介紹,讀者可以自行理解。
3. 內容結構
在本文中,我們將介紹序列生成、區間變換、排列組合和連續判別等主題,並通過例項說明其實際應用。 每個主題之間有輕微的依賴關係,最好依次閱讀。
4. 提示資訊
本文涉及的SQL語句僅使用MaxCompute(ODPS)SQL的基本語法特性,理論上所有SQL語句都可以在當前最新版本下執行。
二、數字序列
序列是最常見的資料形式之一,大多數是實際資料開發場景中遇到的有限序列。 本節將從最簡單的遞增序列開始,找出一般方法,並推廣到更通用的方案。
1.常用號碼系列
1)簡單的公升序數字序列
首先,引入乙個簡單的整數遞增序列:
從值 0 開始;
每個後續值遞增 1;
到值 3 的末尾;
如何生成滿足上述三個條件的系列? 即 [0,1,2,3]。
事實上,有幾種方法可以生成這個序列,這裡有乙個簡單而通用的方法。
sql - 1
select
t.pos as a_n
from (
select posexplode(split(space(3), space(1), false))
t;
從上面的 SQL 程式碼片段中可以看出,生成遞增序列只需三個步驟:
生成乙個適當長度的陣列,陣列中的元素不需要有實際意義;
通過 UDTF 函式 posexplode 為陣列中的每個元素生成乙個索引下標;
取出每個元素的索引下標。 以上三個步驟可以推廣到更一般的序列場景:等差級數、等比例級數。 基於此,最終的實現模板將直接在下面給出。
2)等差級數
如果設定了第一項。 公差為:
那麼差分級數的一般公式是。
SQL 實現:
sql - 2
select
a + t.pos * d as a_n
from (
select posexplode(split(space(n - 1), space(1), false))
t;3) 比例系列
如果設定了第一項。 常見的比例是。
那麼比例級數的一般公式是。
SQL 實現:
sql - 3
select
a * pow(q, t.pos) as a_n
from (
select posexplode(split(space(n - 1), space(1), false))
t;提示:您也可以直接使用MaxCompute(ODPS)系統函式序列快速生成序列。
sql - 4
select sequence(1, 3, 1);
result
二、應用場景舉例
1) 恢復任意維度組合下的維度列集群名稱
在多維分析場景中,可以使用多維資料集、彙總、分組集等高階聚合函式來聚合不同維度組合下的資料統計資料。
場景描述
在現有使用者訪問日誌表中,每行資料代表乙個使用者訪問日誌。
sql - 5
with 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;
現在,針對省、市、裝置型別三個維度列,通過分組集聚合統計,獲取不同維度組合下的使用者訪問次數。 問:
如何知道統計資訊是從哪個維度列聚合的?
想要輸出聚合維度列的名稱如何應對下游報表展示等場景?
解決方案思路:
這可以借助MaxCompute提供的分組ID(ODPS)來解決,核心方法是反向實現分組ID。
具體步驟如下:
準備好所有分組 ID。
生成值的公升序序列,將每個值轉換為二進位字串,並展開二進位字串的每個位。
其中。 所有維度列的數目是所有維度組合的數目,每個數值表示乙個分組 ID。
準備好所有維度名稱。
生成乙個字串序列,依次儲存維度列的名稱,即。
dim_name_1, dim_name_2, .dim_name_n }
將分組 ID 對映到維度列名稱。
對於分組 id 遞增序列中的每個數值,將該值的每個位對映到維度名稱序列的下標,並輸出與位 0 對應的所有維度名稱。 例如:
grouping__id:3 =>
維度名稱序列:
對映:分組 ID 為 3 的行的聚合維度為:省份。
SQL 實現:
sql - 6
with 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))
組 -- 所有組。
lateral view posexplode(regexp_extract_all(lpad(conv(groups.pos,10,2), 3, "0"), '(0|1)'PE 作為佔位符 IDX,佔位位 -- 每個組的位資訊。
gbleft 聯接 ( 所有維度字段。
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_count
from (
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_id
order by group_dimension.dimension_name;
3. 間隔
區間與序列具有不同的資料特性,但在實際應用中,序列和區間的處理具有更多的共性。 本節將介紹一些常見的間隔方案,並抽象出常見的解決方案。
1. 常用間隔操作
1) 區間分割
數字間隔是已知的。
如何將此區間劃分為多個子段?
這個問題可以簡化為一系列問題,序列的公式是。
其中。
具體步驟如下:
生成乙個長度的陣列;
通過 UDTF 函式 posexplode 為陣列中的每個元素生成乙個索引下標;
取出每個元素的索引下標,計算序列公式,得到每個子區間的起始值和結束值。
SQL 實現:
sql - 7
select
a + t.pos * d as sub interval start, - 子間隔的起始值。
a + t.pos + 1) *d 作為子間隔結束 -- 子間隔的結束值。
from (
select posexplode(split(space(n - 1), space(1), false))
t;2) 間隔交叉
已知兩個日期間隔之間存在交叉 ['2024-01-01', '2024-01-03'] 、'2024-01-02', '2024-01-04']。問:
如何合併兩個日期間隔並返回到新的合併間隔?
如何知道哪些日期是交叉日期並返回該日期的交叉次數?
有很多方法可以解決這些問題,但這裡有乙個簡單而通用的解決方案。 其核心思想是將序列生成和區間分割的方法結合起來,首先將日期區間分解為最小的處理單元,即由多個日期組成的序列,然後根據日期粒度進行統計。 具體步驟如下:
獲取每個日期間隔中包含的天數;
根據日期間隔中包含的天數,將日期間隔拆分為相應數量的公升序日期序列;
合併的間隔和交叉次數通過日期序列進行計算。
SQL 實現:
sql - 8
with dummy_table as (
select stack(
as (date_start, date_end)
select
min(date item) 作為日期開始合併,max(date item) 作為日期結束合併,collect set( - 交叉日期計數。
case when date_item_cnt > 1 then concat(date_item, ':', date_item_cnt) else null end
as overlap_date
from (
select
拆卸後的單個日期。
date add(date start, pos) 作為日期項,- 拆解後單個日期的出現次數。
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;
讓它更難一點!
如果存在多個日期間隔,並且間隔之間的相交狀態未知,如何解決上述問題。 即:
如何合併多個日期間隔並在合併後返回多個新間隔?
如何知道哪些日期是交叉日期並返回該日期的交叉次數?
SQL 實現:
sql - 9
with dummy_table as (
select stack(
as (date_start, date_end)
select
min(date item) 作為日期開始合併,max(date item) 作為日期結束合併,collect set( - 交叉日期計數。
case when date_item_cnt > 1 then concat(date_item, ':', date_item_cnt) else null end
as overlap_date
from (
select
拆卸後的單個日期。
date add(date start, pos) 作為日期項,- 拆解後單個日期的出現次數。
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;
二、應用場景舉例
1) 任何時間段的統計資料
場景描述
現有使用者還款計畫,表中的一條資料表示使用者每天在指定的日期間隔內還款人民幣 [開始日期、結束日期]。
sql - 10
with user_repayment as (
select stack(
字段:使用者、開始日期、結束日期、每日還款金額。
as (user_id, date_start, date_end, repayment)
select * from user_repayment;
如何計算所有使用者在任何時間段(例如2024-01-15至2024-01-16)每天的總還款金額?
解決方案思路:
核心思想是將日期間隔轉換為日期序列,然後根據日期序列收集**。
SQL 實現:
sql - 11
select
date_item as day,sum(repayment) as total_repayment
from (
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_item
order by date_item;
四、排列組合
排列和組合是離散資料常用的資料組織方法,本節將介紹排列和組合的實現方法,並通過組合示例重點介紹資料的處理。
1. 常見的排列和組合操作
1) 安排
已知字元序列 ['a', 'b', 'c'] 一次從序列中重複選擇 2 個字元,如何獲得所有排列?
借助多個橫向檢視,整體實現相對簡單。
sql - 12
select
concat(val1, val2) as perm
from (select split('a,b,c', ',') as characters) dummy
lateral view explode(characters) t1 as val1
lateral view explode(characters) t2 as val2;
2) 組合
已知字元序列 ['a', 'b', 'c'] 一次重複序列中的 2 個字元,如何獲得所有組合?
借助多個橫向檢視,整體實現相對簡單。
sql - 13
select
concat(least(val1, val2), greatest(val1, val2)) as comb
from (select split('a,b,c', ',') as characters) dummy
lateral view explode(characters) t1 as val1
lateral view explode(characters) t2 as val2
group by least(val1, val2), greatest(val1, val2);
注意:您也可以使用MaxCompute(ODPS)系統功能組合快速生成組合。
sql - 14
select combinations(array('foo', 'bar', 'boo'),2);
result
foo', 'bar'], 'foo', 'boo']['bar', 'boo']]
二、應用場景舉例
1)組別對比統計
場景描述
現有的配送策略轉換表,其中包含一段資料,該資料表示配送策略在一天內生成的訂單數。
sql - 15
with 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;
如何根據發貨策略設定成對對比組,按組對比展示不同策略的轉化訂單量?
解決方案思路:
其核心思想是從所有無重複交割策略的列表中抽取2個策略,生成所有組合結果,然後關聯策略順序表對統計結果進行分組。
SQL 實現:
sql - 16
select /*+ mapjoin(combs) */
combs.strategy_comb,so.strategy,so.order_cnt
from strategy_order so
join ( 生成所有比較組。
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
dummylateral 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 = 1
where so.strategy in (combs.strategy_1, combs.strategy_2)
order by combs.strategy_comb, so.strategy;
5. 連續
本節重點介紹連續性問題,重點介紹常見的順序活動方案。 針對靜態型別和動態型別的連續活動,描述了不同的實現方案。
1. 普通連續活動統計
場景描述
在現有使用者訪問日誌表中,每行資料代表乙個使用者訪問日誌。
sql - 17
with 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 實現:
基於相鄰日期之間的差異(滯後領先版本)。
整體實現相對簡單。
sql - 18
select user_id
from (
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 = 2
group by user_id;
根據相鄰日期之間的差異實現(排序版本)。
整體實現相對簡單。
sql - 19
select user_id
from (
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 = 2
group by user_id;
根據連續的活動天數實施
可以看作是:根據相鄰日期之間的差異實現(排序版本)。,實現可以獲取更多資訊,例如連續活動天數。
sql - 20
select user_id
from (
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 >= 2
group by user_id;
基於連續活動間隔
可以看作是:根據相鄰日期之間的差異實現(排序版本)。,實現可以獲取更多資訊,例如連續活動間隔。
sql - 21
select user_id
from (
select
使用者 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
t1group by user_id, cont
t2where datediff(cont_date_end, cont_date_start) +1 >= 2
group by user_id;
2. 動態連續活動統計
場景描述
在現有使用者訪問日誌表中,每行資料代表乙個使用者訪問日誌。
sql - 22
with 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 - 23
select
user id, - 最大連續活動天數。
datediff(max(dt), min(dt)) 1 作為連續天數,- 最長的連續活動日期範圍。
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
t1group by user_id, cont
order by cont_days desc
limit 2;
6. 擴充套件
擴充套件到更複雜的方案是本文前幾章內容的組合和變體。
1.區間是連續的(將最長的子區間劃分)。
場景描述
現有使用者掃瞄或連線WiFi的使用者WiFi日誌,每行資料代表使用者在特定時間掃瞄或連線WiFi的日誌。
sql - 24
with 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行為區間? 滿意:
有兩種型別的行為:scan、conn;
行為間隔定義為:相同的行為型別,兩個相鄰行為之間的時間差不超過30分鐘;
如果滿足定義,則不同的行為間隔應該是最長的;
以上問題稍微複雜一些,可以看出如下:動態連續活動統計最長的連續活動變種。 可以描述為:結合連續性閾值和行為序列中的上下文資訊,劃分最長的子區間問題。
SQL 實現:
核心邏輯:按使用者和 wifi 分組劃分行為間隔,結合連續性閾值和行為序列上下文資訊。
詳細步驟:按使用者和wifi分組,在分組視窗中按時間順序對資料進行排序;
如果兩條記錄之間的時間差超過 30 分鐘,或者兩條記錄的行為狀態(掃瞄、連線)發生變化,則行為間隔除以臨界點。 直到遍歷所有記錄;
最終輸出結果:使用者、wifi、行為狀態(掃瞄狀態、連線狀態)、行為開始時間、行為結束時間;
sql - 25
select
user_id,wifi,max(status) as status,min(time) as start_time,max(time) as end_time
from (
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
這種情況下的連續性判別可以擴充套件到更多的場景,例如基於日期差、時差、列舉型別、距離差等的資料場景。
結論
通過靈活零星的資料處理思維,可以用基礎語法來解決複雜的資料場景,這是貫穿本文全文的思想。 本文針對序列生成、區間變換、排列組合、連續判別等常見場景給出了較為通用的解決方案,並結合算例對實際應用進行了說明。
本文試圖找到一種獨特的方式,強調靈活的資料處理思維,希望讓讀者感到光明,希望能真正幫助到讀者。 同時,畢竟個人能力有限,想法不一定是最優的,甚至可能會出現錯誤,歡迎提出意見或建議。
作者丨Rigaud.
*丨***阿里開發者(ID:Ali Tech)。