[SQL] อธิบายวิธีใช้คำสั่ง GROUP BY อย่างละเอียด! รวมข้อมูลเพื่อการวิเคราะห์ได้อย่างอิสระ
ไม่ว่าจะเป็นการวิเคราะห์การเข้าชมเว็บไซต์ การวิเคราะห์ยอดขายของร้านค้าออนไลน์ หรือการตรวจสอบพฤติกรรมผู้ใช้งานของแอปพลิเคชัน การ “รวมข้อมูล” ถือเป็นขั้นตอนสำคัญเมื่อทำงานกับข้อมูล คุณเคยคิดไหมว่ามันจะสะดวกแค่ไหนถ้าสามารถดึงข้อมูลเช่น “จำนวนสินค้าต่อหมวดหมู่” “จำนวนผู้ใช้ต่อจังหวัด” หรือ “ยอดขายรวมต่อเดือน” ได้อย่างรวดเร็ว? คำสั่ง GROUP BY ของ SQL ช่วยให้คุณทำเช่นนั้นได้
GROUP BY เป็นเครื่องมืออันทรงพลังที่ใช้ในการจัดกลุ่มข้อมูลในตารางตามค่าของคอลัมน์ที่ระบุ และสามารถใช้ฟังก์ชันรวมต่าง ๆ เช่น COUNT, SUM, AVG กับแต่ละกลุ่มได้ แม้ในตอนแรกอาจดูซับซ้อนเล็กน้อย แต่หากเข้าใจหลักการใช้งานแล้ว ความสามารถในการวิเคราะห์ข้อมูลของคุณจะพัฒนาไปได้ไกลอย่างมาก
บทความนี้ถูกเขียนขึ้นโดยคำนึงถึงผู้เริ่มต้นที่ต้องการเป็นนักสร้างเว็บไซต์ เราจะอธิบายตั้งแต่การใช้ GROUP BY เบื้องต้น ไปจนถึงเทคนิคขั้นสูง เช่น การใช้คำสั่ง HAVING และการจัดกลุ่มด้วยหลายคอลัมน์ โดยใช้ตัวอย่างโค้ด “คัดลอกและใช้งานได้ทันที” อย่างละเอียด มาร่วมกันสัมผัสความสนุกของ “การจัดกลุ่มและรวมข้อมูล” กันเถอะ!
เตรียมความพร้อม: สร้างข้อมูลตัวอย่างเพื่อฝึกวิเคราะห์
วิธีที่ดีที่สุดในการเรียนรู้ SQL คือการลงมือทำจริง บทความนี้จะใช้ตารางชื่อ products ซึ่งจำลองรายชื่อสินค้าในร้านค้าออนไลน์ขนาดเล็กเสมือนจริง โค้ด SQL ด้านล่างนี้จะสร้างตารางและเพิ่มข้อมูลตัวอย่าง SQL ทุกตัวอย่างในบทความนี้จะใช้ตารางนี้เป็นฐานข้อมูล
-- ลบตารางหากมีอยู่แล้ว (เพื่อให้สามารถทดลองซ้ำได้)
DROP TABLE IF EXISTS products;
-- สร้างตาราง products
CREATE TABLE products (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
category TEXT NOT NULL,
prefecture TEXT NOT NULL,
price INTEGER NOT NULL,
stock_quantity INTEGER NOT NULL
);
-- เพิ่มข้อมูล
INSERT INTO products (id, name, category, prefecture, price, stock_quantity) VALUES
(1, 'เสื้อยืดสุดเจ๋ง', 'เสื้อผ้า', 'โตเกียว', 3000, 50),
(2, 'กางเกงยีนส์ที่ดีที่สุด', 'เสื้อผ้า', 'โอกายามะ', 12000, 30),
(3, 'คีย์บอร์ดวิเศษ', 'อุปกรณ์ PC', 'โตเกียว', 8500, 20),
(4, 'เมาส์มีไฟ', 'อุปกรณ์ PC', 'คานากาวะ', 4500, 45),
(5, 'มีดฝีมือช่าง', 'เครื่องครัว', 'นีงาตะ', 9800, 15),
(6, 'กระทะสุดยอด', 'เครื่องครัว', 'นีงาตะ', 6200, 25),
(7, 'เริ่มต้นกับ SQL', 'หนังสือ', 'โตเกียว', 2800, 100),
(8, 'การออกแบบเว็บไซต์เบื้องต้น', 'หนังสือ', 'โตเกียว', 3200, 80),
(9, 'รองเท้าผ้าใบสุดสบาย', 'เสื้อผ้า', 'โตเกียว', 7800, 60);
ตารางนี้ประกอบด้วยข้อมูล เช่น รหัสสินค้า ชื่อสินค้า หมวดหมู่ จังหวัดที่ผลิต ราคา และจำนวนสินค้าในสต็อก ซึ่งมีข้อมูลซ้ำโดยตั้งใจ เช่น หมวดหมู่ “เสื้อผ้า” มี 3 รายการ และสินค้าที่มาจาก “โตเกียว” มี 4 รายการ เหมาะสำหรับการทดลองรวมข้อมูลด้วย GROUP BY
【พื้นฐาน】นับจำนวนสินค้าต่อหมวดหมู่ด้วย `GROUP BY`
มาเริ่มจากการใช้ GROUP BY แบบพื้นฐานที่สุดกันก่อน “มีสินค้ากี่ชนิดต่อหนึ่งหมวดหมู่?” เราจะหาคำตอบนี้โดยใช้ GROUP BY กับคอลัมน์ category และใช้ฟังก์ชัน COUNT() ที่คุ้นเคยในการนับจำนวนแถวในแต่ละกลุ่ม
SELECT
category,
COUNT(*) AS product_count
FROM
products
GROUP BY
category;
ผลลัพธ์:
category | product_count
-------------|---------------
อุปกรณ์ PC | 2
เสื้อผ้า | 3
เครื่องครัว | 2
หนังสือ | 2
เราได้จำนวนสินค้าต่อหมวดหมู่อย่างสมบูรณ์แบบ! SQL engine จะจัดกลุ่มข้อมูลภายในตามค่าของคอลัมน์ category (เช่น 'เสื้อผ้า', 'อุปกรณ์ PC') และใช้ COUNT(*) กับแต่ละกลุ่ม นอกจากนี้ AS product_count ก็ใช้ตั้งชื่อให้กับคอลัมน์ผลลัพธ์ด้วย
【応用1】ผสมกับฟังก์ชันการรวมอื่นๆ เช่น `SUM` และ `AVG`
จุดเด่นของ GROUP BY คือสามารถใช้งานร่วมกับฟังก์ชันการรวมอื่นๆ ได้อย่างอิสระ ไม่ว่าจะเป็น COUNT(), SUM() (ผลรวม), AVG() (ค่าเฉลี่ย), MAX() (ค่าสูงสุด), และ MIN() (ค่าต่ำสุด)
คราวนี้เราจะลองคำนวณ “ราคาเฉลี่ยของแต่ละหมวดหมู่” กันดู
SELECT
category,
AVG(price) AS average_price
FROM
products
GROUP BY
category;
ผลลัพธ์:
category | average_price
-------------|---------------
อุปกรณ์ PC | 6500
เสื้อผ้า | 7600
เครื่องครัว | 8000
หนังสือ | 3000
เพียงแค่เปลี่ยนฟังก์ชันการรวม ก็สามารถวิเคราะห์ข้อมูลในแง่มุมต่างๆ ได้ เช่น หากต้องการรู้ “จำนวนสินค้าคงเหลือรวมต่อหมวดหมู่” ให้ใช้ SUM(stock_quantity)
SELECT
category,
SUM(stock_quantity) AS total_stock
FROM
products
GROUP BY
category;
ผลลัพธ์:
category | total_stock
-------------|-------------
อุปกรณ์ PC | 65
เสื้อผ้า | 140
เครื่องครัว | 40
หนังสือ | 180
จากผลลัพธ์จะเห็นได้ชัดว่าหมวด “หนังสือ” มีสินค้าคงเหลือมากที่สุด และ “เครื่องครัว” มีน้อยที่สุด
【ขั้นสูง 2】กรองผลลัพธ์ที่รวมแล้วด้วยคำสั่ง `HAVING`
มักจะมีกรณีที่คุณต้องการ "กรองเพิ่มเติม" จากผลลัพธ์ที่ได้จาก GROUP BY ตัวอย่างเช่น "แสดงเฉพาะหมวดหมู่สินค้าที่มีอย่างน้อย 3 รายการ"
สิ่งที่ต้องระวังคือ คำสั่ง WHERE ใช้สำหรับกรองข้อมูลก่อนการรวมกลุ่ม (ต่อแถว) จึงไม่สามารถใช้กับผลลัพธ์ที่รวมแล้วเช่น COUNT(*) ได้
การระบุเงื่อนไขหลังจากการรวมกลุ่ม จะใช้คำสั่ง `HAVING`
SELECT
category,
COUNT(*) AS product_count
FROM
products
GROUP BY
category
HAVING
COUNT(*) >= 3;
ผลลัพธ์:
category | product_count
---------|---------------
เสื้อผ้า | 3
เนื่องจากเราเพิ่มเงื่อนไข HAVING COUNT(*) >= 3 จึงได้เฉพาะหมวด "เสื้อผ้า" ที่มีจำนวนสินค้า 3 ชิ้นแสดงออกมา
`WHERE` ใช้ก่อนการจัดกลุ่ม, `HAVING` ใช้หลังจากจัดกลุ่มแล้ว อย่าลืมความแตกต่างนี้เด็ดขาด
【ขั้นสูง 3】จัดกลุ่มด้วยหลายคอลัมน์
GROUP BY สามารถใช้หลายคอลัมน์ร่วมกันเพื่อสร้างกลุ่มที่ละเอียดขึ้นได้ ตัวอย่างเช่น "ต้องการนับจำนวนสินค้าต่อหมวดหมู่และจังหวัดที่ผลิต"
เพียงแค่ระบุชื่อคอลัมน์ด้วยเครื่องหมายจุลภาคในคำสั่ง GROUP BY
SELECT
category,
prefecture,
COUNT(*) AS product_count
FROM
products
GROUP BY
category, prefecture;
ผลลัพธ์:
category | prefecture | product_count
-------------|------------|---------------
อุปกรณ์ PC | คานากาว่า | 1
อุปกรณ์ PC | โตเกียว | 1
เสื้อผ้า | โอคายามะ | 1
เสื้อผ้า | โตเกียว | 2
เครื่องครัว | นีงาตะ | 2
หนังสือ | โตเกียว | 2
คุณจะเห็นว่า "เสื้อผ้า จากโอคายามะ" และ "เสื้อผ้า จากโตเกียว" ถูกแยกเป็นกลุ่มคนละกลุ่ม นี่ทำให้สามารถวิเคราะห์เชิงลึกได้เช่น "มีสินค้าประเภทเสื้อผ้าในโตเกียวอยู่ 2 รายการ"
[ส่วนปฏิบัติ] เรียกใช้ SQL ได้อย่างอิสระในเบราว์เซอร์!
พอแล้วกับทฤษฎี! ถึงเวลาที่คุณจะได้เขียน SQL, รัน และตรวจสอบผลลัพธ์ด้วยตัวเอง
คัดลอกโค้ด HTML ด้านล่างทั้งหมด แล้วบันทึกลงเครื่องพีซีของคุณเป็นไฟล์ชื่อ sql_groupby_test.html หรือชื่อใดที่คุณต้องการ จากนั้นเปิดไฟล์นั้นในเบราว์เซอร์เว็บ คุณจะได้สภาพแวดล้อมการรัน SQL ของคุณเองที่ฝังตาราง products ไว้
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>สภาพแวดล้อมการรัน SQL GROUP BY</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; line-height: 1.7; color: #333; max-width: 800px; margin: 2rem auto; padding: 0 1rem; }
h1 { color: #2c3e50; }
textarea { width: 100%; height: 180px; font-family: "SF Mono", "Consolas", monospace; font-size: 16px; padding: 12px; border: 1px solid #ccc; border-radius: 6px; box-sizing: border-box; margin-bottom: 1rem; }
button { background-color: #3498db; color: white; border: none; padding: 12px 22px; font-size: 16px; border-radius: 6px; cursor: pointer; transition: background-color 0.2s; }
button:hover { background-color: #2980b9; }
button:disabled { background-color: #bdc3c7; cursor: not-allowed; }
#result-container { margin-top: 2rem; border: 1px solid #ddd; padding: 1rem; border-radius: 6px; background: #fdfdfd; min-height: 50px; }
#error-message { color: #e74c3c; font-weight: bold; }
table { border-collapse: collapse; width: 100%; margin-top: 1rem; }
th, td { border: 1px solid #ddd; padding: 10px; text-align: left; }
th { background-color: #f2f2f2; }
tr:nth-child(even) { background-color: #f9f9f9; }
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.10.3/sql-wasm.js"></script>
</head>
<body>
<h1>ลองใช้ SQL ในเบราว์เซอร์ของคุณดูสิ!</h1>
<p>ป้อนคำสั่ง SQL ใน textarea ข้างล่าง แล้วคลิกปุ่ม “Execute” เพื่อทดลองคำสั่ง SQL ที่นําเสนอในบทความนี้!</p>
<textarea id="sql-input">-- แสดงราคาเฉลี่ยและราคาสูงสุดตามจังหวัด
SELECT
prefecture,
AVG(price) AS "Average Price",
MAX(price) AS "Max Price"
FROM
products
GROUP BY
prefecture;</textarea>
<button id="execute-btn">Execute</button>
<div id="result-container">
<p id="error-message"></p>
<div id="result-output"></div>
</div>
<script>
const sqlInput = document.getElementById('sql-input');
const executeBtn = document.getElementById('execute-btn');
const errorMsg = document.getElementById('error-message');
const resultOutput = document.getElementById('result-output');
let db;
async function initDb() {
executeBtn.disabled = true;
executeBtn.textContent = 'กำลังเตรียมฐานข้อมูล...';
try {
const SQL = await initSqlJs({
locateFile: file => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.10.3/${file}`
});
db = new SQL.Database();
const setupSql = `
DROP TABLE IF EXISTS products;
CREATE TABLE products (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
category TEXT NOT NULL,
prefecture TEXT NOT NULL,
price INTEGER NOT NULL,
stock_quantity INTEGER NOT NULL
);
INSERT INTO products (id, name, category, prefecture, price, stock_quantity) VALUES
(1, 'เสื้อยืดสุดเจ๋ง', 'เสื้อผ้า', 'โตเกียว', 3000, 50),
(2, 'กางเกงยีนส์ดีที่สุด', 'เสื้อผ้า', 'โอกายามะ', 12000, 30),
(3, 'คีย์บอร์ดวิเศษ', 'อุปกรณ์คอมพิวเตอร์', 'โตเกียว', 8500, 20),
(4, 'เมาส์เรืองแสง', 'อุปกรณ์คอมพิวเตอร์', 'คานางาวะ', 4500, 45),
(5, 'มีดฝีมือช่าง', 'เครื่องครัว', 'นีงาตะ', 9800, 15),
(6, 'กระทะสุดยอด', 'เครื่องครัว', 'นีงาตะ', 6200, 25),
(7, 'เริ่มต้นใช้งาน SQL', 'หนังสือ', 'โตเกียว', 2800, 100),
(8, 'พื้นฐานการออกแบบเว็บไซต์', 'หนังสือ', 'โตเกียว', 3200, 80),
(9, 'รองเท้าผ้าใบใส่สบาย', 'เสื้อผ้า', 'โตเกียว', 7800, 60);
`;
db.run(setupSql);
executeBtn.disabled = false;
executeBtn.textContent = 'Execute';
resultOutput.innerHTML = '<p>พร้อมใช้งานแล้ว! ทดลอง SQL ได้เต็มที่เลย</p>';
} catch (err) {
errorMsg.textContent = 'การเตรียมฐานข้อมูลล้มเหลว: ' + err.message;
console.error(err);
}
}
function executeSql() {
if (!db) return;
const sql = sqlInput.value;
errorMsg.textContent = '';
resultOutput.innerHTML = '';
try {
const results = db.exec(sql);
if (results.length === 0) {
resultOutput.innerHTML = '<p>คำสั่งถูกดำเนินการแต่ไม่มีผลลัพธ์ (เช่น INSERT, UPDATE)</p>';
return;
}
results.forEach(result => {
const table = document.createElement('table');
const thead = document.createElement('thead');
const tbody = document.createElement('tbody');
const headerRow = document.createElement('tr');
result.columns.forEach(colName => {
const th = document.createElement('th');
th.textContent = colName;
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
result.values.forEach(row => {
const bodyRow = document.createElement('tr');
row.forEach(cellValue => {
const td = document.createElement('td');
td.textContent = cellValue === null ? 'NULL' : (typeof cellValue === 'number' ? cellValue.toLocaleString() : cellValue);
bodyRow.appendChild(td);
});
tbody.appendChild(bodyRow);
});
table.appendChild(thead);
table.appendChild(tbody);
resultOutput.appendChild(table);
});
} catch (err) {
errorMsg.textContent = 'ข้อผิดพลาด SQL: ' + err.message;
console.error(err);
}
}
executeBtn.addEventListener('click', executeSql);
initDb();
</script>
</body>
</html>
【โจทย์ท้าทาย】
- ลองแสดงจำนวนสินค้าต่อจังหวัด (
prefecture) ดูสิ - กรองเฉพาะสินค้าที่ราคามากกว่า 5,000 เยน (คำแนะนำ: ใช้คำสั่ง
WHERE) แล้วแสดงจำนวนสินค้าต่อประเภท - ลองใช้คำสั่ง
HAVINGเพื่อหาประเภทสินค้าที่มีจำนวนสต๊อก (stock_quantity) น้อยกว่า 30
ข้อควรระวังที่มือใหม่มักจะพลาด: กฎของคำสั่ง `SELECT`
เมื่อใช้ GROUP BY มีข้อผิดพลาดที่มือใหม่มักเจอบ่อยที่สุด คือ “ในคำสั่ง SELECT สามารถเขียนได้แค่คอลัมน์ที่ใช้ใน GROUP BY หรือคอลัมน์ที่อยู่ในฟังก์ชันรวมเท่านั้น”
ตัวอย่างเช่น SQL ด้านล่างนี้จะเกิดข้อผิดพลาด:
-- ตัวอย่างที่จะเกิดข้อผิดพลาด
SELECT
category,
name -- ← คอลัมน์นี้ไม่ได้อยู่ใน GROUP BY หรือฟังก์ชันรวม
FROM
products
GROUP BY
category;
ทำไมถึงเกิดข้อผิดพลาด? หากคิดให้ดีจะเข้าใจได้ง่าย เมื่อใช้ GROUP BY category แล้วรวมข้อมูลเป็นแถวเดียว เช่น “เสื้อผ้า” คอลัมน์ name จะมีตัวเลือก 3 ตัว เช่น “เสื้อยืดสุดเจ๋ง”, “ยีนส์ที่ดีที่สุด” และ “รองเท้าสวมใส่สบาย” ระบบ SQL ไม่สามารถรู้ได้ว่าจะเลือกแสดงตัวไหน จึงส่งคืนข้อผิดพลาด
แถวที่ได้จากการ GROUP BY เป็นตัวแทนของหลายแถวจากข้อมูลเดิม ดังนั้นใน SELECT จึงสามารถใช้ได้แค่คอลัมน์ที่ระบุใน GROUP BY หรือค่าจากฟังก์ชันรวม เช่น COUNT(*) หรือ SUM(price)
เทคนิคที่เกี่ยวข้อง: การจัดเรียงผลลัพธ์ด้วย `ORDER BY`
บ่อยครั้งที่คุณอาจอยากจัดเรียงผลลัพธ์ที่ได้จาก GROUP BY ให้เรียงตามลำดับที่ต้องการ ในกรณีนั้นให้ใช้คำสั่ง ORDER BY ซึ่งจะเขียนไว้หลัง GROUP BY และ HAVING
ตัวอย่างเช่น ลองจัดเรียง “จำนวนสินค้าต่อประเภท” ตามจำนวนสินค้าที่มากไปน้อย (จากมากไปน้อย):
SELECT
category,
COUNT(*) AS product_count
FROM
products
GROUP BY
category
ORDER BY
product_count DESC;
ผลลัพธ์:
category | product_count
----------------|---------------
เสื้อผ้า | 3
อุปกรณ์คอมพิวเตอร์ | 2
ของใช้ในครัว | 2
หนังสือ | 2
โดยใช้ ORDER BY product_count DESC เราสามารถจัดเรียงตาม product_count ในลำดับจากมากไปน้อย (DESC) ได้ทันที ช่วยให้เห็นว่า ประเภทสินค้าใดมีสินค้ามากที่สุดได้อย่างชัดเจน
สรุป
ในบทความนี้ เราได้เรียนรู้เกี่ยวกับเครื่องมืออันทรงพลังอย่างคำสั่ง GROUP BY เพื่อการจัดกลุ่มและวิเคราะห์ข้อมูล
- พื้นฐาน: ใช้
GROUP BY ชื่อคอลัมน์เพื่อจัดกลุ่มข้อมูล - การรวมค่า: ใช้ร่วมกับ
COUNT,SUM,AVGเพื่อดึงคุณลักษณะของแต่ละกลุ่ม - การกรอง: หากต้องการกรองหลังการรวมค่า ให้ใช้คำสั่ง
HAVING - หลายคอลัมน์: สามารถใช้
GROUP BY คอลัมน์1, คอลัมน์2เพื่อจัดกลุ่มในระดับละเอียด - กฎ: ใน
SELECTสามารถใช้ได้แค่คอลัมน์ที่อยู่ในGROUP BYหรือคอลัมน์ที่มีฟังก์ชันรวมเท่านั้น
เมื่อคุณเชี่ยวชาญการใช้ GROUP BY จะไม่เพียงแค่ดึงข้อมูลออกมา แต่ยังสามารถค้นหาแนวโน้มและข้อมูลเชิงลึกจากข้อมูลดิบได้อีกด้วย ลองใช้ในส่วนทดลองของบทความนี้ แล้วคุณจะรู้สึกถึงพลังของมันอย่างแท้จริง ความสามารถด้าน “การรวมวิเคราะห์ข้อมูล” จะเป็นทักษะอันทรงพลังในฐานะนักพัฒนาเว็บไซต์