數據分析

SQL 面試題入門:數據分析師必會的 10 道基礎題 (上)

SQL 面試題入門上篇:數據分析師必會的十道基礎題

準備數據分析師面試時,SQL 幾乎是無法避開的一關,即使公司使用 Tableau、Power BI 或 Python進行視覺化呈現,分析師仍需要透過 SQL 從資料庫提取視覺化前需要的底層資料。

SQL 面試的重點不只是在測試語法,面試官更想透過面試,知道你是否能理解資料內容、將商業問題轉換成查詢條件、避免重複計算,並正確處理商業問題,這篇文章就來與你分享 10 道數據分析師的 SQL 基礎面試題。

練習使用的資料表

一開始,我們先假設兩張簡化的電商資料表:

一張資料表叫做customers 包含客戶資料,包括 customer_id、signup_date、country、acquisition_channel

另外一張表則為訂單紀錄表orders, 包含所有訂單紀錄,資料欄位有: order_id、customer_id、order_date、order_status、total_amount。

第 1 題:如何每個月的訂單數與營收?

這時候你是不是會想要急著開始寫你的SQL呢?

SELECT
    DATE_TRUNC('month', order_date) AS order_month,
    COUNT(DISTINCT order_id) AS order_count,
    SUM(total_amount) AS revenue
FROM orders
GROUP BY 1
ORDER BY 1;

在還沒確認商業問題之前,貿然寫出你的Query答案,可能會讓你有點扣分,解題之前,其實最重要的就是確認商業問題以及資料格式,所以先別急著寫下你的query,如果資訊只有上面提供的資料表以及資料欄位,其實並不足以讓你判斷你的Query語法應該怎麼下。

下手之前,你可以問問,例如:

1. order_date的資料格式是什麼?是日期格式嗎?還是string(字串), int(整數) 或者是timestamp格式呢?基於資料表的大小以及儲存限制,不同公司不同資料表可能會針對日期做不同的儲存格式,所以確認資料欄位的格式,絕對是第一步。

2. 每月的訂單數與營收是指完成的訂單數嗎?資料表目前的資料只包含完成的訂單嗎?如果是先完成的訂單,但後續有退貨或是換貨,該訂單的狀態會是退貨嗎?部分退貨部分取貨,那資料欄位又會怎麼顯示呢?

3. revenue是否代表實際客戶付款的金額呢?還是折扣前金額呢?如果是折扣前金額,是否需要扣除折扣金額或是運費呢?

發現了嗎?光是詢問這些問題,就可能讓你的query的處理方式完全不一樣,所以,開始回答前,請先想一下實際的商業案例可能會有哪些情況,並試想可能的情況,才會讓你的能力不僅僅停留在sql的寫手,而是專業的商業問題解析員

問完這些問題,你可能會得到:

日期的格式為整數,需要計算已完成的訂單,已完成的訂單可以以order_status = ‘completed’來判斷,先不考慮退貨,revenue是指最終營收,所以其他折扣與運費不避考慮。

SELECT
    DATE_TRUNC('month', DATE_PARSE(CAST(order_date AS VARCHAR), '%Y%m%d')) AS order_month,
    COUNT(DISTINCT order_id) AS order_count,
    SUM(total_amount) AS revenue
FROM orders
WHERE order_status = 'completed'
GROUP BY 1
ORDER BY 1;

結果是不是就跟一開始還沒釐清商業問題完全不一樣了呢?

第 2 題:如何找出消費金額最高的 10 位顧客?

看到「消費金額最高」這幾個字,你是不是已經準備把每位顧客的 total_amount 加總,再由高到低排序了呢?

SELECT
    customer_id,
    SUM(total_amount) AS total_spend
FROM orders
GROUP BY customer_id
ORDER BY total_spend DESC
LIMIT 10;

先別急著交出這個答案。這段 SQL 在語法上可能沒有問題,但「消費金額」在商業情境中,還有很多需要確認的細節。

下手之前,你可以先問:

1. 消費金額是所有訂單的金額,還是只計算已完成的訂單?取消、付款失敗與退款訂單需要排除嗎?

2. total_amount 代表折扣前金額,還是顧客實際支付的金額?裡面是否包含運費、退款金額?

3. 資料中是否有不同幣別?如果有人使用美元消費、有人使用台幣消費,可以直接把金額放在一起比較嗎?

發現了嗎?只是要找出消費金額最高的顧客,就會牽涉訂單狀態、金額定義、幣別。技術上能夠加總,不代表這些數字在商業上真的可以直接相比。

假設面試官進一步告訴你:資料只有單一幣別、total_amount 已經是最終付款金額,而且只需要計算 order_status = ‘completed’ 的訂單,那麼 Query 就可以寫成:

SELECT
    customer_id,
    SUM(total_amount) AS total_spend
FROM orders
WHERE order_status = 'completed'
GROUP BY customer_id
ORDER BY total_spend DESC
LIMIT 10;

這題真正想測試的,不只是你會不會使用 SUM、GROUP BY、ORDER BY 和 LIMIT,而是你是否知道「消費金額最高」背後還藏著哪些商業定義。

第 3 題:如何計算每個國家的購買顧客數?

這一題需要使用 customers 與 orders 兩張資料表。看到 customer_id 同時出現在兩張表裡,你可能會很快想到用 JOIN 把它們連起來。

SELECT
    c.country,
    COUNT(DISTINCT c.customer_id) AS customer_count
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.country
ORDER BY customer_count DESC;

不過,在正式回答之前,還是要先確認題目裡的「國家」和「購買顧客」分別代表什麼。

你可以先問:

1. country 是會員註冊時填寫的國家、目前居住國家,還是訂單的配送國家?如果顧客購買送給住在不同居住地的收件者,應該歸在哪個國家?

2. 「購買顧客」是曾經建立訂單就算?還是至少要有一筆已完成訂單?如果是要有一筆完成訂單,是多久內完成訂單的客戶才要算?

3. 一位顧客可能有很多筆訂單,JOIN 之後會出現很多列。這時候應該計算訂單筆數,還是不重複的顧客人數?

第 3 個問題非常重要。如果直接使用 COUNT(*),一位下過 10 次訂單的顧客就會被計算 10 次,但題目問的是顧客數,因此需要使用 COUNT(DISTINCT customer_id) 避免重複計算。

假設 country 代表顧客註冊的國家,而購買顧客的定義是2025(2025-01-01~2025-12-31)至少完成過一筆訂單,那麼 SQL 可以寫成:

SELECT
    c.country,
    COUNT(DISTINCT c.customer_id) AS customer_count
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id and 
WHERE o.order_status = 'completed' and DATE(DATE_PARSE(CAST(order_date AS VARCHAR), '%Y%m%d')) between DATE('2025-01-01') AND DATE('2025-12-31') 
GROUP BY c.country
ORDER BY customer_count DESC;

如果題目改成「每個國家的註冊顧客數」,那就不一定需要 JOIN orders。光是「註冊顧客」與「購買顧客」兩個字的差別,就會直接改變你需要使用的資料表與篩選條件囉~

第 4 題:如何找出從未下單的會員?

這是一道很常見的 JOIN 題目。你可能知道要使用 LEFT JOIN,但真正容易出錯的地方,反而是「從未下單」到底是什麼意思。

開始寫 Query 前,可以先確認:

1. 從未下單是指完全沒有建立過任何訂單,還是沒有完成過訂單?

2. 如果會員曾經下單,但後來取消、付款失敗或全額退款,他還算是下過單嗎?

3. orders 資料表是否包含測試訂單?測試帳號與內部員工帳號是否需要排除?

假設面試官說,這裡的「從未下單」就是在 orders 資料表中完全找不到任何訂單紀錄,那麼可以使用 LEFT JOIN:

SELECT
    c.customer_id,
    c.signup_date,
    c.country
FROM customers c
LEFT JOIN orders o
    ON c.customer_id = o.customer_id
WHERE o.order_id IS NULL;

為什麼不能使用 INNER JOIN 呢?因為 INNER JOIN 只會保留兩張表都有出現的 customer_id,沒有訂單的會員反而會直接被排除。LEFT JOIN 則會先保留 customers 中的所有會員,再讓找不到訂單的資料呈現 NULL,因此可以用 o.order_id IS NULL 把他們找出來。

但如果題目真正想找的是「從未完成訂單的會員」,就不能只是把 order_status = ‘completed’ 寫在最後的 WHERE 條件中,否則很容易改變 LEFT JOIN 的結果。這時候,可以把完成訂單的條件放進 JOIN:

SELECT
    c.customer_id,
    c.signup_date,
    c.country
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id AND o.order_status = 'completed'
WHERE o.order_id IS NULL;

發現了嗎?「沒有下單」和「沒有完成訂單」看起來很接近,實際上卻是兩個不同的客群。面試時先說清楚你的定義,會比直接背出 LEFT JOIN 更加分。

第 5 題:如何計算不同訂單狀態的占比?

如果題目要你計算 completed、cancelled、refunded 等不同訂單狀態的占比,你可能會先想到依照 order_status 分組,再計算每一組的訂單數。

SELECT
    order_status,
    COUNT(*) AS order_count
FROM orders
GROUP BY order_status;

但是只有各狀態的訂單數,還不能算出占比。正式往下寫之前,你可以先確認:

1. 一筆資料列就代表一張訂單嗎?還是同一張訂單會因為包含多個商品而出現多列?如果是一張訂單多列,就不能直接使用 COUNT(*)。

2. 訂單狀態是目前的最終狀態,還是每次狀態變更都會留下紀錄?如果是狀態歷程表,同一張訂單可能會被重複計算。

3. 占比的分母是所有訂單,還是只計算某段期間、特定市場或特定渠道的訂單?

4. order_status 如果是 NULL 或出現未知狀態,要獨立列出、歸類為 other,還是排除?

假設 orders 中的一列就是一張訂單、order_status 是訂單目前的最終狀態,而且分母包含資料表中的所有訂單,那麼可以搭配 Window Function 計算占比:

WITH status_count AS (
    SELECT
        order_status,
        COUNT(*) AS order_count
    FROM orders
    GROUP BY order_status
),

total_count AS (
    SELECT
        COUNT(*) AS total_order_count
    FROM orders
)

SELECT
    s.order_status,
    s.order_count,
    ROUND(
        100.0 * s.order_count / t.total_order_count,
        2
    ) AS order_percentage
FROM status_count s
CROSS JOIN total_count t
ORDER BY s.order_count DESC;

在這段 Query 中,我們先透過 status_count 計算每個 order_status 分別有多少筆訂單,再透過 total_count 計算全部訂單的總數。

最後,將每種狀態的訂單數除以全部訂單數,再乘以 100,就可以得到各訂單狀態所占的百分比。

Query 寫完後也別急著結束。你還可以檢查各狀態的百分比加總是否接近 100%,並觀察 cancelled 或 refunded 的比例是否異常。這樣回答,就不只是算出結果,也展現了你會檢查資料品質與商業合理性。

總結:SQL 面試不是只看你會不會寫語法

看完前面五道題目,你可能會發現,這些 SQL 語法其實都不算太複雜,使用的主要是 GROUP BY、JOIN、SUM、COUNT、ORDER BY。

真正困難的地方,反而是在開始寫 Query 之前,你能不能先把問題問清楚。

日期欄位是 date、timestamp、string,還是整數?營收要不要扣除退款與折扣?購買顧客是曾經建立訂單,還是至少完成過一筆訂單?從未下單,是完全沒有訂單紀錄,還是沒有成功完成的訂單?計算訂單狀態占比時,一列資料又是否真的代表一張訂單?

這些問題看起來都不是 SQL 語法,卻會直接決定你的 SQL 應該怎麼寫。如果商業定義理解錯誤,就算 Query 能夠成功執行,最後得到的答案也不一定是公司真正想知道的結果,所以啊,下次遇到 SQL 面試題時,先別急著埋頭寫答案。你可以按照下面的順序思考:

  1. 確認題目真正想解決的商業問題。
  2. 確認資料表、欄位格式與每一列資料代表的意義。
  3. 說明你對指標與篩選條件的假設。
  4. 拆解查詢步驟,再開始撰寫 SQL。
  5. 寫完後檢查重複資料、NULL、取消、退款與其他例外情況。
  6. 最後確認查詢結果在商業上是否合理。

發現了嗎?面試官想找的通常不是一位只會背語法的 SQL 寫手,而是一位能理解資料、釐清問題,並且把商業需求轉換成正確查詢邏輯的數據分析師。

這篇先和你分享前五道 SQL 基礎面試題。下一篇,我會繼續整理第 6~10 題,帶你練習日期處理、HAVING、CASE WHEN,以及更多數據分析師面試中常見的應用情境。

想繼續練習更多電商 SQL 面試題嗎?

如果你已經完成上面的題目,接下來可以試著挑戰更多貼近實際工作情境的 SQL 題目。

我整理了一份《電商產業 SQL 面試實戰題庫》,收錄 24 道電商商業案例,涵蓋付款失敗率、取消與退款、實際營收、新舊客、回購率、商品表現、MoM、QoQ、優惠券成效與購物轉換漏斗。

每一道題目都會提供資料表與欄位,讓你先練習釐清商業問題、拆解查詢步驟,再對照 Athena SQL 與 MySQL 8.0 的參考答案,這裏購買:https://buymeacoffee.com/aderlabsharon/e/572913

最後也提供讀者專屬折扣碼:BLOG,結帳時輸入折扣碼即可享有9折優惠~


Buy me a coffee 用行動支持我的內容創作

如果我的文章對你有幫助,歡迎用小額贊助支持內容創作,也歡迎留言分享你的 SQL 面試經驗。

Tagged , ,

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *