สร้าง
นิยาม relational interface ที่เรียกใช้ซ้ำได้ด้วย CREATE VIEW
DATA ENGINEERING LAB · SQL VIEW
VIEW คือ query ที่ถูกบันทึกไว้และเรียกใช้เสมือนตาราง สำหรับงาน Data Engineering คุณค่าที่ลึกกว่าการเขียน SQL ให้สั้นลง คือการสร้าง interface ที่ควบคุมได้ ระหว่าง source table ซึ่งอาจเปลี่ยนแปลง กับผู้ใช้ dashboard และ pipeline ที่ต้องพึ่งข้อมูลนั้น
นิยาม relational interface ที่เรียกใช้ซ้ำได้ด้วย CREATE VIEW
เปิดเฉพาะข้อมูลที่จำเป็น พร้อมซ่อนรายละเอียดภายในหรือข้อมูลอ่อนไหว
แก้ transformation logic ที่จุดเดียว แทนการแก้ผู้ใช้ข้อมูลทุกระบบ
ตรวจ grain, uniqueness, null และยอดรวมก่อนเผยแพร่ view
MENTAL MODEL
เมื่อ query VIEW ปกติ ฐานข้อมูลจะขยาย definition แล้วอ่านข้อมูลจาก relation ต้นทาง ต่างจาก materialized view ที่เก็บผลลัพธ์จริงและต้อง refresh
สะท้อนข้อมูลต้นทางเมื่อถูกเรียกใช้ ต้นทุน query เกิดตอนอ่าน เหมาะกับ abstraction, governance และ logic ที่ใช้ซ้ำ
เก็บผลลัพธ์ที่คำนวณแล้ว อ่านได้เร็วขึ้น แต่ความสดขึ้นกับนโยบาย refresh เหมาะกับ aggregation ที่แพงและถูกเรียกซ้ำ
เป็นเจ้าของแถวข้อมูลและวงจรการเขียน ใช้เมื่อ dataset ต้องมี storage, history หรือ incremental load ของตนเอง
01 · SET UP THE SOURCE
รันคำสั่งใน PostgreSQL, DuckDB หรือฐานข้อมูล SQL อื่น โดยปรับชนิดข้อมูลและ syntax ตามระบบที่ใช้
CREATE SCHEMA IF NOT EXISTS raw;
CREATE SCHEMA IF NOT EXISTS analytics;
CREATE TABLE raw.customers (
customer_id INTEGER PRIMARY KEY,
customer_name VARCHAR(100) NOT NULL,
email VARCHAR(150),
segment VARCHAR(30) NOT NULL
);
CREATE TABLE raw.orders (
order_id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL REFERENCES raw.customers(customer_id),
ordered_at TIMESTAMP NOT NULL,
status VARCHAR(20) NOT NULL,
amount DECIMAL(12,2) NOT NULL
);
INSERT INTO raw.customers VALUES
(1,'Aster Studio','[email protected]','SME'),
(2,'Blue River','[email protected]','Enterprise'),
(3,'Cedar Lab','[email protected]','Education');
INSERT INTO raw.orders VALUES
(101,1,'2026-08-01 09:15','completed',12500.00),
(102,1,'2026-08-03 13:40','cancelled',3200.00),
(103,2,'2026-08-04 10:10','completed',54000.00),
(104,3,'2026-08-05 16:25','pending',7800.00),
(105,2,'2026-08-07 11:30','completed',18500.00);02 · CREATE THE FIRST VIEW
Grain หรือความหมายของหนึ่งแถว เป็นส่วนหนึ่งของ data contract ต้องระบุให้ชัดก่อนเขียน SQL
CREATE VIEW analytics.v_completed_orders AS
SELECT
o.order_id,
o.ordered_at,
CAST(o.ordered_at AS DATE) AS order_date,
c.customer_id,
c.customer_name,
c.segment,
o.amount
FROM raw.orders AS o
JOIN raw.customers AS c
ON c.customer_id = o.customer_id
WHERE o.status = 'completed';
SELECT *
FROM analytics.v_completed_orders
ORDER BY ordered_at;03 · BUILD A REUSABLE METRIC
VIEW ที่สองพึ่ง VIEW แรก การแบ่งชั้นมีประโยชน์เมื่อแต่ละชั้นมีหน้าที่ชัดเจน แต่หากต่อ VIEW ยาวโดยไร้แบบแผน ระบบจะ debug ยาก
CREATE VIEW analytics.v_daily_revenue AS
SELECT
order_date,
COUNT(*) AS completed_orders,
COUNT(DISTINCT customer_id) AS active_customers,
SUM(amount) AS revenue,
AVG(amount) AS average_order_value
FROM analytics.v_completed_orders
GROUP BY order_date;
SELECT *
FROM analytics.v_daily_revenue
ORDER BY order_date;Pending order ควรถูกนับเป็นรายได้หรือไม่ การคืนเงินควรลดยอดวันขายหรือวันคืน SQL ตัดสินนโยบายบัญชีแทนองค์กรไม่ได้ VIEW ต้องบันทึกนิยามที่ตกลงกับเจ้าของข้อมูลแล้ว
04 · CHANGE WITHOUT BREAKING CONSUMERS
CREATE OR REPLACE VIEW ช่วยเปลี่ยน logic ที่ยังเข้ากันได้โดยรักษาชื่อ object แต่แต่ละ database มีข้อกำหนดต่างกันเรื่องการลบ สลับ หรือเปลี่ยนชนิดคอลัมน์ ต้องตรวจเอกสารของระบบที่ใช้
CREATE OR REPLACE VIEW analytics.v_daily_revenue AS
SELECT
order_date,
COUNT(*) AS completed_orders,
COUNT(DISTINCT customer_id) AS active_customers,
SUM(amount) AS revenue,
AVG(amount) AS average_order_value,
MAX(amount) AS largest_order
FROM analytics.v_completed_orders
GROUP BY order_date;คอลัมน์ใหม่จาก source อาจไหลเข้าสู่ downstream โดยไม่มีใครตั้งใจ การระบุคอลัมน์ชัดเจนช่วยให้ review schema และ lineage ง่ายขึ้น
05 · VALIDATE BEFORE PUBLISHING
รันการตรวจเหล่านี้หลัง source หรือ transformation เปลี่ยน ใน production ควร automate ผ่าน dbt, orchestration หรือ data-quality framework
SELECT order_id, COUNT(*)
FROM analytics.v_completed_orders
GROUP BY order_id
HAVING COUNT(*) > 1;คาดหวัง: ไม่พบแถวSELECT COUNT(*) AS invalid_rows
FROM analytics.v_completed_orders
WHERE customer_id IS NULL
OR amount IS NULL
OR amount < 0;คาดหวัง: 0SELECT
(SELECT SUM(amount) FROM raw.orders
WHERE status='completed') AS source_total,
(SELECT SUM(revenue)
FROM analytics.v_daily_revenue) AS view_total;คาดหวัง: ยอดเท่ากัน06 · DATA ENGINEERING VALUE
VIEW คือสถาปัตยกรรมขนาดเบา คุณค่าจะเห็นชัดเมื่อเจ้าของระบบต้นทาง Data Engineer, Analyst, Application Developer และทีม Governance ต้องใช้ข้อมูลร่วมกัน โดยไม่จำเป็นต้องรู้หรือเปิดเผยรายละเอียดภายในทั้งหมด VIEW จึงกลายเป็นข้อตกลงขนาดเล็กแต่ชัดเจนว่า สิ่งใดคือข้อมูลที่ผู้อื่นสามารถพึ่งพาได้
ซ่อนวิธีจัดเก็บ แล้วเผยแพร่ความหมายทางธุรกิจที่ผู้ใช้ต้องการ
ระบบคำสั่งซื้อเก็บสถานะ C, P และ X กระจายอยู่ใน orders, status_history และ customer Analyst ไม่ควรต้องเรียนรู้สาม schema และถอดรหัสภายใน ก่อนตอบคำถามง่าย ๆ ว่า “เมื่อวานเราขายได้เท่าไร”
order_id, order_date, customer_segment, order_status, net_amount ด้วยค่าที่อ่านรู้เรื่องและ grain ที่ระบุชัด
ผลที่ได้: ตารางต้นทางยังออกแบบให้เหมาะกับ transaction ได้ ขณะที่ผู้ใช้ทำงานกับคำศัพท์เชิงวิเคราะห์ที่มั่นคง แต่ abstraction ไม่ได้แปลว่าซ่อน lineage ต้องยังสืบได้ว่าแต่ละ field มาจากไหน
เขียน logic ที่ยากในจุดเดียว review ร่วมกัน แล้วให้หลายระบบใช้ซ้ำ
Finance ไม่นับ cancelled order, Marketing นับรายการที่จ่ายแล้วแต่ยังไม่ส่ง และ Operations ใช้วันส่งแทนวันสั่งซื้อ แต่ละ dashboard มี query 40 บรรทัดคนละฉบับ จึงมีตัวเลข “รายได้” สามค่าในที่ประชุมเดียวกัน
Join, status filter, cast, currency conversion และ deduplication ถูก review เป็น transformation เดียว แทนการ copy ไปทุก notebook และ dashboard
ผลที่ได้: แก้ logic ครั้งเดียว ผู้ใช้ทุกคนได้รับผลในการ query ครั้งถัดไป แต่ reuse จะปลอดภัยเมื่อทุกฝ่ายหมายถึง concept เดียวกันจริง หากมีความหมายทางธุรกิจต่างกัน ก็ควรแยก VIEW ให้ชัด
เปลี่ยน query ที่ตกลงกันด้วยคำพูด ให้เป็น interface ที่มีชื่อ grain, key และความคาดหวัง
Pipeline ของ Machine Learning คิดว่าหนึ่งแถวคือลูกค้าหนึ่งราย แต่ join ใหม่ทำให้กลายเป็นหนึ่งแถวต่อลูกค้าต่อที่อยู่ Pipeline ยังรันผ่าน แต่ลูกค้าที่มีหลายที่อยู่ถูกให้น้ำหนักมากเกินไปในการ train
หนึ่งแถวต่อลูกค้า; customer_id ต้อง unique และไม่เป็น null; amount มีหน่วย THB; ข้อมูลพร้อมทุกวันก่อน 07:00; มี owner และขั้นตอนเมื่อเกิด breaking change
ผลที่ได้: ทีม downstream รู้ว่าสมมติอะไรได้และเขียน automated test รองรับได้ แต่ VIEW เพียงอย่างเดียวยังไม่ใช่ contract ที่ครบ ต้องมีเอกสาร ownership, freshness monitoring และการสื่อสารเมื่อเปลี่ยนแปลง
ให้ข้อมูลเท่าที่งานต้องใช้ โดยไม่ส่งมอบ source ทั้งหมด
Analyst ของมหาวิทยาลัยต้องการจำนวนผู้ลงทะเบียนแยกคณะและปี แต่ student table ยังมีชื่อ email ส่วนตัว เบอร์โทร และเลขประจำตัวประชาชน การให้ SELECT บน base table เปิดข้อมูลมากเกินกว่างานต้องใช้
คณะ ปีการศึกษา และจำนวนสรุป หรือ pseudonymous identifier ที่ได้รับอนุมัติ ขณะที่ยัง revoke สิทธิ์เข้าถึง base table โดยตรง
ผลที่ได้: ขอบเขตข้อมูลที่เปิดใช้เล็กลงและ audit ง่ายขึ้น แต่ยังต้องทดสอบ ownership rule, row-level policy, การอนุมานจากกลุ่มขนาดเล็ก และ permission ด้วย role ของผู้ใช้จริง
ทำให้คำเดียวกันมีความหมายเดียวกันข้ามเครื่องมือ
“Active customer” สำหรับ Product คือ login ภายใน 30 วัน สำหรับ Marketing คือซื้อภายใน 90 วัน และสำหรับ Finance คือมีสัญญาที่ยังไม่สิ้นสุด ทุกนิยามอาจถูกต้อง แต่ metric ที่ไม่ระบุความหมายทำให้ผู้ใช้เปรียบเทียบตัวเลขที่ตอบคนละคำถาม
v_product_active_customer_30d, v_purchasing_customer_90d พร้อมนิยามที่ owner ของแต่ละเรื่องรับรอง ผู้ใช้เลือก concept ที่ต้องการ แทนการสร้างนิยามใหม่โดยไม่ตั้งใจ
ผลที่ได้: การประชุมเปลี่ยนจากเถียงว่า SQL ของใครถูก ไปเป็นการเลือกว่ากำลังตอบคำถามธุรกิจข้อใด ความสอดคล้องไม่ได้หมายถึงบังคับทุกบริบทให้ใช้ metric สากลเพียงตัวเดียว
เปลี่ยนกลไกหลัง interface ที่ยังคงเดิม แล้วทยอยย้ายผู้ใช้ตามแผน
ข้อมูล orders ย้ายจากฐานข้อมูลเดิมไป platform ใหม่ ชื่อตาราง ชนิด timestamp และ customer key เปลี่ยน แต่ dashboard 12 ชุด scheduled export 2 งาน และโมเดล 1 ตัว ยังอาศัยรูปแบบข้อมูลเดิม การสลับพร้อมกันทั้งหมดเสี่ยงทำให้ทุกระบบเสียพร้อมกัน
ชื่อคอลัมน์ ชนิดข้อมูล และ grain เดิม ขณะที่อ่านจาก source ใหม่ จากนั้นเปิด v2 สำหรับ schema ที่ปรับปรุงแล้ว เพื่อให้ consumer ย้ายและ validate ทีละระบบ
ผลที่ได้: ความเสี่ยงถูกแบ่งเป็นขั้นที่สังเกตและตรวจสอบได้ พร้อม rollback ได้ แต่ compatibility VIEW ต้องมีวันเลิกใช้ ไม่เช่นนั้นสะพานชั่วคราวจะกลายเป็น technical debt ถาวร
VIEW ที่ดีไม่ได้เพียงทำให้ SQL สั้นลง แต่เปิดให้ผู้ผลิตข้อมูลปรับ implementation ภายในได้ ขณะเดียวกันผู้ใช้ได้รับคำสัญญาที่ชัดเจน ตรวจสอบได้ และอยู่ภายใต้การกำกับที่เหมาะสม
07 · GOVERN ACCESS
กลไกความปลอดภัยขึ้นกับ database ต้องตรวจ owner, invoker/definer rights, row-level security และความเสี่ยงจากการอนุมานข้อมูล ไม่ควรสรุปจากการมี VIEW เพียงอย่างเดียว
-- Example role names; adapt to your environment.
REVOKE ALL ON raw.customers, raw.orders FROM analyst_role;
GRANT USAGE ON SCHEMA analytics TO analyst_role;
GRANT SELECT ON analytics.v_completed_orders,
analytics.v_daily_revenue
TO analyst_role;ทดสอบสิทธิ์ด้วย role ของผู้ใช้จริง และตรวจว่า predicate, function, error message หรือ aggregate อาจเผยข้อมูลที่ต้องปกป้องหรือไม่
CHALLENGES
สร้าง v_customer_lifetime_value ให้หนึ่งแถวแทนลูกค้าหนึ่งราย และต้องมีลูกค้าที่ไม่มี completed order ด้วย
เพิ่มตาราง refund แล้วนิยาม net revenue เขียนสมมติฐานทางธุรกิจก่อนเขียน SQL
สร้าง VIEW สำหรับ analyst ที่มี customer_id และ segment แต่ไม่มีชื่อและ email
ใช้ EXPLAIN เปรียบเทียบ query โดยตรงกับ query ผ่าน VIEW แล้วอธิบายว่าเหตุใด VIEW จึงไม่รับประกัน performance
ออกแบบ v_daily_revenue_v2 โดยไม่ทำให้ v_daily_revenue ใช้งานไม่ได้ พร้อมระบุแผน deprecation
ตัดสินใจว่า monthly aggregate ที่คำนวณแพงควรเป็น VIEW, materialized view หรือตาราง โดยพิจารณา freshness, cost และ recovery
FINAL CHECK