dbt ในปี 2026: การแปลงข้อมูล การทดสอบ และคำถามสัมภาษณ์งาน
คู่มือ dbt สำหรับวิศวกรข้อมูล: การแปลง SQL, การสร้างโมเดลแบบแบ่งชั้น, กลยุทธ์ incremental, การทดสอบคุณภาพข้อมูล และคำถามสัมภาษณ์พร้อมตัวอย่างโค้ดสำหรับปี 2026

dbt (data build tool) ได้กลายเป็น framework มาตรฐานสำหรับการแปลงข้อมูลภายใน data warehouse สมัยใหม่ โดยมีบริษัทมากกว่า 40,000 แห่งใช้งานในสภาพแวดล้อม production ณ ปี 2026 บทความนี้ครอบคลุมกลไกหลักของการแปลงข้อมูลด้วย dbt กลยุทธ์การทดสอบ และคำถามที่พบบ่อยในการสัมภาษณ์งานตำแหน่ง data engineering
dbt จัดการตัว T ใน ELT โดยคอมไพล์คำสั่ง SQL SELECT ให้เป็น DDL (CREATE TABLE, CREATE VIEW, MERGE) และรันคำสั่งเหล่านั้นบน warehouse — Snowflake, BigQuery, Redshift หรือ Databricks ระบบ version control การแก้ไข dependency การทดสอบ และเอกสารประกอบทั้งหมดมาพร้อมในตัว
การสร้างโมเดลแบบแบ่งชั้น: Staging, Intermediate และ Marts
โปรเจกต์ dbt ที่มีโครงสร้างดีจะแบ่งการแปลงข้อมูลออกเป็นสามชั้น แนวทางนี้ได้รับความนิยมจากชุมชน dbt ซึ่งบังคับใช้หลักการ single-responsibility กับแต่ละโมเดล และทำให้การ debug ทำได้ง่ายขึ้น
โมเดล Staging อยู่ใกล้กับข้อมูลดิบมากที่สุด หน้าที่ของมันแคบมาก: เปลี่ยนชื่อคอลัมน์ แปลงชนิดข้อมูล และกรองแถวที่ไม่ถูกต้องออก ไม่มี join ไม่มี aggregation
-- models/staging/stg_orders.sql
WITH source AS (
SELECT * FROM {{ source('ecommerce', 'raw_orders') }}
)
SELECT
id AS order_id,
customer_id,
CAST(order_date AS DATE) AS order_date,
CAST(amount AS DECIMAL(10, 2)) AS order_amount,
LOWER(status) AS order_status
FROM source
WHERE id IS NOT NULLโมเดล Intermediate ใช้ business logic — join, aggregation, window function โดยอ้างอิงโมเดล staging ผ่าน ref() ซึ่งลงทะเบียน dependency ใน DAG
-- models/intermediate/int_customer_orders.sql
WITH orders AS (
SELECT * FROM {{ ref('stg_orders') }}
),
customers AS (
SELECT * FROM {{ ref('stg_customers') }}
)
SELECT
c.customer_id,
c.customer_name,
COUNT(o.order_id) AS total_orders,
SUM(o.order_amount) AS lifetime_value,
MIN(o.order_date) AS first_order_date,
MAX(o.order_date) AS last_order_date
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.customer_nameMarts คือชั้นบริโภคขั้นสุดท้าย — ตารางที่สะอาด มีเอกสารประกอบ พร้อมให้ dashboard และนักวิเคราะห์ query ได้โดยตรง
แนวทางการแบ่งชั้นนี้ทำให้เมื่อแหล่งข้อมูลมีปัญหา จะส่งผลกระทบเฉพาะชั้น staging เท่านั้น ไม่ใช่ทั้ง pipeline แต่ละชั้นสามารถทดสอบได้อย่างอิสระ
ฟังก์ชัน ref() และการแก้ไข DAG
ref() ไม่ใช่แค่ alias การเรียก {{ ref('stg_orders') }} ทำสองสิ่ง: แก้ไขชื่อตารางแบบเต็มในเวลาคอมไพล์ และลงทะเบียน dependency edge ใน DAG (directed acyclic graph) ของ dbt หากไม่มี ref() dbt จะไม่มีทางกำหนดลำดับการทำงานได้
ข้อผิดพลาดที่พบบ่อยคือการเขียนชื่อตารางแบบ hardcode แทนที่จะใช้ ref() ซึ่งจะทำให้ระบบติดตาม dependency เสียหาย และอาจทำให้โมเดลทำงานก่อนที่ dependency upstream จะพร้อม
-- ผิด: อ้างอิงแบบ hardcode ไม่มีการติดตาม dependency
SELECT * FROM analytics.stg_orders
-- ถูก: ref() ลงทะเบียน dependency
SELECT * FROM {{ ref('stg_orders') }}DAG ยังรองรับฟีเจอร์อย่าง dbt run --select stg_orders+ ซึ่งรันโมเดลและทุกอย่างที่อยู่ downstream — มีประโยชน์สำหรับการ rebuild แบบเจาะจงหลังจากมีการเปลี่ยนแปลง schema ของแหล่งข้อมูล
Materialization: การเลือกกลยุทธ์การจัดเก็บที่เหมาะสม
dbt มีmaterialization ในตัวสี่แบบ แต่ละแบบเหมาะกับรูปแบบการเข้าถึงและปริมาณข้อมูลที่แตกต่างกัน
| Materialization | การจัดเก็บ | เหมาะสำหรับ | ข้อแลกเปลี่ยน |
|----------------|-----------|------------|---------------|
| view | ไม่มีการจัดเก็บ (คำนวณเมื่ออ่าน) | การแปลงขนาดเล็ก dataset ขนาดเล็ก | อ่านช้าเมื่อข้อมูลมาก |
| table | สร้างตารางใหม่ทั้งหมดทุกครั้งที่รัน | โมเดลชั้น mart อ่านเร็ว | สร้างใหม่ทุกอย่าง ค่าใช้จ่ายสูงกว่า |
| incremental | เพิ่ม/merge เฉพาะแถวใหม่ | ตาราง fact ขนาดใหญ่ event stream | logic ซับซ้อนกว่า ต้องการ unique_key |
| ephemeral | ถูก inline เป็น CTE ไม่เคยถูก materialize | logic ที่ใช้ซ้ำระหว่างโมเดล | ไม่สามารถ query ได้โดยตรง |
ค่าเริ่มต้นคือ view สามารถเปลี่ยนได้ในบล็อก config ของโมเดลหรือใน dbt_project.yml:
-- models/marts/fct_daily_revenue.sql
{{
config(
materialized='incremental',
unique_key='revenue_date',
incremental_strategy='merge'
)
}}
SELECT
DATE(order_date) AS revenue_date,
SUM(order_amount) AS daily_revenue,
COUNT(DISTINCT customer_id) AS unique_customers
FROM {{ ref('stg_orders') }}
WHERE order_status = 'completed'
{% if is_incremental() %}
AND order_date > (SELECT MAX(revenue_date) FROM {{ this }})
{% endif %}
GROUP BY DATE(order_date)บล็อก is_incremental() จะทำงานเฉพาะระหว่าง incremental build เท่านั้น — เมื่อรีเฟรชทั้งหมด (dbt run --full-refresh) บล็อกนี้จะถูกข้ามและสร้างตารางใหม่ทั้งหมด
พร้อมที่จะพิชิตการสัมภาษณ์ Data Engineering แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
การทดสอบคุณภาพข้อมูลในทุกชั้น
dbt มีการทดสอบสองประเภท: generic test ที่ประกาศใน YAML และ singular test ที่เขียนเป็นไฟล์ SQL แยกต่างหาก ซึ่งจะคืนค่าแถวเมื่อการทดสอบล้มเหลว
Generic test ครอบคลุม constraint เชิงโครงสร้าง:
# models/staging/_stg_models.yml
version: 2
models:
- name: stg_orders
columns:
- name: order_id
tests:
- unique # ไม่มี order ID ซ้ำ
- not_null # ทุกแถวต้องมี order ID
- name: order_status
tests:
- accepted_values:
values: ['completed', 'pending', 'cancelled', 'refunded']
- name: customer_id
tests:
- not_null
- relationships: # ตรวจสอบ referential integrity
to: ref('stg_customers')
field: customer_idSingular test ตรวจสอบกฎทางธุรกิจที่ generic test ไม่สามารถแสดงออกได้:
-- tests/assert_revenue_never_negative.sql
-- คืนค่าแถวที่รายได้รายวันเป็นลบ (ควรคืนค่า 0 แถว)
SELECT
revenue_date,
daily_revenue
FROM {{ ref('fct_daily_revenue') }}
WHERE daily_revenue < 0Unit test ซึ่งเปิดตัวใน dbt 1.8 จะตรวจสอบ logic การแปลงข้อมูลด้วย input ที่ควบคุมได้และ output ที่คาดหวัง — โดยไม่ต้องใช้ warehouse ทำงานโดยตรงในขั้นตอน dbt test
กลยุทธ์การทดสอบควรเป็นไปตามการไหลของข้อมูล: ตรวจสอบความสดของแหล่งข้อมูลและจำนวนแถว ทดสอบ schema ในระดับ staging (not_null, unique) ความสมบูรณ์ของ referential integrity ในระดับ intermediate และยืนยัน business logic ในระดับ mart
dbt Core v1.10 และ Fusion Engine
dbt Core v1.10 เปิดตัว flag --sample สำหรับคำสั่ง run และ build flag นี้ใช้การสุ่มตัวอย่างตามเวลากับ ref() และ source() ช่วยให้นักพัฒนาตรวจสอบการแปลงข้อมูลบนชุดข้อมูลย่อยโดยไม่ต้องเสียค่าใช้จ่ายสำหรับ build ทั้งหมด มีประโยชน์อย่างมากเมื่อทำ iteration บนโมเดลที่อ้างอิงตาราง fact ที่มีหลายพันล้านแถว
Fusion engine ของ dbt ซึ่งเป็นการเขียนใหม่ทั้งหมดด้วย Rust ตอนนี้เป็นค่าเริ่มต้นสำหรับโปรเจกต์ใหม่ใน dbt Cloud บน Snowflake, BigQuery, Redshift และ Databricks Fusion เปิดตัว semantic versioning เริ่มต้นที่เวอร์ชัน 2.0 และนำการปรับปรุงประสิทธิภาพที่สำคัญมาสู่การคอมไพล์และการรัน
สิ่งที่เพิ่มเข้ามาที่น่าสนใจอื่นๆ ในปี 2026: Semantic Layer YAML spec สำหรับนิยาม metric แบบรวมศูนย์, Cost Insights (beta) สำหรับประมาณค่าใช้จ่าย compute ของ warehouse ต่อโมเดล, และ native private packages ที่พร้อมใช้งานทั่วไปแล้ว
Macro และ Jinja สำหรับ Logic ที่ใช้ซ้ำได้
เมื่อรูปแบบ SQL เดียวกันปรากฏในหลายโมเดล ควรแยกออกมาเป็น macro ซึ่งเป็นฟังก์ชัน Jinja ที่เก็บไว้ในไดเรกทอรี macros/
-- macros/cents_to_dollars.sql
{% macro cents_to_dollars(column_name) %}
ROUND(CAST({{ column_name }} AS DECIMAL(10, 4)) / 100, 2)
{% endmacro %}เรียกใช้ในโมเดลใดก็ได้:
-- models/staging/stg_payments.sql
SELECT
payment_id,
order_id,
{{ cents_to_dollars('amount_cents') }} AS amount_dollars,
payment_method
FROM {{ source('stripe', 'payments') }}แนวทางนี้ช่วยให้ codebase เป็นไปตามหลัก DRY (Don't Repeat Yourself) ทางเลือกอื่น — คือการคัดลอกสูตรการแปลงไปยังทุกโมเดลที่ต้องการ — จะสร้างภาระในการบำรุงรักษาและความไม่สอดคล้องกัน
คำถามสัมภาษณ์ที่พบบ่อย
การสัมภาษณ์ตำแหน่ง data engineering ในปี 2026 ทดสอบความรู้ dbt ในเชิงลึกมากขึ้น ต่อไปนี้คือคำถามที่ช่วยแยกแยะผู้สมัคร อ้างอิงจากรูปแบบที่พบในบริษัทต่างๆ ที่ใช้ modern data stack
ถ: โมเดล incremental ประมวลผลซ้ำทั้งตารางในช่วงกลางคืน เกิดอะไรขึ้น?
สาเหตุที่พบบ่อยที่สุด: คอลัมน์ unique_key มีค่า null เมื่อ dbt พยายาม MERGE บนคีย์ที่เป็น null การจับคู่จะล้มเหลวสำหรับทุกแถว จึงเกิดการแทรกข้อมูลซ้ำ สาเหตุที่สอง: มีคนรัน dbt run --full-refresh โดยไม่ทราบว่าคำสั่งนี้จะสร้างตารางใหม่ทั้งหมดตั้งแต่ต้น สาเหตุที่สาม: ตัวกรอง is_incremental() อ้างอิงคอลัมน์ที่ยังไม่มีอยู่ในตารางปลายทาง (การรันครั้งแรก vs การรันครั้งถัดไป) การ debug เริ่มต้นด้วยการตรวจสอบ SQL ที่คอมไพล์แล้วใน target/compiled/
ถ: การทดสอบควรถูกจัดเรียงเป็นชั้นอย่างไรในโปรเจกต์ dbt ที่ใช้งานจริง?
การตรวจสอบความสดของแหล่งข้อมูลรันก่อน — หากแหล่งข้อมูลยังไม่อัปเดต โมเดล downstream ไม่ควรรันบนข้อมูลเก่า การทดสอบ staging ตรวจสอบ constraint ของ schema: primary key (unique + not_null) ค่าที่ยอมรับได้ และการแปลงชนิดข้อมูล การทดสอบ intermediate ตรวจสอบ referential integrity ระหว่าง join การทดสอบ mart ยืนยัน business invariant: รายได้ >= 0 ผู้ใช้ที่ active >= ผู้ใช้ที่จ่ายเงิน และผลรวมของส่วนย่อยเท่ากับทั้งหมด การทดสอบทั้งหมดรันใน CI บน pull request กับ schema dev จากนั้นรันอีกครั้งหลัง deployment ใน production พร้อมระบบแจ้งเตือน
ถ: เมื่อไหร่ควรใช้โมเดล ephemeral แทน view?
โมเดล ephemeral จะ inline SQL ของมันเป็น CTE ในโมเดลที่เรียกใช้ — มีประโยชน์สำหรับ logic ที่ใช้ซ้ำได้แต่ไม่จำเป็นต้อง query แยก View จะคำนวณเมื่ออ่านและมีอยู่เป็นออบเจกต์ที่ query ได้ ข้อแลกเปลี่ยน: โมเดล ephemeral ไม่สามารถทดสอบได้โดยตรง (ไม่มีตารางให้รัน assertion) และไม่ปรากฏในเครื่องมือ data lineage ควรใช้ ephemeral สำหรับ logic helper ภายใน; ใช้ view สำหรับสิ่งที่ต้องการทดสอบหรือ monitoring แยกต่างหาก
ถ: อธิบายความแตกต่างระหว่าง source() และ ref()
source() ชี้ไปยังตารางดิบที่กำหนดในไฟล์ sources.yml — เป็นตารางที่ dbt ไม่ได้จัดการ ref() ชี้ไปยังโมเดล dbt อื่นๆ ทั้งคู่ลงทะเบียน dependency ของ DAG แต่ source() ยังเปิดใช้งานการตรวจสอบความสด (dbt source freshness) ซึ่ง ref() ไม่มี การใช้ source() สำหรับตารางดิบและ ref() สำหรับทุกอย่างอื่นไม่ใช่ทางเลือก — เป็นวิธีที่ dbt รู้ว่าอะไรอยู่ในการควบคุมของมันและอะไรไม่ใช่
สำหรับชุดคำถามสัมภาษณ์ data engineering ที่ครอบคลุมมากขึ้น รวมถึงหัวข้ออย่าง สถาปัตยกรรม pipeline ETL vs ELT โมดูลฝึกฝนของ SharpSkill เกี่ยวกับพื้นฐาน dbt และรูปแบบ dbt ขั้นสูง ครอบคลุมแนวคิดเหล่านี้พร้อมแบบฝึกหัดเชิงโต้ตอบ
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
สรุป
- dbt แปลงข้อมูลดิบใน warehouse ผ่านคำสั่ง SQL SELECT ที่คอมไพล์เป็น DDL โดยอัตโนมัติ — โครงสร้างแบบแบ่งชั้น staging/intermediate/mart ช่วยให้แต่ละโมเดลมุ่งเน้นที่ความรับผิดชอบเดียว
- ฟังก์ชัน
ref()เป็นแกนหลักของการจัดการ dependency: แก้ไขชื่อตารางและสร้าง DAG ที่ควบคุมลำดับการทำงาน - Materialization แบบ incremental ลดค่าใช้จ่ายสำหรับตารางขนาดใหญ่ แต่ต้องจัดการ
unique_keyค่า null และตัวกรองis_incremental()อย่างระมัดระวัง - การทดสอบควรเป็นไปตามการไหลของข้อมูล — ความสดของแหล่งข้อมูล constraint ของ schema ที่ staging referential integrity ที่ intermediate การยืนยัน business logic ที่ mart
- dbt Core v1.10 นำ flag
--sampleมาเพื่อ iteration ที่เร็วขึ้น ขณะที่ Fusion engine (สร้างด้วย Rust เวอร์ชัน 2.0) เป็นค่าเริ่มต้นใน dbt Cloud แล้ว - คำถามสัมภาษณ์เกี่ยวกับ dbt เน้นที่การ debug ความล้มเหลวจริง (การประมวลผลซ้ำ incremental คีย์ null แหล่งข้อมูลเก่า) มากกว่าคำนิยาม — ประสบการณ์จริงกับ SQL ที่คอมไพล์แล้วและ pipeline CI สำคัญกว่าคำตอบที่ท่องจำ
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
แท็ก
แชร์
บทความที่เกี่ยวข้อง

ETL vs ELT ในปี 2026: สถาปัตยกรรม Data Pipeline ที่ Data Engineer ต้องรู้
เปรียบเทียบ ETL และ ELT อย่างละเอียด พร้อมตัวอย่างโค้ด dbt และ Python เพื่อเลือกสถาปัตยกรรม data pipeline ที่เหมาะสมกับทีมของคุณในปี 2026

25 คำถามสัมภาษณ์งาน Data Engineering ยอดนิยมในปี 2026
25 คำถามสัมภาษณ์งาน data engineering ที่ถูกถามบ่อยที่สุดในปี 2026 ครอบคลุม SQL, data pipeline, ETL/ELT, Spark, Kafka, data modeling และ system design พร้อมคำตอบโดยละเอียด

Apache Spark 4: ฟีเจอร์ใหม่ Structured Streaming และคำถามสัมภาษณ์งาน
สำรวจฟีเจอร์สำคัญใน Apache Spark 4 รวมถึง ANSI SQL Mode, VARIANT data type, Real-Time Mode streaming และ transformWithState API พร้อมตัวอย่างโค้ดและคำถามสัมภาษณ์งานที่พบบ่อย