วัตถุประสงค์การเรียนรู้
เมื่อเรียนจบบทนี้ คุณจะสามารถ
- เพิ่มระเบียนสินค้าใหม่ลงในตาราง
menusโดยใช้คำสั่ง INSERT แบบแถวเดียวและหลายแถว - เติมข้อมูลในตารางจาก query ด้วย
INSERT … SELECTสำหรับการดำเนินการจำนวนมากและการ archive ข้อมูล - แก้ไขระเบียนที่มีอยู่อย่างแม่นยำด้วย
UPDATE … SET … WHEREรวมถึงUPDATEแบบมีเงื่อนไขโดยใช้CASE - ลบแถวออกถาวรด้วย
DELETE … WHEREและอธิบายได้ว่าเมื่อใดควรใช้ DELETE เมื่อใดควรใช้รูปแบบ soft-delete แทน
บทนำ
ฐานข้อมูลเป็นองค์ประกอบสำคัญของการดำเนินธุรกิจ เพราะต้องรองรับทั้งการเติบโตของข้อมูล การเปลี่ยนแปลงของรายการธุรกรรม และการกำจัดข้อมูลที่ล้าสมัยอย่างเป็นระบบ บทนี้จึงนำเสนอคำสั่งในกลุ่ม Data Manipulation Language (DML) ซึ่งใช้ในการเพิ่ม แก้ไข และลบข้อมูล เพื่อให้ข้อมูลในฐานข้อมูลถูกต้องและทันสมัยอยู่เสมอ ได้แก่ INSERT, UPDATE และ DELETE
จนถึงจุดนี้ คำสั่ง SQL ที่ปรากฏในตำราเล่มนี้ยังจำกัดอยู่ที่ SELECT ซึ่งใช้เพื่อการสืบค้นข้อมูลเป็นหลัก แต่ฐานข้อมูลในองค์กรธุรกิจจริงไม่ได้มีหน้าที่เพียงเก็บและแสดงผลข้อมูลเท่านั้น หากยังต้องรองรับการเพิ่ม การปรับปรุง และการยกเลิกรายการข้อมูลอย่างต่อเนื่อง ตัวอย่างเช่น เมื่อมีการพัฒนาเครื่องดื่มใหม่อย่าง Lychee Rose Milk Tea สำหรับโปรโมชันเทศกาลสงกรานต์ ธุรกิจก็ต้องใช้ INSERT เพื่อบันทึกเมนูใหม่ลงในตาราง menus หากต้นทุนวัตถุดิบเพิ่มขึ้นและมีนโยบายปรับราคาสินค้ากลุ่ม seasonal ขึ้น 5% ก็ต้องใช้ UPDATE เพื่อแก้ไขราคาที่เกี่ยวข้องทั้งหมด และเมื่อ Osmanthus Milk Tea ถูกยกเลิกหลังสัปดาห์ที่ 4 ก็ต้องตัดสินใจให้เหมาะสมว่าจะใช้ DELETE เพื่อลบข้อมูลออก หรือจะคงข้อมูลไว้ในสถานะปิดใช้งานเพื่อประโยชน์ด้านการควบคุมและการตรวจสอบย้อนหลัง
INSERT
INSERT ใช้สำหรับเพิ่มแถวข้อมูลในตาราง เราจะเริ่มจากกรณีเพิ่มเมนูใหม่แบบแถวเดียว ขยายไปสู่การเพิ่มหลายแถว และจบด้วยการใช้ INSERT ... SELECT เพื่อ archive สินค้าที่กำลังจะถูกปลดระวาง
ก่อนที่จะ INSERT ควรตรวจสอบสถานะปัจจุบันของตารางก่อนเสมอ โดยตรวจสอบจำนวนแถวข้อมูลในตาราง menus ก่อน INSERT
| total_skus | highest_sku_id |
|---|---|
| 22 | 22 |
sku_id ที่ว่างถัดไปคือ 23 ตอนนี้ INSERT เครื่องดื่มใหม่
INSERT แบบแถวเดียว
คำสั่ง INSERT มีไวยากรณ์พื้นฐานดังนี้
คุณไม่จำเป็นต้องระบุทุกคอลัมน์ แต่ควรระบุชื่อคอลัมน์ที่ให้ค่าอย่างชัดเจนเสมอ คอลัมน์ที่ไม่ระบุจะได้ค่า DEFAULT (หรือ NULL หากไม่กำหนด default) การระบุ column list ทำให้โค้ดปลอดภัยต่อการเปลี่ยนแปลง schema ในอนาคต
หากทีมผลิตภัณฑ์ของ PiCha Tea House ได้อนุมัติเครื่องดื่ม seasonal ใหม่สำหรับเทศกาลสงกรานต์ ได้แก่ Lychee Rose Milk Tea (ขนาด Large), ราคา 145 บาท เป็นสินค้าในหมวด milk_tea ขนาด Large (700 มล.) คาเฟอีนระดับกลาง เป็นสินค้า seasonal และเปิดตัวเฉพาะที่สาขา ICONSIAM
INSERT INTO menus (
sku_id, sku_name, family, tea_base,
milk_flag, fruit_flag, cold_brew_flag, blended_flag,
size_code, size_ml, list_price_thb,
sweetness_min_pct, sweetness_max_pct,
caffeine_level, seasonal_flag, signature_flag, launch_branch_flag
) VALUES (
23, 'Lychee Rose Milk Tea L', 'milk_tea', 'lychee_rose', -- sku_id, sku_name, family, tea_base,
1, 0, 0, 0, -- milk: ใช่, fruit/cold-brew/blended: ไม่ใช่
'L', 700, 145, -- ขนาด large, 700 มล., 145 บาท
0, 100, -- รองรับความหวาน 0–100%
'medium', 1, 0, 1 -- คาเฟอีนปานกลาง, seasonal, ไม่ใช่ signature, เปิดตัวที่ flagship
);การระบุชื่อคอลัมน์ในคำสั่ง INSERT INTO มีบทบาทสำคัญในการกำหนดให้ค่าข้อมูลแต่ละค่าถูกจับคู่กับคอลัมน์ปลายทางได้อย่างชัดเจน ช่วยลดความเสี่ยงของความผิดพลาดที่อาจเกิดจากการอาศัยลำดับคอลัมน์เพียงอย่างเดียว และยังเปิดโอกาสให้สามารถละเว้นคอลัมน์ที่มีค่าเริ่มต้น หรือคอลัมน์ที่ระบบสร้างขึ้นโดยอัตโนมัติได้
SELECT เพื่อยืนยันหลัง INSERT
| sku_id | sku_name | family | size_code | list_price_thb | seasonal_flag | signature_flag |
|---|---|---|---|---|---|---|
| 23 | Lychee Rose Milk Tea L | milk_tea | L | 145 | 1 | 0 |
จะเห็นได้ว่ามีแถวข้อมูลเมนูใหม่ Lychee Rose Milk Tea L เพิ่มเข้าในตาราง menus
| total_skus |
|---|
| 23 |
เมื่อนับจำนวนเมนูทั้งหมดจะอยู่ที่ 23 เมนู
INSERT แบบหลายแถว
การเพิ่มหลายแถวพร้อมกันทำได้โดยระบุ column list ครั้งเดียวใน INSERT INTO แล้วตามด้วย VALUES ที่มีหลายชุดคั่นด้วย , โดยแต่ละชุดค่าอยู่ในวงเล็บ (...) แยกกัน เช่น
หากทีมผลิตภัณฑ์ได้อนุมัติเครื่องดื่มเพิ่มเติมอีกสองรายการสำหรับการเปิดตัวในฤดูกาลถัดไป ได้แก่ Strawberry Matcha Latte (Large, 155 บาท) และ Cold Brew Jasmine Pure Tea (Large, 120 บาท) แทนที่จะรันคำสั่ง INSERT แยกกันสองครั้ง เราสามารถเขียนคำสั่ง INSERT เดียวที่เพิ่มข้อมูลหลายแถวพร้อมกันได้
-- เพิ่มเมนูใหม่ 2 รายการ: Strawberry Matcha Latte L และ Cold Brew Jasmine Pure Tea L
INSERT INTO menus (
sku_id, sku_name, family, tea_base,
milk_flag, fruit_flag, cold_brew_flag, blended_flag,
size_code, size_ml, list_price_thb,
sweetness_min_pct, sweetness_max_pct,
caffeine_level, seasonal_flag, signature_flag, launch_branch_flag
) VALUES
(24, 'Strawberry Matcha Latte L', 'milk_tea', 'matcha',
1, 1, 0, 0, 'L', 700, 155, 0, 100, 'high', 1, 0, 0),
(25, 'Cold Brew Jasmine Pure Tea L', 'pure_tea', 'jasmine',
0, 0, 1, 0, 'L', 700, 120, 0, 75, 'medium', 0, 0, 0);ตรวจสอบรายการที่เพิ่ม:
| sku_id | sku_name | family | size_code | list_price_thb | seasonal_flag |
|---|---|---|---|---|---|
| 24 | Strawberry Matcha Latte L | milk_tea | L | 155 | 1 |
| 25 | Cold Brew Jasmine Pure Tea L | pure_tea | L | 120 | 0 |
| total_skus |
|---|
| 25 |
INSERT … SELECT
INSERT ... SELECT เป็นรูปแบบที่ขยายความสามารถของคำสั่ง INSERT โดยเปิดให้ผู้ใช้เพิ่มข้อมูลลงในตารางเป้าหมายจากผลลัพธ์ของ query SELECT ได้โดยตรง แทนที่จะป้อนข้อมูลทีละแถว วิธีนี้มีประโยชน์อย่างยิ่งในงานจัดการข้อมูลระดับธุรกิจ เช่น การจัดเก็บข้อมูลย้อนหลังใน archive table การสร้าง summary table เพื่อการวิเคราะห์ และการถ่ายโอนข้อมูลระหว่างตาราง
ไวยากรณ์ ชอง INSERT ... SELECT
ในการใช้คำสั่ง INSERT ... SELECT รายการคอลัมน์ของตารางเป้าหมายต้องตรงกับคอลัมน์ที่ SELECT ส่งคืนทั้งจำนวนและลำดับ เพื่อให้การนำข้อมูลเข้าสู่ตารางเป็นไปอย่างถูกต้อง ทั้งนี้ หากตารางเป้าหมายถูกสร้างจาก CREATE TABLE AS SELECT ... ซึ่งทำให้โครงสร้างคอลัมน์สอดคล้องกับผลลัพธ์ต้นทางอยู่แล้ว ผู้ใช้ก็สามารถละเว้นการระบุรายการคอลัมน์ในฝั่ง INSERT ได้
ตัวอย่างการใช้งาน INSERT ... SELECT ก่อนยุติการจำหน่ายเครื่องดื่ม seasonal เมื่อสิ้นฤดูกาล ทีม data ของ PiCha จะย้าย SKU ที่เกี่ยวข้องไปจัดเก็บในตาราง menus_archive เพื่อคงไว้ซึ่งข้อมูลสำหรับการรายงานย้อนหลังและการวิเคราะห์ในอนาคต
ขั้นตอนที่ 1 สร้าง archive table (DDL):
-- สร้างตาราง archive สำหรับเก็บประวัติเมนูที่ถูกยกเลิก โดยคัดลอก schema จาก menus
CREATE TABLE menus_archive AS
SELECT
*, -- คัดลอกทุก column จากตาราง menus
CURRENT_DATE AS archived_date -- เพิ่ม column วันที่เก็บ archive
FROM menus
WHERE 1=0; -- คัดลอกเฉพาะ schema ไม่มีแถวข้อมูล (เงื่อนไขเป็นเท็จเสมอ)ขั้นตอนที่ 2 Archive สินค้า seasonal (INSERT … SELECT):
ยืนยัน archive:
| sku_id | sku_name | family | list_price_thb | seasonal_flag | archived_date |
|---|---|---|---|---|---|
| 18 | Fresh Orange Jasmine Tea L | fruit_tea | 150 | 1 | 2026-04-26 |
| 21 | Mango Sticky Rice Tea L | fruit_tea | 135 | 1 | 2026-04-26 |
| 22 | Thai Tea Frappe L | blended | 125 | 1 | 2026-04-26 |
เมื่อเข้าใจการเพิ่มแถวใหม่ด้วย INSERT แล้ว ขั้นต่อไปคือการเปลี่ยนข้อมูลในแถวที่มีอยู่ด้วย UPDATE ซึ่งเทียบได้กับการเดินถือปืนยิงป้ายราคาไปตามเมนูที่มีอยู่เดิม
UPDATE
UPDATE เป็นคำสั่งในกลุ่ม DML ที่ใช้สำหรับแก้ไขข้อมูลในแถวที่มีอยู่แล้วภายในตาราง โดยผู้ใช้สามารถเปลี่ยนแปลงค่าของคอลัมน์หนึ่งคอลัมน์หรือหลายคอลัมน์พร้อมกันได้ตามเงื่อนไขที่กำหนด คำสั่งนี้จึงมีบทบาทสำคัญในการทำให้ฐานข้อมูลสะท้อนสภาพการดำเนินงานที่เป็นปัจจุบันอยู่เสมอ
ไวยากรณ์ของคำสั่ง UPDATE แบบทั่วไป
คุณสามารถอัปเดตหลายคอลัมน์ในคำสั่งเดียว โดยระบุใน SET คั่นด้วยจุลภาค หากไม่มี WHERE ทุกแถวในตารางจะถูกอัปเดต ซึ่งในระบบ production มักเป็นสิ่งที่ไม่ต้องการ
ในบริบทธุรกิจ UPDATE มักถูกใช้เมื่อข้อมูลเดิมยังคงมีอยู่ แต่รายละเอียดบางส่วนต้องได้รับการปรับปรุง เช่น การขึ้นราคาสินค้า การเปลี่ยนสถานะคำสั่งซื้อ การแก้ไขข้อมูลลูกค้า หรือการปรับหมวดหมู่ของสินค้าใหม่ การใช้ UPDATE อย่างถูกต้องช่วยให้ธุรกิจรักษาความถูกต้อง ความสอดคล้อง และความทันสมัยของข้อมูลได้ดียิ่งขึ้น
อีกประเด็นที่สำคัญคือ คำสั่ง UPDATE มักทำงานร่วมกับ WHERE เพื่อระบุอย่างชัดเจนว่าต้องการแก้ไขแถวใดบ้าง หากละเว้น WHERE ระบบจะปรับค่าทุกแถวในตารางทันที ซึ่งอาจก่อให้เกิดผลกระทบอย่างมากในเชิงปฏิบัติ ดังนั้นการใช้ UPDATE จึงต้องอาศัยความระมัดระวังและการตรวจสอบเงื่อนไขก่อนดำเนินการเสมอ
ตัวอย่างนี้สาธิตการใช้คำสั่ง UPDATE เพื่อปรับราคาสินค้า โดยสมมติสถานการณ์ว่าทีม supply chain รายงานต่อทีม finance ว่าต้นทุนวัตถุดิบของสินค้ากลุ่ม seasonal เพิ่มขึ้น 5% ในไตรมาสปัจจุบัน ภายใต้นโยบายการตั้งราคาของบริษัท ต้นทุนที่เพิ่มขึ้นนี้จะถูกส่งผ่านไปยังราคาขายของเมนู seasonal ทั้งหมด
ขั้นตอนที่ 1 ยืนยันแถวเป้าหมายด้วย SELECT
| sku_id | sku_name | list_price_thb | seasonal_flag |
|---|---|---|---|
| 18 | Fresh Orange Jasmine Tea L | 150 | 1 |
| 21 | Mango Sticky Rice Tea L | 135 | 1 |
| 22 | Thai Tea Frappe L | 125 | 1 |
สามแถวนี้คือแถวที่ต้องปรับราคาขึ้น
ทำการ UPDATE list_price_thb เพิ่มขึ้น 5%
ตรวจสอบราคาหลังการ UPDATE
| sku_id | sku_name | list_price_thb | seasonal_flag |
|---|---|---|---|
| 18 | Fresh Orange Jasmine Tea L | 158 | 1 |
| 21 | Mango Sticky Rice Tea L | 142 | 1 |
| 22 | Thai Tea Frappe L | 131 | 1 |
ลองดูอีกตัวอย่างของการ UPDATE โดยมีสถานการณ์ว่า จากการวิเคราะห์ข้อมูลย้อนหลัง 60 วันที่ผ่านมาแสดงให้เห็นว่า Pu’er Milk Tea (Regular และ Large) มีแนวโน้มสูงขึ้นทั้งในคะแนนความพึงพอใจของลูกค้าและความถี่ของออเดอร์ ทีม marketing ต้องการเพิ่มทั้งสองขนาดในรายการเครื่องดื่ม signature
ตรวจสอบก่อน UPDATE
| sku_id | sku_name | signature_flag |
|---|---|---|
| 7 | Pu’er Milk Tea R | 0 |
| 8 | Pu’er Milk Tea L | 0 |
ทั้งสองแถวมี signature_flag = 0 ดำเนินการยกระดับ
UPDATE เพื่อยกระดับสถานะ signature
ยืนยันหลัง UPDATE
| sku_id | sku_name | signature_flag |
|---|---|---|
| 7 | Pu’er Milk Tea R | 1 |
| 8 | Pu’er Milk Tea L | 1 |
ทางทีมการตลาดแจ้งว่า เครื่องดื่มประเภท blended ต้องมีการบวก premium 10 บาท และ pure tea ต้องมีการบวก premium 5 บาท ขณะที่หมวดสินค้าอื่นไม่ต้องมีการเปลี่ยนแปลงราคา ในสถานการณ์เช่นนี้ แทนที่จะดำเนินการ UPDATE หลายครั้งแยกตามประเภทสินค้า ผู้ใช้สามารถอาศัย CASE expression ภายใน SET clause เพื่อจัดการเงื่อนไขทั้งหมดได้ใน statement เดียว
เริ่มจากการหาค่าเฉลี่ยของ family ก่อนทำการปรับราคาด้วย UPDATE
| family | skus | avg_price |
|---|---|---|
| blended | 1 | 131.0 |
| fruit_tea | 5 | 121.0 |
| milk_tea | 14 | 107.5 |
| pure_tea | 2 | 97.5 |
ใช้ UPDATE แบบมีเงื่อนไขด้วย CASE
-- ปรับราคาแยกตามกลุ่มสินค้า: blended +10 บาท, pure_tea +5 บาท, กลุ่มอื่นคงเดิม
UPDATE menus
SET list_price_thb = CASE
WHEN family = 'blended' THEN list_price_thb + 10 -- เพิ่ม 10 บาทสำหรับเมนูปั่น
WHEN family = 'pure_tea' THEN list_price_thb + 5 -- เพิ่ม 5 บาทสำหรับชาไม่มีนม
ELSE list_price_thb -- milk_tea และ fruit_tea ไม่เปลี่ยนแปลง
END;ค่าเฉลี่ยของ family หลัง UPDATE
| family | skus | avg_price_after |
|---|---|---|
| blended | 1 | 141.0 |
| fruit_tea | 5 | 121.0 |
| milk_tea | 14 | 107.5 |
| pure_tea | 2 | 102.5 |
เมื่อเข้าใจการเพิ่มและเปลี่ยนแปลงข้อมูลด้วย INSERT และ UPDATE แล้ว คำสั่ง DML ตัวสุดท้ายคือ DELETE ซึ่งเกี่ยวข้องกับการนำแถวออกจากตาราง ทั้งในรูปแบบ hard delete และ soft delete ผ่าน flag
DELETE
DELETE ใช้สำหรับลบแถวข้อมูลออกจากตารางโดยตรง และเมื่อ transaction ถูก commit แล้ว ข้อมูลดังกล่าวจะถูกลบออกจากตารางอย่างถาวร ซึ่งเรียกว่า Hard Delete การลบลักษณะนี้มีความเสี่ยงสูงกว่าการแก้ไขข้อมูลทั่วไป เพราะโดยปกติไม่มีกลไกสำหรับย้อนคืนได้โดย่ง่าย ไม่มีปุ่ม Undo ให้กด ด้วยเหตุนี้ ระบบฐานข้อมูลในสภาพแวดล้อม production จำนวนมากจึงเลือกใช้แนวทาง Soft Delete แทนการลบจริง โดยเปลี่ยนสถานะของแถวผ่านคอลัมน์ เช่น active_flag จาก 1 เป็น 0 วิธีนี้ทำให้ข้อมูลยังคงอยู่ในตาราง แต่ถูกแยกออกจากการใช้งานตามปกติผ่านเงื่อนไขใน query
ในเชิงการจัดการข้อมูล Hard Delete เหมาะสำหรับกรณีที่ข้อมูลหมดความสำคัญ ไม่มีความจำเป็นในเชิงประวัติ และไม่เกี่ยวข้องกับความสัมพันธ์เชิงอ้างอิงของตารางอื่น ขณะที่ Soft Delete เหมาะกับสถานการณ์ที่ข้อมูลยังมีคุณค่าในด้านการตรวจสอบย้อนหลัง การอ้างอิงกับธุรกรรมเดิม หรือการกลับมาเปิดใช้งานในภายหลัง ดังนั้น การตัดสินใจเลือกระหว่างสองแนวทางนี้จึงไม่ใช่เพียงประเด็นทางเทคนิค แต่ยังเป็นประเด็นด้านการควบคุมภายใน ความเสี่ยง และการกำกับดูแลข้อมูลด้วย
ขอเริ่มจากตัวอย่างการทำ Soft Delete โดยสมมติสถานการณ์ว่า ทีมผลิตภัณฑ์ยืนยันว่า Osmanthus Milk Tea (SKU 9 และ 10) ถูกยกเลิกหลังสัปดาห์ที่ 4 ก่อนตัดสินใจลบหรือปิดการใช้งาน ให้ตรวจสอบว่ามีออเดอร์ประวัติที่อ้างอิง SKU เหล่านี้หรือไม่
การตรวจสอบข้อมูลก่อน DELETE
| sku_id | sku_name | order_lines |
|---|---|---|
| 9 | Osmanthus Milk Tea R | 1383 |
| 10 | Osmanthus Milk Tea L | 3432 |
สอง SKU ของ Osmanthus ปรากฏใน 4,815 order lines ประวัติ ถ้า hard-delete SKU 9 และ 10 ออกจาก menus แถว order_items ทั้งหมดนี้จะอ้างอิง sku_id ที่ไม่มีอยู่ รายงานรายได้ การวิเคราะห์ความสามารถในการทำกำไร และการแจกแจง product family จะเพี้ยนโดยเงียบ ๆ
ทำ soft delete ผ่าน active_flag
แทนที่จะลบแถวออก ให้เพิ่มคอลัมน์ active_flag ในตาราง menus และทำเครื่องหมาย SKU ที่ถูกยกเลิกว่าไม่ active
ยืนยันผลลัพธ์ soft delete:
| active_flag | sku_count |
|---|---|
| 1 | 20 |
| 0 | 2 |
แสดงเมนูที่ถูกยกเลิกจำนวน 2 เมนู WHERE active_flag = 0
แสดงเมนูที่ถูกยกเลิกจำนวน 2 เมนู
| sku_id | sku_name | list_price_thb | active_flag |
|---|---|---|---|
| 9 | Osmanthus Milk Tea R | 95 | 0 |
| 10 | Osmanthus Milk Tea L | 115 | 0 |
แสดงเมนูที่ถูกยกเลิกจำนวน 2 เมนู WHERE active_flag = 1
| sku_id | sku_name | list_price_thb | active_flag |
|---|---|---|---|
| 22 | Thai Tea Frappe L | 125 | 1 |
| 17 | Peach Jasmine Tea L | 95 | 1 |
| 15 | Jasmine Grapefruit Tea L | 105 | 1 |
| 16 | Lemon Glutinous Green Tea L | 105 | 1 |
| 21 | Mango Sticky Rice Tea L | 135 | 1 |
| 18 | Fresh Orange Jasmine Tea L | 150 | 1 |
| 1 | Glutinous Green Milk Tea R | 85 | 1 |
| 7 | Pu’er Milk Tea R | 95 | 1 |
| 11 | Jasmine Milk Tea R | 95 | 1 |
| 13 | White Peach Milk Tea R | 95 | 1 |
| 2 | Glutinous Green Milk Tea L | 100 | 1 |
| 3 | Tie Guan Yin Milk Tea R | 110 | 1 |
| 5 | Da Hong Pao Milk Tea R | 110 | 1 |
| 8 | Pu’er Milk Tea L | 115 | 1 |
| 12 | Jasmine Milk Tea L | 115 | 1 |
| 14 | White Peach Milk Tea L | 115 | 1 |
| 4 | Tie Guan Yin Milk Tea L | 130 | 1 |
| 6 | Da Hong Pao Milk Tea L | 130 | 1 |
| 20 | Glutinous Green Pure Tea R | 85 | 1 |
| 19 | Tie Guan Yin Pure Tea R | 110 | 1 |
วิธีนี้ทำให้ 20 SKU ที่ยัง active ปรากฏในรายงานการดำเนินงานทั้งหมด ในขณะที่แถว Osmanthus ที่ถูกยกเลิกยังคงอยู่ในตารางสำหรับ query ประวัติและไม่ทำให้ความสัมพันธ์ foreign key เสียหาย
หาก PiCha hard-delete SKU ที่ถูกยกเลิก การวิเคราะห์เปรียบเทียบปีต่อปีทุกครั้งจะนับรายได้ต่ำกว่าความเป็นจริงสำหรับช่วงเวลาที่ SKU เหล่านั้นยังขายอยู่ เช่น เดือนไหนที่ Osmanthus เคยขายดีจะดูแย่ลงอย่างผิดจริง Soft delete จึงเป็นแนวปฏิบัติมาตรฐานของอุตสาหกรรมสำหรับสถานการณ์แบบนี้
Hard Delete เหมาะสมเมื่อใด: การลบระเบียนทดสอบ
Hard Delete เหมาะสำหรับกรณีที่ระเบียนถูกสร้างขึ้นโดยไม่ตั้งใจ และไม่ได้ถูกอ้างอิงหรือใช้งานต่อในระบบ downstream ตัวอย่างเช่น หากมีการ INSERT SKU ทดสอบที่มี sku_id = 99 และ sku_name = 'TEST ITEM DO NOT USE' เข้าไปในฐานข้อมูล production โดยผิดพลาด และระเบียนนั้นไม่เคยปรากฏในธุรกรรมการขายจริง การลบออกอย่างถาวรด้วย DELETE ก็ถือเป็นแนวทางที่เหมาะสม
ไวยกรณ์พื้นฐานของ DELETE
หากไม่มี WHERE ทุกแถวในตารางจะถูกลบ แต่โครงสร้างตารางยังอยู่ หากต้องการลบทั้งโครงสร้างและข้อมูล ให้ใช้ DROP TABLE
เริ่มต้นจากการตรวจสอบแถวข้อมูลที่ต้องการลบ ก่อน DELETE
| sku_id | sku_name | order_lines |
|---|---|---|
| 99 | TEST ITEM DO NOT USE | 0 |
ไม่มี order lines จึงปลอดภัยที่จะลบ
SQL Hard DELETE:
ยืนยันหลัง DELETE:
| test_row_count |
|---|
| 0 |
การทำ Hard Delete ถือว่าปลอดภัยได้เฉพาะในกรณีที่เงื่อนไขสองข้อเป็นจริงพร้อมกัน ได้แก่ แถวนั้นเป็นข้อมูลที่เกิดขึ้นจากความผิดพลาดหรือไม่มีคุณค่าในเชิงประวัติแล้ว และไม่ถูกอ้างอิงโดยตารางอื่นในระบบ ด้วยเหตุนี้ การตรวจสอบทั้งสองประเด็นด้วย LEFT JOIN ก่อนทำ Hard Delete ทุกครั้งจึงควรเป็นมาตรฐานในการปฏิบัติ
สรุป
บทนี้ได้แนะนำคำสั่งพื้นฐานสำหรับการเพิ่ม แก้ไข และลบข้อมูลในฐานข้อมูล ซึ่งเป็นทักษะสำคัญในการดูแลข้อมูลให้ถูกต้อง ทันสมัย และพร้อมใช้งานทางธุรกิจ ไม่ว่าจะเป็นการเพิ่มสินค้าใหม่ การปรับราคา การยกเลิกเมนู หรือการลบข้อมูลที่ไม่ต้องการใช้อีกต่อไป
ตารางต่อไปนี้สรุปแนวคิดสำคัญ syntax ที่ใช้บ่อย และตัวอย่างการประยุกต์ใช้ในบริบทของธุรกิจ
| แนวคิด | Syntax | การใช้งานทางธุรกิจ |
|---|---|---|
| INSERT แถวเดียว | INSERT INTO t (cols) VALUES (vals) |
เพิ่ม SKU ใหม่เข้าสู่เมนู |
| INSERT หลายแถว | INSERT INTO t (cols) VALUES (r1), (r2) |
เปิดตัว SKU หลายรายการพร้อมกัน |
INSERT ... SELECT |
INSERT INTO t SELECT ... FROM s |
ย้ายหรือ archive ข้อมูลสินค้า seasonal จากอีกตารางหนึ่ง |
UPDATE ... WHERE |
UPDATE t SET col = val WHERE condition |
ปรับราคาเครื่องดื่ม seasonal หรือแก้ไขข้อมูลเฉพาะบางรายการ |
CASE ใน SET |
SET col = CASE WHEN ... END |
กำหนดราคา premium หรือเงื่อนไขแบบ tier ตาม family ของสินค้า |
| Soft delete | UPDATE t SET active_flag = 0 WHERE ... |
SKU ที่ยกเลิกขายโดยไม่ลบข้อมูลออกจริง |
| Hard delete | DELETE FROM t WHERE condition |
ลบข้อมูลทดสอบหรือระเบียนที่บันทึกผิดพลาดออกจากระบบ |
เมื่อมองภาพรวม คำสั่งเหล่านี้ช่วยให้เราจัดการข้อมูลได้ครบทั้งวงจร ตั้งแต่การเพิ่มข้อมูลใหม่ การปรับปรุงข้อมูลเดิม ไปจนถึงการนำข้อมูลที่ไม่ต้องการออกจากระบบ การเลือกใช้คำสั่งให้เหมาะกับสถานการณ์จึงมีความสำคัญมาก โดยเฉพาะการแยกให้ชัดว่าเมื่อใดควรใช้การลบแบบชั่วคราว และเมื่อใดจึงเหมาะกับการลบออกจริง
คำถามท้ายบท
- INSERT แถวเดียว: เพิ่ม SKU pure tea ใหม่ลงในเมนู เลือก
sku_id,sku_name,tea_baseและlist_price_thbที่เหมาะสมในช่วง 90–120 บาท รัน pre-flight check ดำเนินการINSERTแล้วยืนยันด้วยSELECT
คลิกเพื่อดูเฉลย
-- Pre-flight check: ดู pure_tea ที่มีอยู่และ max sku_id
SELECT sku_id, sku_name, tea_base, list_price_thb
FROM menus
WHERE family = 'pure_tea'
ORDER BY sku_id;
SELECT MAX(sku_id) AS next_id FROM menus; -- ได้ 22 → ใช้ 23
-- INSERT
INSERT INTO menus (
sku_id, sku_name, family, tea_base,
milk_flag, fruit_flag, cold_brew_flag, blended_flag,
size_code, size_ml, list_price_thb,
sweetness_min_pct, sweetness_max_pct,
caffeine_level, seasonal_flag, signature_flag, launch_branch_flag
) VALUES (
23, 'Da Hong Pao Pure Tea R', 'pure_tea', 'da_hong_pao',
0, 0, 0, 0,
'R', 500, 110,
0, 100,
'high', 0, 0, 0
);
-- Verification
SELECT sku_id, sku_name, family, tea_base, list_price_thb
FROM menus
WHERE sku_id = 23;- UPDATE พร้อม WHERE: ทีม finance ต้องการเพิ่มค่าบรรจุภัณฑ์ 2 บาทสำหรับออเดอร์ delivery ทั้งหมดโดยการเพิ่มราคาเมนูของเครื่องดื่ม blended ขึ้น 2 บาท เขียน
UPDATEและรวมทั้ง pre-flightSELECTและ post-flight verification แบบGROUP BY
คลิกเพื่อดูเฉลย
-- Pre-flight: ดู blended SKU ที่จะได้รับผลกระทบ
SELECT sku_id, sku_name, list_price_thb, list_price_thb + 2 AS new_price
FROM menus
WHERE family = 'blended';
-- Thai Tea Frappe L: 125 → 127, สั่งผ่าน delivery 427 ครั้ง
-- UPDATE
UPDATE menus
SET list_price_thb = list_price_thb + 2
WHERE family = 'blended';
-- Post-flight verification
SELECT family, COUNT(*) AS sku_count,
MIN(list_price_thb) AS min_price,
MAX(list_price_thb) AS max_price
FROM menus
GROUP BY family
ORDER BY family;- Soft delete: โดยใช้รูปแบบ
active_flagเขียน SQL เพื่อปิดการใช้งาน SKU ทั้งหมดในหมวดpure_teaก่อนดำเนินการ รันLEFT JOINcheck เพื่อวัดจำนวนแถวในorder_itemsที่อ้างอิง pure tea SKU
คลิกเพื่อดูเฉลย
-- เพิ่มคอลัมน์ active_flag ก่อน (ถ้ายังไม่มี)
ALTER TABLE menus ADD COLUMN active_flag INTEGER DEFAULT 1;
-- Pre-flight: LEFT JOIN check วัดผลกระทบ
SELECT
m.sku_id,
m.sku_name,
COUNT(oi.sku_id) AS order_items_affected
FROM menus m
LEFT JOIN order_items oi ON m.sku_id = oi.sku_id
WHERE m.family = 'pure_tea'
GROUP BY m.sku_id, m.sku_name;
-- Tie Guan Yin Pure Tea R: 1,331 แถว
-- Glutinous Green Pure Tea R: 1,362 แถว → รวม 2,693 แถว historical
-- Soft delete (ไม่ลบข้อมูลจริง)
UPDATE menus
SET active_flag = 0
WHERE family = 'pure_tea';
-- Verification
SELECT sku_id, sku_name, family, active_flag
FROM menus
WHERE family = 'pure_tea';INSERT … SELECTarchive: สร้างตารางdiscontinued_skusและเติมข้อมูลด้วย SKU ทั้งหมดที่มีseasonal_flag = 1โดยใช้INSERT … SELECTเพิ่มคอลัมน์discontinued_dateที่มีวันที่วันนี้ ยืนยันจำนวน archive
คลิกเพื่อดูเฉลย
-- สร้างตาราง discontinued_skus สำหรับเก็บเมนูที่เลิกขาย โดยคัดลอก schema จาก menus
CREATE TABLE discontinued_skus AS
SELECT
sku_id, -- รหัสสินค้า
sku_name, -- ชื่อสินค้า
family, -- กลุ่มสินค้า
tea_base, -- ชาหลักที่ใช้
list_price_thb, -- ราคาขาย (บาท)
seasonal_flag, -- flag เมนูตามฤดูกาล
CURRENT_DATE AS discontinued_date -- วันที่ปลดระวาง
FROM menus
WHERE 1 = 0; -- คัดลอกเฉพาะ schema ไม่มีแถวข้อมูล (เงื่อนไขเป็นเท็จเสมอ)
-- ย้ายเมนู seasonal ทั้งหมดไปยังตาราง discontinued_skus
INSERT INTO discontinued_skus
SELECT
sku_id, -- รหัสสินค้า
sku_name, -- ชื่อสินค้า
family, -- กลุ่มสินค้า
tea_base, -- ชาหลักที่ใช้
list_price_thb, -- ราคาขาย (บาท)
seasonal_flag, -- flag เมนูตามฤดูกาล
CURRENT_DATE AS discontinued_date -- วันที่ปลดระวาง
FROM menus
WHERE seasonal_flag = 1; -- กรองเฉพาะเมนู seasonal
-- Verification
SELECT COUNT(*) AS archived_count FROM discontinued_skus;
-- ได้ 3 แถว: Fresh Orange Jasmine Tea L, Mango Sticky Rice Tea L, Thai Tea Frappe L
SELECT * FROM discontinued_skus;- การตัดสินใจ DELETE: stakeholder ขอให้คุณลบ transaction ทั้งหมดที่มี
satisfaction_score = 1ออกจากตารางtransactionsโดยอ้างว่า “นี่คือข้อมูลที่ไม่ดีอย่างชัดเจน” เขียน pre-flight query เพื่อประเมินว่านี่ส่งผลกระทบต่อกี่แถว คิดเป็นสัดส่วนเท่าไรของ transaction ทั้งหมด และแถวเหล่านี้มาจากสาขาไหนบ้าง จากนั้นเขียนคำแนะนำ 3 ประโยคว่าควรดำเนินการDELETEหรือไม่ พร้อมอธิบายเหตุผล
คลิกเพื่อดูเฉลย
-- Pre-flight: ประเมินขนาดผลกระทบ
SELECT
COUNT(*) AS low_score_rows,
(SELECT COUNT(*) FROM transactions) AS total_rows,
ROUND(100.0 * COUNT(*) /
(SELECT COUNT(*) FROM transactions), 2) AS pct_of_total
FROM transactions
WHERE satisfaction_score = 1;
-- 13 แถว จาก 53,323 = 0.02%
-- Pre-flight: แยกตามสาขา
SELECT
s.store_name,
COUNT(*) AS cnt,
MIN(t.order_date) AS earliest,
MAX(t.order_date) AS latest
FROM transactions t
JOIN stores s ON t.store_id = s.store_id
WHERE t.satisfaction_score = 1
GROUP BY s.store_name
ORDER BY cnt DESC;
-- Exchange Tower: 10 แถว, Park Silom: 3 แถวคำแนะนำ: ไม่ควร DELETE
คะแนน 1 ดาวจาก 13 แถว (0.02%) คือ ข้อมูลที่ถูกต้อง ไม่ใช่ข้อผิดพลาด — ลูกค้าที่ไม่พอใจมีอยู่จริงและเป็นสัญญาณสำคัญที่ควรวิเคราะห์ การลบออกจะทำให้ Exchange Tower ดูดีกว่าความเป็นจริง (10 จาก 13 แถวมาจากสาขานี้) ซึ่งอาจทำให้ทีมบริหารมองข้ามปัญหาด้านคุณภาพบริการ หากต้องการแยกข้อมูลออกจากการวิเคราะห์บางส่วน ให้ใช้WHERE satisfaction_score > 1 ใน query แทน ไม่ใช่การลบถาวร