3 นาที

6 JOIN ใน SQL ที่คุณควรรู้ (พร้อมตัวอย่างชัดเจนและเข้าใจง่าย)

เรียนรู้ 6 ประเภท JOIN ใน SQL ที่นักวิเคราะห์ควรรู้—INNER, LEFT, RIGHT, FULL OUTER, CROSS และ SELF—พร้อมตัวอย่างใช้งานและข้อควรระวัง

6 JOIN ใน SQL ที่คุณควรรู้ (พร้อมตัวอย่างชัดเจนและเข้าใจง่าย)

JOIN ใน SQL คืออะไร และทำไมต้องใช้

SQL JOIN ช่วยให้คุณรวมแถวจากสองตาราง (หรือมากกว่า) เป็นผลลัพธ์เดียวด้วยการจับคู่คอลัมน์ที่เกี่ยวข้อง—โดยปกติจะเป็น ID

ทำไม JOIN สำคัญ

ฐานข้อมูลจริงมักจะแยกข้อมูลเป็นหลายตารางเพื่อไม่ให้ซ้ำซ้อน เช่น ชื่อของลูกค้าเก็บในตาราง customers ขณะที่การสั่งซื้อเก็บในตาราง orders JOIN คือวิธีที่คุณเชื่อมข้อมูลเหล่านั้นกลับเมื่อคุณต้องการคำตอบ

ดังนั้น JOIN จึงถูกใช้ทั่วในการรายงานและการวิเคราะห์:

  • สร้างรายงานยอดขายที่มีชื่อลูกค้า ยอดคำสั่งซื้อ และสถานะการชำระเงิน
  • หา ลูกค้าที่ยังไม่เคยสั่งซื้อ
  • ตรวจสอบความไม่ตรงกัน เช่น คำสั่งซื้อที่ไม่มีการชำระเงิน
  • สร้างสรุป “หนึ่งแถวต่อหนึ่งลูกค้า” จากหลายแถวที่เกี่ยวข้อง

ถ้าไม่มี JOIN คุณจะต้องรันคำสั่งแยกแล้วรวมผลด้วยมือ—ช้า เสี่ยงผิดพลาด และทำซ้ำยาก

ถ้าคุณสร้างผลิตภัณฑ์บนฐานข้อมูลเชิงสัมพันธ์ (แดชบอร์ด พื้นที่แอดมิน เครื่องมือภายใน พอร์ทัลลูกค้า) JOIN คือสิ่งที่เปลี่ยน "ตารางดิบ" ให้เป็นมุมมองที่ผู้ใช้เห็นได้ แพลตฟอร์มอย่าง Koder.ai (ที่สร้างแอป React + Go + PostgreSQL จากการคุย) ยังคงต้องพึ่งพื้นฐาน JOIN ที่ดีเมื่อต้องการหน้ารายการ รายงาน และหน้าตรวจสอบ เพราะตรรกะฐานข้อมูลยังคงสำคัญ แม้ว่าการพัฒนาจะเร็วขึ้น

6 ประเภท JOIN ที่คุณควรรู้

คู่มือนี้เน้น JOIN หกแบบที่ครอบคลุมงาน SQL ประจำวันได้ส่วนใหญ่:

  • INNER JOIN: คืนเฉพาะแถวที่จับคู่กันทั้งสองตาราง (เหมาะกับ “แสดงความสัมพันธ์ที่ยืนยันแล้ว”)
  • LEFT JOIN: เก็บแถวทั้งหมดจากตารางซ้าย และดึงข้อมูลจากขวาเมื่อมีการจับคู่ (เหมาะกับ “รวมข้อมูลที่อาจจะหายไป”)
  • RIGHT JOIN: กระจกของ LEFT JOIN (ไม่ค่อยใช้ แต่มีประโยชน์ตามสไตล์การเขียน)
  • FULL OUTER JOIN: เก็บทุกแถวจากทั้งสองฝั่งและจับคู่เมื่อเป็นไปได้ (ดีสำหรับการไกล่เกลี่ย)
  • CROSS JOIN: สร้างทุกชุดคู่ของแถว (ใช้สร้างปฏิทิน สถานการณ์ หรือข้อมูลทดสอบ—แต่ใช้ผิดง่าย)
  • SELF JOIN: ต่อ таблица กับตัวเอง (สะดวกกับโครงสร้างลำดับชั้นเช่น พนักงาน/ผู้จัดการ)

หมายเหตุสั้นๆ เกี่ยวกับซินแทกซ์

รูปแบบ JOIN คล้ายกันในฐานข้อมูลส่วนใหญ่ (PostgreSQL, MySQL, SQL Server, SQLite) มีความแตกต่างเล็กน้อย—โดยเฉพาะการสนับสนุน FULL OUTER JOIN และพฤติกรรมขอบกรณี—แต่แนวคิดและรูปแบบหลักนำไปใช้ได้ข้ามระบบ

ตารางตัวอย่างที่ใช้ (customers, orders, payments)

เพื่อให้ตัวอย่าง JOIN เรียบง่าย เราจะใช้สามตารางขนาดเล็กที่สะท้อนการตั้งค่าจริง: ลูกค้าสร้างคำสั่งซื้อ และคำสั่งซื้ออาจ (หรืออาจจะไม่) มีการชำระเงิน

โน้ตสั้นก่อนเริ่ม: ตารางตัวอย่างด้านล่างแสดงเฉพาะบางคอลัมน์ แต่ตัวอย่างบางคำสั่งจะอ้างถึงฟิลด์เพิ่มเติม (เช่น order_date, created_at, status, หรือ paid_at) ให้ถือว่าเป็นคอลัมน์ “ทั่วไป” ที่มักมีในสคีมาของการใช้งานจริง

1) customers

Primary key: customer_id

customer_idname
1Ava
2Ben
3Chen
4Dia

2) orders

Primary key: order_id
Foreign key: customer_idcustomers.customer_id

order_idcustomer_idorder_total
101150
1021120
103235
104570

สังเกตว่า order_id = 104 อ้างอิง customer_id = 5 ซึ่ง ไม่มี ในตาราง customers กรณี "ไม่มีการจับคู่" นี้มีประโยชน์เพื่อดูพฤติกรรมของ LEFT JOIN, RIGHT JOIN, และ FULL OUTER JOIN

3) payments

Primary key: payment_id
Foreign key: order_idorders.order_id

payment_idorder_idamount
900110150
900210260
900310260
900499925

รายละเอียดสำคัญสองข้อสำหรับการสอน:

  • order_id = 102 มี สองแถวการชำระ (แบ่งจ่าย) เมื่อคุณ join orders กับ payments คำสั่งซื้อนั้นจะแสดง สองครั้ง — นี่คือที่มาของการเกิดซ้ำที่มักทำให้คนตกใจ
  • payment_id = 9004 อ้างอิง order_id = 999 ซึ่งไม่มีใน orders นี่คืออีกกรณีของการ “ไม่จับคู่”

คาดหวังอะไรเมื่อ JOIN ตารางเหล่านี้

  • แถวที่จับคู่ได้: เช่น ลูกค้า 1 ↔ คำสั่งซื้อ 101/102; คำสั่งซื้อ 101 ↔ การชำระ 9001
  • แถวที่ไม่จับคู่: เช่น ลูกค้า 3 และ 4 ไม่มีคำสั่งซื้อ; คำสั่งซื้อ 104 ไม่มีลูกค้า; การชำระ 9004 ไม่มีคำสั่งซื้อ
  • การซ้ำ: การ join orders กับ payments จะทำให้คำสั่งซื้อ 102 ซ้ำเพราะมีการชำระสองรายการ

INNER JOIN: เก็บเฉพาะแถวที่จับคู่

INNER JOIN คืนเฉพาะแถวที่มีการจับคู่ใน ทั้งสอง ตาราง ถ้าลูกค้าไม่มีคำสั่งซื้อ เขาจะไม่ปรากฏ ถ้าคำสั่งซื้ออ้างอิงลูกค้าที่ไม่มี (ข้อมูลไม่ดี) คำสั่งซื้อจะไม่ปรากฏเช่นกัน

รูปแบบพื้นฐาน

คุณเลือกตาราง “ซ้าย” แล้ว join กับตาราง “ขวา” โดยระบุเงื่อนไขใน ON:

SELECT
  c.customer_id,
  c.name,
  o.order_id,
  o.order_date
FROM customers c
INNER JOIN orders o
  ON o.customer_id = c.customer_id;

แกนหลักคือบรรทัด ON o.customer_id = c.customer_id: บอก SQL ว่าแถวใดเกี่ยวข้องกัน

กรณีใช้งานจริง: ลูกค้าที่เคยสั่งซื้อ

ถ้าต้องการรายการเฉพาะลูกค้าที่เคยสั่งซื้อจริงๆ พร้อมรายละเอียดคำสั่งซื้อ INNER JOIN คือทางเลือกตามธรรมชาติ:

SELECT
  c.name,
  o.order_id,
  o.total_amount
FROM customers c
INNER JOIN orders o
  ON o.customer_id = c.customer_id
ORDER BY o.order_id;

เป็นประโยชน์สำหรับงานอย่าง “ส่งอีเมลติดตามคำสั่งซื้อ” หรือ “คำนวณรายได้ต่อลูกค้า” เมื่อคุณสนใจเฉพาะลูกค้าที่มีการซื้อ

ความผิดพลาดบ่อย: ลืมเงื่อนไข join

ถ้าคุณเขียน join แต่ลืม ON (หรือ join บนคอลัมน์ผิด) คุณอาจสร้าง Cartesian product (ทุกลูกค้าจับคู่กับทุกคำสั่งซื้อ) หรือได้ผลลัพธ์ผิดพลาดอย่างเงียบๆ

ตัวอย่างไม่ดี (อย่าใช้):

SELECT c.name, o.order_id
FROM customers c
JOIN orders o;

จงใส่เงื่อนไข join ที่ชัดเจนใน ON เสมอ (หรือ USING ในกรณีที่เหมาะสม)

LEFT JOIN: เก็บทุกแถวจากตารางซ้าย

LEFT JOIN คืน แถวทั้งหมดจากตารางซ้าย และเพิ่มข้อมูลจากตารางขวาเมื่อมีการจับคู่ ถ้าไม่มีการจับคู่ คอลัมน์ฝั่งขวาจะเป็น NULL

เมื่อใช้

ใช้ LEFT JOIN เมื่อต้องการรายการสมบูรณ์จากตารางหลัก พร้อมข้อมูลที่เกี่ยวข้องเมื่อมี

ตัวอย่าง: “แสดง ลูกค้าทั้งหมด และรวมคำสั่งซื้อของพวกเขา ถ้ามี

SELECT
  c.customer_id,
  c.name,
  o.order_id,
  o.order_date
FROM customers c
LEFT JOIN orders o
  ON o.customer_id = c.customer_id
ORDER BY c.customer_id;
  • ลูกค้าที่มีคำสั่งซื้อจะปรากฏพร้อมรายละเอียด
  • ลูกค้าที่ไม่มีคำสั่งซื้อยังปรากฏ แต่ o.order_id จะเป็น NULL

หาแถวที่ “ไม่มีการจับคู่” (รูปแบบคลาสสิก)

ตัวอย่าง: “ลูกค้าคนไหน ไม่เคยสั่งซื้อ บ้าง?”

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

เงื่อนไข WHERE ... IS NULL เก็บเฉพาะแถวจากตารางซ้ายที่การ join ไม่พบการจับคู่

ระวัง: การจับคู่หลายรายการทำให้แถวทวีคูณ

LEFT JOIN อาจทำให้แถวของตารางซ้าย “ซ้ำ” เมื่อฝั่งขวามีหลายแถวที่จับคู่

ถ้าลูกค้าคนหนึ่งมี 3 คำสั่งซื้อ ลูกค้าคนนั้นจะปรากฏ 3 ครั้ง—ครั้งละหนึ่งคำสั่งซื้อ นี่เป็นพฤติกรรมปกติ แต่จะทำให้สับสนหากคุณพยายามนับลูกค้า

ตัวอย่างการนับที่อาจผิด:

SELECT COUNT(*)
FROM customers c
LEFT JOIN orders o
  ON o.customer_id = c.customer_id;

ถ้าต้องการนับลูกค้า ควรนับคีย์ของลูกค้า (เช่น COUNT(DISTINCT c.customer_id)) ขึ้นกับสิ่งที่คุณวัด

RIGHT JOIN: เก็บทุกแถวจากตารางขวา

RIGHT JOIN เก็บ แถวทั้งหมดจากตารางขวา และเฉพาะแถวที่จับคู่จากตารางซ้าย หากไม่มีการจับคู่ คอลัมน์ฝั่งซ้ายจะเป็น NULL มันเหมือนภาพสะท้อนของ LEFT JOIN

ตัวอย่างง่ายๆ

สมมติอยากแสดง การชำระทั้งหมด แม้ว่าจะจับกับคำสั่งซื้อไม่ได้ (อาจเพราะคำสั่งซื้อถูกลบหรือข้อมูลการชำระไม่สะอาด)

SELECT
  o.order_id,
  o.customer_id,
  p.payment_id,
  p.amount,
  p.paid_at
FROM orders o
RIGHT JOIN payments p
  ON o.order_id = p.order_id;

ผลลัพธ์:

  • การชำระทั้งหมด ถูกใส่เข้ามา (เพราะ payments อยู่ฝั่งขวา)
  • ถ้าการชำระไม่มีคำสั่งซื้อที่ตรงกัน o.order_id และ o.customer_id จะเป็น NULL

คำสั่งเดียวกันเขียนเป็น LEFT JOIN (มักชอบแบบนี้มากกว่า)

บ่อยครั้งคุณสามารถเขียน RIGHT JOIN ให้เป็น LEFT JOIN โดยสลับลำดับตาราง:

SELECT
  o.order_id,
  o.customer_id,
  p.payment_id,
  p.amount,
  p.paid_at
FROM payments p
LEFT JOIN orders o
  ON o.order_id = p.order_id;

จะได้ผลเหมือนกัน แต่มักอ่านง่ายกว่าเพราะเริ่มจากตารางหลักที่คุณสนใจ (ตรงนี้คือ payments)

ความอ่านง่าย: ทำไมหลายทีมหลีกเลี่ยง RIGHT JOIN

แนวทางการเขียน SQL ส่วนใหญ่ขอให้เริ่มจากตารางหลักแล้ว LEFT JOIN ตารางเพิ่มเติม เมื่อความสัมพันธ์ optional เขียนเป็น LEFT JOIN เสมอ คิวรีจะอ่านง่ายขึ้น

เมื่อ RIGHT JOIN ยังมีประโยชน์

RIGHT JOIN อาจสะดวกเมื่อคุณแก้คิวรีเดิมและพบว่าตารางที่ต้องเก็บไว้ดันอยู่ฝั่งขวา แทนที่จะเขียนคิวรียาวใหม่ทั้งหมด การเปลี่ยน JOIN หนึ่งบรรทัดเป็น RIGHT JOIN อาจเร็วและเสี่ยงน้อย

FULL OUTER JOIN: เก็บทุกแถวจากทั้งสองตาราง

สร้างแผงผู้ดูแลอย่างรวดเร็ว
สร้างหน้าจอ CRUD ด้วย React, Go และ PostgreSQL อย่างรวดเร็ว แล้วปรับแต่งคำสั่งได้ตามต้องการ

FULL OUTER JOIN คืน ทุกแถวจากทั้งสองตาราง

  • ถ้าแถวจับคู่กัน จะได้แถวเดียวรวมกัน (เหมือน INNER JOIN)
  • ถ้าแถวมีแค่ฝั่งซ้าย จะยังปรากฏ โดยคอลัมน์ฝั่งขวาจะเป็น NULL
  • ถ้าแถวมีแค่ฝั่งขวา จะยังปรากฏ โดยคอลัมน์ฝั่งซ้ายจะเป็น NULL

เมื่อต้องการใช้

กรณีธุรกิจคลาสสิกคือ ไกล่เกลี่ย orders กับ payments:

  • ดูคำสั่งซื้อที่ชำระแล้ว (จับคู่)
  • คำสั่งซื้อที่ยังไม่ชำระ (order มี แต่ payment หาย)
  • การชำระที่ไร้คำสั่งซื้อ (payment มี แต่ order ไม่มี)

ตัวอย่าง:

SELECT
  o.order_id,
  o.customer_id,
  p.payment_id,
  p.amount
FROM orders o
FULL OUTER JOIN payments p
  ON p.order_id = o.order_id;

ฐานข้อมูลที่รองรับ

FULL OUTER JOIN รองรับใน PostgreSQL, SQL Server, และ Oracle

มัน ไม่รองรับ ใน MySQL และ SQLite (ต้องใช้วิธีแก้ไข)

ทางเลือกที่พกพาได้: UNION ของ LEFT JOIN และ RIGHT JOIN

ถ้าฐานข้อมูลไม่รองรับ FULL OUTER JOIN คุณสามารถจำลองได้โดยรวม:

  1. แถวทั้งหมดจาก orders (พร้อมการจับคู่เมื่อมี)
  2. แถวทั้งหมดจาก payments ที่ไม่จับกับ orders

รูปแบบหนึ่งที่พบบ่อย:

SELECT o.order_id, o.customer_id, p.payment_id, p.amount
FROM orders o
LEFT JOIN payments p
  ON p.order_id = o.order_id

UNION

SELECT o.order_id, o.customer_id, p.payment_id, p.amount
FROM orders o
RIGHT JOIN payments p
  ON p.order_id = o.order_id;

เคล็ดลับ: เมื่อเห็น NULL ฝั่งใดฝั่งหนึ่ง นั่นคือสัญญาณว่าข้อมูลจากอีกฝั่ง “หายไป” ซึ่งต้องการสำหรับการตรวจสอบและไกล่เกลี่ย

CROSS JOIN: สร้างทุกการจับคู่ (ใช้อย่างระมัดระวัง)

CROSS JOIN คืน ทุกการจับคู่ที่เป็นไปได้ ของแถวจากสองตาราง ถ้าตาราง A มี 3 แถว และตาราง B มี 4 แถว ผลลัพธ์จะมี 3 × 4 = 12 แถว เรียกว่า Cartesian product

ฟังดูน่ากลัว—และเป็นได้—แต่มีประโยชน์เมื่อต้องการสร้างการผสมผสานจริงๆ

ตัวอย่างเล็กๆ ที่ปลอดภัย: ขนาด × สี (สร้าง SKU)

สมมติคุณเก็บตัวเลือกสินค้าแยกตาราง:

  • sizes: S, M, L
  • colors: Red, Blue

CROSS JOIN สร้างตัวแปรทั้งหมด (ใช้สร้าง SKU ล่วงหน้า สร้างแคตตาล็อก หรือทดสอบ):

SELECT
  s.size,
  c.color
FROM sizes AS s
CROSS JOIN colors AS c;

ผลลัพธ์ (3 × 2 = 6 แถว):

  • S / Red
  • S / Blue
  • M / Red
  • M / Blue
  • L / Red
  • L / Blue

คำเตือน: ผลลัพธ์โตเร็ว

เพราะจำนวนแถวถูกคูณ CROSS JOIN อาจขยายเร็วมาก:

  • 10,000 ลูกค้า × 50 ผลิตภัณฑ์ = 500,000 แถว
  • 100,000 × 100,000 = 10,000,000,000 แถว

ซึ่งทำให้คิวรีช้าหลายและใช้หน่วยความจำมาก หากต้องการการผสม ให้จำกัดขนาดตารางนำเข้าและพิจารณาเพิ่ม LIMIT หรือกรองอย่างรอบคอบ

SELF JOIN: ต่อโต๊ะกับตัวเอง

นำ SQL ของคุณสู่ production
ปรับใช้และโฮสต์แอปของคุณโดยไม่ชะลอการทดลอง

SELF JOIN ก็คือการ join ตารางกับตัวมันเอง ใช้เมื่อแถวหนึ่งในตารางสัมพันธ์กับอีกแถวหนึ่งในตารางเดียวกัน—เช่น โครงสร้างลำดับชั้นพนักงาน/ผู้จัดการ

ทำไมต้องมี alias และช่วยยังไง

เพราะใช้ตารางเดียวกันสองครั้ง คุณต้องตั้ง alias ให้แต่ละ "สำเนา" เพื่อให้คิวรีอ่านง่ายและบอก SQL ว่าหมายถึงฝั่งไหน

รูปแบบที่นิยม:

  • e สำหรับ employee
  • m สำหรับ manager

ตัวอย่างปฏิบัติ: พนักงานและชื่อผู้จัดการ

สมมติ employees มี:

  • id
  • name
  • manager_id (ชี้ไปที่ id ของพนักงานคนอื่น)

เพื่อแสดงพนักงานแต่ละคนพร้อมชื่อผู้จัดการ:

SELECT
  e.id,
  e.name AS employee_name,
  m.name AS manager_name
FROM employees e
LEFT JOIN employees m
  ON e.manager_id = m.id;

จัดการพนักงานระดับบนสุด (manager_id เป็น NULL)

คิวรีใช้ LEFT JOIN ไม่ใช่ INNER JOIN เพราะบางคนอาจไม่มีผู้จัดการ (เช่น CEO) ในกรณีเหล่านั้น manager_id มักเป็น NULL และ LEFT JOIN จะยังคงเก็บแถวนั้นไว้พร้อมให้ manager_name เป็น NULL ถ้าใช้ INNER JOIN พนักงานระดับบนจะหายไปเพราะไม่มีแถว manager ที่ตรงกัน

เงื่อนไขการ JOIN: ON vs USING (และทำไมสำคัญ)

JOIN ไม่ได้ "รู" ว่าตารางสองตารางสัมพันธ์กันอย่างไร—คุณต้องบอก โดยใส่เงื่อนไขการจับคู่ ซึ่งควรวางถัดจาก JOIN เพราะเป็นการอธิบายว่าตารางจับคู่กันอย่างไร ไม่ใช่การกรองผลลัพธ์สุดท้าย

ON: ยืดหยุ่นที่สุดและใช้บ่อยสุด

ใช้ ON เมื่อคุณต้องการควบคุมการจับคู่เต็มที่—ชื่อคอลัมน์ต่างกัน หลายเงื่อนไข หรือกฎเพิ่มเติม

SELECT
  c.customer_id,
  c.name,
  o.order_id,
  o.created_at
FROM customers AS c
INNER JOIN orders AS o
  ON o.customer_id = c.customer_id;

ON ยังใช้สำหรับการจับคู่ซับซ้อน (เช่น จับสองคอลัมน์) โดยไม่ทำให้คิวรีสับสน

USING: สั้น แต่ใช้ได้เฉพาะคอลัมน์ชื่อเดียวกัน

บางฐานข้อมูล (เช่น PostgreSQL และ MySQL) รองรับ USING เป็นชอร์ตฮันด์เมื่อทั้งสองตารางมีคอลัมน์ชื่อเดียวกันและต้องการ join บนคอลัมน์นั้น

SELECT
  customer_id,
  name,
  order_id
FROM customers
JOIN orders
USING (customer_id);

ข้อดีอย่างหนึ่ง: USING มักส่งคืนคอลัมน์ customer_id เพียงคอลัมน์เดียวในผลลัพธ์ (ไม่ใช่ 2 คอลัมน์)

หลีกเลี่ยงชื่อคอลัมน์คลุมเครือ: ใส่คำนำตารางเสมอ

เมื่อ join แล้ว ชื่อคอลัมน์มักซ้อนทับกัน (id, created_at, status) ถ้าคุณเขียน SELECT id ฐานข้อมูลอาจโชว์ข้อผิดพลาดว่า "ambiguous column" หรือแย่กว่านั้น คุณอาจอ่านผิด id

ควรใช้ prefix ของตาราง (หรือ alias) เพื่อความชัดเจน:

SELECT c.customer_id, o.order_id
FROM customers AS c
JOIN orders AS o
  ON o.customer_id = c.customer_id;

หลีกเลี่ยง SELECT * ในคิวรีที่มี JOIN

SELECT * จะยุ่งเหยิงเร็วเมื่อ JOIN: คุณดึงคอลัมน์ไม่จำเป็น เสี่ยงชื่อซ้ำ และทำให้มองไม่ออกว่าคิวรีสร้างอะไรขึ้นมา

แทนที่จะใช้ ให้เลือกคอลัมน์ที่ต้องการ ผลลัพธ์จะสะอาด ดูแลรักษาง่าย และมักมีประสิทธิภาพกว่า—โดยเฉพาะเมื่อตารางกว้าง

การกรองข้อมูลที่ JOIN: WHERE vs ON

เมื่อคุณ join ตาราง WHERE และ ON ต่างทำการกรองแต่ในช่วงเวลาที่ต่างกัน

  • ON ตัดสินว่า แถวไหนจะจับคู่ขณะ JOIN
  • WHERE กรอง ผลลัพธ์สุดท้ายหลัง JOIN เสร็จแล้ว

ความต่างของเวลาเกิดเป็นเหตุผลที่คนเปลี่ยน LEFT JOIN เป็น INNER JOIN โดยไม่ตั้งใจ

ตัวอย่าง WHERE ทำให้ LEFT JOIN หาย

ถ้าคุณต้องการ ลูกค้าทั้งหมด แม้ไม่มีคำสั่งซื้อที่ชำระล่าสุด:

SELECT c.customer_id, c.name, o.order_id, o.status, o.order_date
FROM customers c
LEFT JOIN orders o
  ON o.customer_id = c.customer_id
WHERE o.status = 'PAID'
  AND o.order_date >= DATE '2025-01-01';

ปัญหา: สำหรับลูกค้าที่ไม่มีคำสั่งซื้อ o.status และ o.order_date เป็น NULL เงื่อนไข WHERE จะตัดแถวนั้นทิ้ง ทำให้ LEFT JOIN ทำงานเหมือน INNER JOIN

ย้ายเงื่อนไขที่เกี่ยวกับการจับคู่ไปใส่ใน ON เพื่อเก็บแถวที่ไม่จับคู่

SELECT c.customer_id, c.name, o.order_id, o.status, o.order_date
FROM customers c
LEFT JOIN orders o
  ON o.customer_id = c.customer_id
 AND o.status = 'PAID'
 AND o.order_date >= DATE '2025-01-01';

ตอนนี้ลูกค้าที่ไม่มีคำสั่งซื้อที่เข้าเงื่อนไขยังปรากฏ (พร้อมคอลัมน์คำสั่งซื้อเป็น NULL) ซึ่งมักเป็นจุดประสงค์ของ LEFT JOIN

เช็คลิสต์ด่วน: อะไรควรอยู่ใน ON vs WHERE?

  • ใส่เงื่อนไขใน ON เมื่อมันอธิบายว่า แถวฝั่งขวาที่อนุญาตให้จับคู่เป็นอย่างไร (เช่น กรองสถานะ/วันที่ของตารางที่ถูก join)
  • ใส่เงื่อนไขใน WHERE เมื่อมันอธิบายว่า ผลลัพธ์สุดท้ายต้องการเก็บแถวแบบใด (เช่น กรองตารางซ้าย หรือกรองหลังจากต้องการให้จับคู่แล้ว)
  • ถ้าคุณต้องการ “เก็บแถวซ้ายทั้งหมด แต่จำกัดแถวขวาที่จะจับคู่” ให้ใช้ LEFT JOIN + ใส่เงื่อนไขใน ON
  • ถ้าคุณต้องการแถวที่มีการจับคู่จริงๆ ให้ใช้ INNER JOIN (หรือ WHERE o.order_id IS NOT NULL อย่างชัดเจน)

หลีกเลี่ยงแถวซ้ำและกับดัก many-to-many

สร้างแดชบอร์ดรายงาน
เปลี่ยนตารางของคุณให้เป็นหน้ารายการ ตัวกรอง และรายงาน ผ่านการคุยโต้ตอบ

JOIN ไม่ได้แค่ “เพิ่มคอลัมน์”—มันอาจ เพิ่มจำนวนแถว ซึ่งเป็นพฤติกรรมที่ถูกต้องแต่ทำให้คนสับสนเมื่อตัวเลขรวมเปลี่ยนไปอย่างไม่คาดคิด

ทำไมแถวถึงทวีคูณ

การ join คืนหนึ่งแถวผลลัพธ์สำหรับทุกคู่แถวที่จับคู่กัน

  • One-to-many: ลูกค้าหนึ่งคนมีหลายคำสั่งซื้อ ถ้า join customers กับ orders ลูกค้าแต่ละคนอาจปรากฏหลายครั้ง
  • Many-to-many: ถ้าคุณ join orders กับ payments แล้วแต่ละคำสั่งซื้อมีหลายการชำระ และคุณยัง join กับตาราง "many" อีก (เช่น order_items) คุณอาจได้ผลคูณระหว่าง payments × items

สรุปก่อน join (pre-aggregate)

ถ้าจุดประสงค์คือ “หนึ่งแถวต่อคำสั่งซื้อ” หรือ “หนึ่งแถวต่อลูกค้า” ให้สรุปฝั่งที่เป็น many ก่อนแล้วค่อย join

-- One row per order from payments
WITH payment_totals AS (
  SELECT
    order_id,
    SUM(amount) AS total_paid,
    COUNT(*)   AS payment_count
  FROM payments
  GROUP BY order_id
)
SELECT
  o.order_id,
  o.customer_id,
  COALESCE(pt.total_paid, 0) AS total_paid,
  COALESCE(pt.payment_count, 0) AS payment_count
FROM orders o
LEFT JOIN payment_totals pt
  ON pt.order_id = o.order_id;

จะช่วยให้รูปร่างของการ join คาดเดาได้: หนึ่งคำสั่งซื้อคงเป็นหนึ่งแถว

DISTINCT เป็นทางเลือกสุดท้าย

SELECT DISTINCT อาจแก้ปัญหาแถวซ้ำได้แต่ซ่อนสาเหตุจริง:

  • อาจคัดแถวที่ถูกต้องทิ้ง
  • อาจซ่อนเงื่อนไข join ผิดพลาด
  • อาจทำให้ผลรวมผิดพลาด (โดยเฉพาะ SUM และ COUNT)

ใช้เมื่อเข้าใจสาเหตุของการซ้ำแน่ชัดเท่านั้น

เช็ครวดเร็ว: ตรวจสอบจำนวนแถว

ก่อนเชื่อผลลัพธ์ ให้เปรียบเทียบจำนวนแถว:

  • นับแถวในตารางหลัก (เช่น orders)
  • นับแถวหลัง join
  • ถ้าจำนวนเพิ่มขึ้นไม่คาดคิด ให้ตรวจสอบคีย์ที่ทำให้จับคู่หลายครั้ง และตัดสินใจว่าจะสรุปก่อนหรือเปลี่ยนเส้นทางการ join

พื้นฐานประสิทธิภาพและชีทสรุป JOIN

JOIN มักถูกตำหนิว่าเป็นสาเหตุคิวรีช้า แต่สาเหตุจริงมักเป็น ปริมาณข้อมูลที่คุณขอให้ฐานข้อมูลรวมกัน และ ความง่ายที่มันจะหาการจับคู่

ดัชนี (แนวคิด) และทำไมช่วย JOIN

คิดว่าดัชนีเหมือนสารบัญหนังสือ ถ้าไม่มีมัน ฐานข้อมูลอาจต้องสแกนหลายแถวเพื่อหาการจับคู่สำหรับเงื่อนไข JOIN หากมีดัชนีบนคีย์การ join (เช่น customers.customer_id และ orders.customer_id) ฐานข้อมูลจะกระโดดไปที่แถวที่เกี่ยวข้องได้เร็วขึ้น

คุณไม่ต้องรู้รายละเอียดภายในเพื่อใช้ให้ดี: ถ้าคอลัมน์ถูกใช้บ่อยในการจับคู่ (ON a.id = b.a_id) มันเป็นผู้สมัครที่ดีที่จะทำดัชนี

JOIN บนคีย์ที่เสถียร (ไม่ใช่ชื่อหรืออีเมล)

ถ้าเป็นไปได้ ให้ join บนตัวระบุที่เสถียรและไม่ซ้ำซ้อน:

  • ดี: customers.customer_id = orders.customer_id
  • เสี่ยง: customers.email = orders.email หรือ customers.name = orders.name

ชื่อเปลี่ยนได้และซ้ำได้ อีเมลอาจเปลี่ยนหรือมีรูปแบบต่างกัน ID ออกแบบมาสำหรับการจับคู่สม่ำเสมอและมักถูกทำดัชนี

ลดงานตั้งแต่ต้น

สองนิสัยที่ทำให้ JOIN เร็วขึ้นอย่างเห็นได้ชัด:

  1. เลือกคอลัมน์น้อยลง หลีกเลี่ยง SELECT * เมื่อ join หลายตาราง—คอลัมน์เกินความจำเป็นเพิ่มการใช้หน่วยความจำและเครือข่าย
  2. จำกัดแถวก่อนหรือตอน JOIN กรองแต่เนิ่นๆ เท่าที่เป็นไปได้

ตัวอย่าง: จำกัดคำสั่งซื้อก่อนแล้วค่อย join:

SELECT c.customer_id, c.name, o.order_id, o.created_at
FROM customers c
JOIN (
  SELECT order_id, customer_id, created_at
  FROM orders
  WHERE created_at >= DATE '2025-01-01'
) o
  ON o.customer_id = c.customer_id;

ถ้าคุณทำงานซ้ำบนคิวรีเหล่านี้ภายในกระบวนการสร้างแอป (เช่น สร้างหน้ารายงานบน PostgreSQL) เครื่องมืออย่าง Koder.ai ช่วยเร่งการสร้างสเกฟโฟลด์—สคีมา endpoints UI—ในขณะที่คุณควบคุมโลจิก JOIN ที่กำหนดความถูกต้อง

ชีทสรุป JOIN ด่วน

  • INNER JOIN → แถวที่จับคู่ใน ทั้งสอง ตารางเท่านั้น
  • LEFT JOIN → แถวทั้งหมดจากตารางซ้าย พร้อมการจับคู่จากขวา (ไม่จับคู่จะเป็น NULL)
  • RIGHT JOIN → แถวทั้งหมดจากตารางขวา พร้อมการจับคู่จากซ้าย (NULL เมื่อหาย)
  • FULL OUTER JOIN → แถวทั้งหมดจากทั้งสองตาราง; แถวที่จับคู่จะรวมกัน แถวที่ไม่จับคู่จะมี NULL
  • CROSS JOIN → ทุกการผสมของแถว (ขนาดถูกคูณ; ใช้อย่างระมัดระวัง)
  • SELF JOIN → ตารางต่อกับตัวเอง (ใช้กับโครงสร้างลำดับชั้นและการเปรียบเทียบ)

คำถามที่พบบ่อย

What is a SQL JOIN in plain English?

การ JOIN ใน SQL รวมแถวจากสอง (หรือมากกว่า) ตารางให้เป็นชุดผลลัพธ์เดียว โดยจับคอลัมน์ที่เกี่ยวข้องเข้าด้วยกัน—บ่อยครั้งคือ primary key กับ foreign key (เช่น customers.customer_id = orders.customer_id) วิธีนี้แหละที่ช่วยให้คุณนำตารางที่ถูก normalized กลับมารวมกันเวลาต้องการรายงาน การตรวจสอบ หรือการวิเคราะห์

When should I use INNER JOIN?

ใช้ INNER JOIN เมื่อคุณต้องการเฉพาะแถวที่มีความสัมพันธ์ในทั้งสองตารางเท่านั้น

  • ลูกค้าที่ไม่มีคำสั่งซื้อจะไม่ปรากฏ
  • คำสั่งซื้อที่อ้างอิงลูกค้าที่หายไปจะไม่ปรากฏ

เหมาะสำหรับความสัมพันธ์ที่ “ยืนยันแล้ว” เช่น การแสดงเฉพาะลูกค้าที่เคยซื้อของ

How do I find rows that don’t have a match in another table?

ใช้ LEFT JOIN เมื่อคุณต้องการเก็บแถวทั้งหมดจากตารางหลัก (ซ้าย) และดึงข้อมูลจากตารางขวาเมื่อมีการจับคู่

ตัวอย่างการหาที่ไม่มีการจับคู่:

SELECT c.customer_id, c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
WHERE o.order_id IS NULL;
Do I really need RIGHT JOIN, and how do I replace it with LEFT JOIN?

RIGHT JOIN จะเก็บแถวทั้งหมดจากตารางด้านขวาและใส่ค่า NULL ให้คอลัมน์ของตารางซ้ายเมื่อไม่มีการจับคู่ ทีมส่วนใหญ่หลีกเลี่ยงเพราะอ่านกลับด้านได้ยาก

โดยทั่วไปสามารถเขียนใหม่เป็น LEFT JOIN ได้โดยสลับลำดับตาราง เช่น:

FROM payments p
LEFT JOIN orders o ON o.order_id = p.order_id
What is FULL OUTER JOIN best used for?

ใช้ FULL OUTER JOIN เมื่อคุณต้องการผลลัพธ์ที่รวมทั้งแถวที่จับคู่และแถวที่มีเฉพาะฝั่งใดฝั่งหนึ่ง ซึ่งเหมาะกับการไกล่เกลี่ย เช่น “คำสั่งซื้อที่ไม่มีการชำระ” และ “การชำระเงินที่ไม่มีคำสั่งซื้อ” เพราะฝั่งที่ไม่มีจะมีค่า NULL ในคอลัมน์อีกฝั่ง

What if my database doesn’t support FULL OUTER JOIN?

บางฐานข้อมูล (เช่น MySQL และ SQLite) ไม่มี FULL OUTER JOIN ตรงๆ วิธีแก้คือรวมผลจาก

  • orders LEFT JOIN payments
  • กับแถวที่อยู่ใน payments แต่ไม่มีใน orders

โดยใช้ UNION (หรือ UNION ALL พร้อมเงื่อนไขที่ระมัดระวัง) เพื่อรักษาแถวที่อยู่ข้างเดียวทั้งสองฝั่ง

What is a CROSS JOIN, and when is it actually useful?

CROSS JOIN คืนค่าทุกการจับคู่ระหว่างสองตาราง (Cartesian product) และมีประโยชน์เมื่อต้องการสร้างสถานการณ์ เช่น ขนาด × สี หรือสร้างกริดปฏิทิน

ข้อควรระวัง: จำนวนแถวเพิ่มขึ้นเร็วมาก ดังนั้นใช้เมื่อขนาดตารางนำเข้าตัวเล็กและควบคุมได้

What is a SELF JOIN, and why do I need table aliases?

Self join คือการเชื่อมตารางกับตัวมันเองเพื่อเชื่อมความสัมพันธ์ภายในตารางเดียวกัน (เช่น พนักงาน → ผู้จัดการ)

คุณต้องใช้ alias เพื่อแยกความหมายของแต่ละสำเนา เช่น:

FROM employees e
LEFT JOIN employees m
  ON e.manager_id = m.id
What’s the difference between filtering in ON vs WHERE for joins?

ON กำหนดว่าแถวใดจะจับคู่กันระหว่างการ JOIN; WHERE กรองผลลัพธ์หลังการ JOIN เสร็จแล้ว

ด้วย LEFT JOIN การใส่เงื่อนไขของตารางขวาใน WHERE อาจทำให้แถวที่ไม่มีการจับคู่ถูกทิ้งไปและเปลี่ยนพฤติกรรมเป็นเหมือน INNER JOIN

ถ้าต้องการเก็บแถวซ้ายทั้งหมดแต่จำกัดว่าแถวขวาที่จะจับคู่นั้นต้องเป็นอย่างไร ให้ใส่เงื่อนไขของตารางขวาไว้ใน ON แทน

Why do joins create duplicates, and how do I prevent double-counting?

การ JOIN ทำให้แถวทวีคูณเมื่อความสัมพันธ์เป็น one-to-many หรือ many-to-many เช่น คำสั่งซื้อที่มีสองการชำระจะปรากฏสองครั้งเมื่อต่อกับตาราง payments

ป้องกันการนับซ้ำโดยสรุปฝั่งที่เป็น "many" ก่อนแล้วค่อย JOIN:

WITH payment_totals AS (
  SELECT
    order_id,
    SUM(amount) AS total_paid,
    COUNT(*)   AS payment_count
  FROM payments
  GROUP BY order_id
)
SELECT
  o.order_id,
  o.customer_id,
  COALESCE(pt.total_paid, 0) AS total_paid,
  COALESCE(pt.payment_count, 0) AS payment_count
FROM orders o
LEFT JOIN payment_totals pt
  ON pt.order_id = o.order_id;

ใช้ DISTINCT เป็นทางเลือกสุดท้ายเพราะมันอาจปกปิดปัญหาจริงและทำให้ผลรวมผิดพลาด

Related posts