Skip to content
All writing Part 09 of 09 · [object Object]
Engineering · 3 min read

跨引擎查詢練習(附解答)

驗收自己:先判斷這是哪一種 operation,再想這個引擎怎麼寫。ClickHouse、Spark、MongoDB 實作練習附解答——數 NULL、array 成員判斷、分群等等。

驗收自己:讀完情境,先想「這是哪一種 operation」,再想「這個引擎怎麼寫」,最後才展開對答案。範例 schema 以 analytics.events 為主,欄位視情境而定。

ClickHouse

1. 只算有填 email 的 sign-up(排除 NULL)

情境: eventssignup_email 欄位,部分為 NULL。同時算出總列數與有 email 的列數。

顯示解答
SELECT count(*) AS total, count(signup_email) AS with_email
FROM analytics.events;

count(*) 含 NULL、count(col) 只算非 NULL;兩者的差就是缺 email 的數量。↳ count(*) vs count(欄位)

2. 找出活動名稱含 “summer”(不分大小寫)

情境:field_name 裡搜尋子字串 summer

顯示解答
SELECT * FROM analytics.events
WHERE field_name ILIKE '%summer%';

ILIKE = 不分大小寫的 LIKE% 代表任意字元。↳ LIKE / ILIKE 子字串搜尋

3. 篩出 tags 陣列含 ‘vip’ 的列

情境: tagsArray(String),要判斷有 vip 成員在 tags 裡面的資料集。

顯示解答
SELECT * FROM analytics.events
WHERE has(tags, 'vip');

has(arr, val) 就是 ClickHouse 的 array 成員判斷。↳ ClickHouse 陣列函式

4. 找出「至少一個 latency 超過 500ms」的請求

情境: latencies 是數值 array,只要有一個 > 500 就要。

顯示解答
SELECT request_id FROM analytics.events
WHERE arrayExists(x -> x > 500, latencies);

arrayExists 等於 Python 的 any()。↳ ClickHouse 陣列函式

5. 依消費與活躍度分群

情境: 用多層條件把使用者分成不同 segment。

過濾欄位:total_spent and days_active

  1. VIP_Active: total_spent >= 5000 & days_active <= 14
  2. VIP_At_Risk: total_spent >= 5000
  3. Loyal_Regular: total_spent >= 1000
  4. 其他皆列為 Standard 即可
顯示解答
SELECT user_id,
    multiIf(
        total_spent >= 5000 AND days_active <= 14, 'VIP_Active',
        total_spent >= 5000, 'VIP_At_Risk',
        total_spent >= 1000, 'Loyal_Regular',
        'Standard'
    ) AS segment
FROM analytics.events;

multiIf 是精簡版的 CASE WHEN。↳ ClickHouse 陣列函式

6. Email 為 Null 時,填入 unknown

情境: signup_email 部分為 NULL,輸出時把 NULL 換成字串 unknown

顯示解答
SELECT ifNull(signup_email, 'unknown') AS email
FROM analytics.events;

ClickHouse 用 ifNull 給 null 預設值(Spark 是 NVL)。↳ ClickHouse 陣列函式

Spark

1. 每個 customer 的商品清單(含重複 vs 去重)

情境: 把多列 customer_iditem_bought field 聚成 array → 一欄可以有重複、另一欄不能有重複。所以會有三欄: id, all_items, unique_items

顯示解答
SELECT customer_id,
    collect_list(item_bought) AS all_items,
    collect_set(item_bought)  AS unique_items
FROM store_sales
GROUP BY customer_id;

collect_list 保留重複、collect_set 去重。↳ Spark 陣列函式與 collect

2. 篩出 tags 含 ‘vip’ 的列

顯示解答
SELECT * FROM events
WHERE array_contains(tags, 'vip');

Spark 的 array 成員判斷是 array_contains。↳ Spark 陣列函式與 collect

3. 把本地價乘上匯率

情境: prices 是數值 array,逐一乘上 rate

顯示解答
SELECT transform(prices, x -> x * rate) AS usd_prices
FROM orders;

transform(arr, x -> ...) 對每個元素套運算式。↳ Spark 陣列函式與 collect

4. 只留下超過 500ms 的 latency

顯示解答
SELECT filter(latencies, x -> x > 500) AS slow
FROM events;

WHERE 篩的是「整列」,filter 篩的是「同一列 array 內的元素」,層級不同:想留下「有任一 latency > 500 的列」用 WHERE exists(latencies, x -> x > 500)(但整個 array 原封不動);想把 array 裡 ≤ 500 的元素砍掉,只能用 filter

filter(arr, x -> ...) 保留符合 predicate 的元素。↳ Spark 陣列函式與 collect

5. email 為 null 時填 ‘unknown’

顯示解答
SELECT NVL(signup_email, 'unknown') AS email FROM events;

Spark 用 NVL 給 null 預設值(ClickHouse 是 ifNull)。↳ Spark 陣列函式與 collect

MongoDB

1. 年齡介於 20 到 40(不含端點)

顯示解答
db.users.find({ age: { $gt: 20, $lt: 40 } });

同一欄位可疊多個比較 operator。↳ MongoDB 運算子與詞彙

2. name 含 “abc”(不分大小寫)

顯示解答
db.users.find({ name: { $regex: "abc", $options: "i" } });

$regex ≈ SQL LIKE$options: "i"ILIKE。↳ MongoDB 運算子與詞彙

3. 有填 email 的 document

顯示解答
db.users.find({ email: { $exists: true } });

Mongo 裡 Absent ≠ null:$exists 問的是「有沒有這個 key」。↳ MongoDB 運算子與詞彙

4. type 是 a / b / c 之一 — 等於 IN

顯示解答
db.users.find({ type: { $in: ["a", "b", "c"] } });

$in ≈ SQL IN。↳ MongoDB 運算子與詞彙

5. 未成年 或 VIP

顯示解答
db.users.find({ $or: [{ age: { $lt: 18 } }, { vip: true }] });

$or / $and / $nor 吃一個 array。↳ MongoDB 運算子與詞彙


參考來源

Related: 回到跨引擎資料庫查詢總覽——這些答案背後的深入篇:count(*) vs count(欄位)LIKE / ILIKEClickHouse 陣列函式Spark 陣列函式MongoDB 運算子

Tags #query-engines #sql #databases
// connect

Be brave | Be wise | Be grateful

21 BreakinCode

// elsewhere
LinkedInMedium (lang: en)Youtube
wh:~$William Hung· © 2026 Taipei · GMT+8 · Available for collaboration