七個(gè)值得復(fù)用的 DuckDB SQL 模式
實(shí)用、快速、可復(fù)制的DuckDB技巧,讓你的筆記本電腦變身小型OLAP引擎——無(wú)需離開Python環(huán)境。
七大DuckDB SQL模式:直接查詢文件、窗口函數(shù)去重、數(shù)據(jù)透視/逆透視、JSON處理、Parquet導(dǎo)出、與Pandas/Polars交互。
并非每個(gè)分析任務(wù)都需要數(shù)據(jù)倉(cāng)庫(kù)。 有時(shí)候,你只需要立刻得到結(jié)果——無(wú)需支付平臺(tái)費(fèi)用。
DuckDB正是為此而生。以下是我在日常Python工作中持續(xù)復(fù)用的七個(gè)SQL模式。它們簡(jiǎn)潔、高效,幾乎無(wú)需任何配置即可融入你的分析流程。
核心工作流
將你的分析路徑想象為:文件 → DuckDB SQL → 小型結(jié)果集 → Python處理。你可以直接用SQL查詢Parquet/CSV/JSON文件,盡早過(guò)濾和聚合,最后才將精簡(jiǎn)的結(jié)果集導(dǎo)入DataFrame。這樣既能保證筆記本電腦保持流暢運(yùn)行,又能快速獲得分析結(jié)果。
模式1
-- 像查詢表一樣查詢文件(支持下推)
直接用SQL查詢本地文件,讓DuckDB在Python接觸數(shù)據(jù)前完成列投影和行過(guò)濾
-- 單行命令查詢文件:列投影+謂詞下推
SELECT user_id, SUM(amount) AS total_spend
FROM read_parquet('data/transactions/*.parquet')
WHERE tx_date BETWEENDATE'2025-01-01'ANDDATE'2025-03-31'
AND country = 'CN'
GROUPBY user_id
ORDERBY total_spend DESC
LIMIT20;核心價(jià)值:read_parquet + WHERE 讓DuckDB僅讀取必要的行組和列。這是在你本地磁盤上實(shí)現(xiàn)的數(shù)據(jù)倉(cāng)庫(kù)級(jí)行為。同樣的技巧也適用于read_csv_auto()和read_json_auto()。
Python銜接(僅返回精簡(jiǎn)結(jié)果):
import duckdb
import pandas as pd
q = """
SELECT user_id, SUM(amount) AS total_spend
FROM read_parquet('data/transactions/*.parquet')
WHERE tx_date >= DATE '2025-01-01' AND tx_date < DATE '2025-04-01'
GROUP BY user_id ORDER BY total_spend DESC LIMIT 20
"""
df = duckdb.query(q).to_df() # 精簡(jiǎn)、整潔、可直接繪圖的數(shù)據(jù)模式2
-- 將分區(qū)文件夾視為表(HIVE分區(qū))
自動(dòng)將目錄名稱(如country=CN/yyyymm=202501/)轉(zhuǎn)換為列
-- 目錄結(jié)構(gòu): data/country=CN/yyyymm=202501/part-*.parquet
SELECT country, yyyymm, COUNT(*) AS n, SUM(amount) AS total_amount
FROM read_parquet('data/country=*/yyyymm=*/part-*.parquet', hive_partitioning=1)
WHERE yyyymm BETWEEN '202501' AND '202503'
GROUP BY country, yyyymm
ORDER BY yyyymm, country;核心價(jià)值: 無(wú)需元數(shù)據(jù)存儲(chǔ)即可實(shí)現(xiàn)快速、整潔的分析。特別適用于事件日志或上游工具導(dǎo)出的即席匯總。
模式3
-- 按主鍵保留最新記錄(QUALIFY技巧)
無(wú)需嵌套子查詢即可獲取每個(gè)實(shí)體的最新記錄
-- 根據(jù)updated_at字段保留每個(gè)user_id的最新檔案
WITHprofilesAS (
SELECT *
FROM read_parquet('data/user_profiles/*.parquet')
)
SELECT *
FROMprofiles
QUALIFY ROW_NUMBER() OVER (
PARTITIONBY user_id ORDERBY updated_at DESC
) = 1;核心價(jià)值:QUALIFY讓你能直接基于窗口函數(shù)結(jié)果進(jìn)行過(guò)濾。比在子查詢中包裝窗口函數(shù)更簡(jiǎn)潔。特別適用于CDC文件、增量數(shù)據(jù)轉(zhuǎn)儲(chǔ)和混亂的數(shù)據(jù)導(dǎo)出。
模式4
-- 真正適合內(nèi)存的滾動(dòng)指標(biāo)計(jì)算
在SQL中完成時(shí)間序列計(jì)算,而非Python循環(huán)
-- 7日滾動(dòng)營(yíng)收和周同比變化
WITH s AS (
SELECT tx_date::DATEAS d, SUM(amount) AS daily_rev
FROM read_parquet('data/transactions/*.parquet')
GROUPBY1
)
SELECT
d,
daily_rev,
SUM(daily_rev) OVER (
ORDERBY d
RANGEBETWEENINTERVAL6DAYPRECEDINGANDCURRENTROW
) AS rev_7d,
(daily_rev - LAG(daily_rev, 7) OVER (ORDERBY d)) AS week_delta
FROM s
ORDERBY d;核心價(jià)值: 讓Python專注于可視化,而非繁重的計(jì)算任務(wù)。窗口函數(shù)在你的機(jī)器上以流式處理,內(nèi)存占用極低。
模式5
-- 輕松實(shí)現(xiàn)數(shù)據(jù)透視/逆透視
單一語(yǔ)句完成指標(biāo)儀表板所需的數(shù)據(jù)重塑
-- 將長(zhǎng)格式轉(zhuǎn)換為寬格式(分類作為列)
WITH daily AS (
SELECT
DATE_TRUNC('day', ts) AS d,
category,
COUNT(*) ASevents
FROM read_parquet('data/events/*.parquet')
GROUPBY1,2
)
PIVOT daily
ONcategory
USINGSUM(events)
GROUPBY d
ORDERBY d;
-- 反向操作:寬格式轉(zhuǎn)長(zhǎng)格式,便于整潔繪圖
UNPIVOT read_parquet('data/agg/daily_by_category.parquet')
ON COLUMNS(* EXCLUDE d)
INTO NAME category VALUE events;核心價(jià)值: 你將不再需要手動(dòng)拼接連接或編寫脆弱的Pandas重塑代碼;所需的數(shù)據(jù)形狀僅需一條SQL語(yǔ)句。
模式6
--JSON和列表處理:展開、整理、重建
許多日志以半結(jié)構(gòu)化字段形式出現(xiàn),DuckDB讓它們重新變得規(guī)整
-- 展開訂單行中的JSON商品數(shù)組
WITH orders AS (
SELECT *
FROM read_json_auto('data/orders_2025.json') -- 每行包含items[]
),
items AS (
SELECT
o.order_id,
i->>'sku' AS sku,
CAST(i->>'qty'ASINTEGER) AS qty,
CAST(i->>'price'ASDOUBLE) AS price
FROM orders o, UNNEST(o.items) AS t(i)
)
SELECT sku, SUM(qty) AS units, SUM(qty*price) AS revenue
FROM items
GROUPBY sku
ORDERBY revenue DESC;
-- 在需要時(shí)重新構(gòu)建整潔的JSON
SELECT order_id,
to_json( struct_pack(
total_items := SUM(qty),
total_price := SUM(qty*price)
)) AS order_summary_json
FROM items
GROUPBY order_id;核心價(jià)值:UNNEST將嵌套數(shù)組轉(zhuǎn)換為可聚合的行;struct_pack/to_json為API或下游工具提供清晰、輕量的輸出。
模式7
-- 將清晰數(shù)據(jù)切片導(dǎo)出至Parquet(便于交接)
經(jīng)過(guò)深度過(guò)濾和聚合后,持久化一個(gè)精簡(jiǎn)的分析結(jié)果
-- 為團(tuán)隊(duì)成員和未來(lái)的你保存一個(gè)"黃金"數(shù)據(jù)切片
COPY (
SELECT user_id,
SUM(amount) AS total_spend,
COUNT(*) AS tx_count
FROM read_parquet('data/transactions/*.parquet')
WHERE tx_date >= DATE'2025-01-01'
GROUPBY user_id
) TO'out/spend_2025_q1.parquet'
(FORMAT PARQUET, COMPRESSION ZSTD, ROW_GROUP_SIZE 128000);核心價(jià)值: 單個(gè)壓縮的Parquet文件非常適合共享或后續(xù)即時(shí)重新加載。避免在每個(gè)筆記本中重復(fù)進(jìn)行全局計(jì)算。
附加技巧
--直接查詢Pandas/Polars數(shù)據(jù)
讓SQL處理繁重的掃描任務(wù);Python僅負(fù)責(zé)小型連接或繪圖
import duckdb
import pandas as pd
users = pd.read_csv("data/users.csv") # 小型維度表
q = """
SELECT u.user_id, u.segment, t.total_spend
FROM users AS u
JOIN (
SELECT user_id, SUM(amount) AS total_spend
FROM read_parquet('data/transactions/*.parquet')
GROUP BY 1
) AS t
USING (user_id)
ORDER BY total_spend DESC
LIMIT 50
"""
df = duckdb.query(q).to_df() # 數(shù)據(jù)分析師的理想工作流核心價(jià)值: DuckDB能夠以零拷貝的方式將DataFrame讀取為表(如上文的"users"),因此你可以無(wú)縫地將SQL掃描與Python原生維度表結(jié)合使用。
實(shí)用建議(團(tuán)隊(duì)易忽略的細(xì)節(jié))
- 優(yōu)選高效格式:對(duì)于重復(fù)讀取,Parquet > CSV。使用COPY (SELECT …) TO 'x.parquet'一次性完成轉(zhuǎn)換。
- 盡早過(guò)濾,延遲提取:向Python返回小型結(jié)果集。僅帶回你需要繪圖的數(shù)據(jù)。
- 保持模式穩(wěn)定:在讀取混亂的JSON時(shí),顯式轉(zhuǎn)換類型(CAST(… AS DOUBLE)),然后持久化清晰的Parquet切片。
- 確保確定性排序:在LIMIT之前始終使用ORDER BY,以保證可復(fù)現(xiàn)的Top-N列表。
- 構(gòu)建可重復(fù)的筆記本:將SQL封裝到小型Python函數(shù)中,使得重新運(yùn)行僅需一次按鍵,而非繁瑣的查找。
微型案例研究(真實(shí)場(chǎng)景體驗(yàn))
某增長(zhǎng)團(tuán)隊(duì)在分析購(gòu)買漏斗時(shí),面對(duì)大量CSV轉(zhuǎn)儲(chǔ)文件。Pandas處理緩慢,連接操作耗時(shí)數(shù)分鐘,有時(shí)甚至更長(zhǎng)。他們轉(zhuǎn)而采用模式1和模式3:
- 直接查詢Parquet文件(他們一次性將CSV轉(zhuǎn)換為Parquet)
- 通過(guò)QUALIFY去重至最新的客戶狀態(tài)
成果: 在MacBook上,端到端的漏斗表在約5秒內(nèi)生成,而非在云環(huán)境中耗時(shí)數(shù)分鐘。圖表快速更新,團(tuán)隊(duì)因即時(shí)反饋循環(huán)而迭代速度提升了一倍。無(wú)需數(shù)據(jù)倉(cāng)庫(kù)工單,無(wú)需Airflow作業(yè),無(wú)需等待。
總結(jié)
現(xiàn)實(shí)而言:能夠立即運(yùn)行的分析才是最快的分析。DuckDB的優(yōu)勢(shì)在于極致的實(shí)用性——謂詞下推、整潔的數(shù)據(jù)重塑、輕松易用的窗口函數(shù),以及與Python可視化或建模的流暢銜接。
復(fù)用這些模式。根據(jù)你的數(shù)據(jù)靈活調(diào)整。如果其中某個(gè)模式為你的工作流節(jié)省了寶貴時(shí)間,請(qǐng)告訴我——然后關(guān)注更多能夠在筆記本電腦上發(fā)揮超出預(yù)期效果的實(shí)用技巧。

































