การสร้างสถานะขึ้นใหม่จากเหตุการณ์
รวมเหตุการณ์เข้ากับสถานะปัจจุบัน
การสร้างสถานะขึ้นใหม่จากเหตุการณ์ เป็นบทเรียน SQL Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน SQL Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส SQL Academy มีบทเรียนทั้งหมด 4 บทเรียน
การสร้างสถานะขึ้นใหม่หมายถึงอะไร
ในการจัดเก็บที่ขับเคลื่อนด้วยเหตุการณ์ ข้อมูลจะถูกเก็บเป็นบันทึกเหตุการณ์ที่เปลี่ยนแปลงไม่ได้ แทนที่จะเก็บเป็นแถวที่แก้ไขได้ หากต้องการทราบสถานะปัจจุบันของสิ่งใดก็ตาม คุณต้องประมวลเหตุการณ์เหล่านั้นซ้ำแล้วรวมผลเป็นผลลัพธ์เดียว
กระบวนการนี้เรียกว่าการสร้างสถานะขึ้นใหม่จากเหตุการณ์ ลองนึกถึงบัญชีธนาคาร แทนที่จะเก็บยอดคงเหลือ คุณจะเก็บรายการฝากเงินและถอนเงินทุกครั้ง ยอดคงเหลือจะเท่ากับผลรวมของเหตุการณ์ทั้งหมดนั้นเสมอ
ตารางเหตุการณ์อย่างง่าย
ให้เริ่มด้วยการสร้างบันทึกเหตุการณ์พื้นฐานสำหรับระบบบัญชีธนาคาร แต่ละแถวแสดงสิ่งที่เกิดขึ้น — การฝากเงินหรือการถอนเงิน — พร้อมจำนวนเงินและเวลาประทับ
ตารางนี้จะไม่ถูกแก้ไขหรือลบ แถวใหม่จะถูกเพิ่มต่อท้ายเสมอเพื่อบันทึกข้อเท็จจริงใหม่
CREATE TABLE account_events (
event_id SERIAL PRIMARY KEY,
account_id INT NOT NULL,
event_type VARCHAR(20) NOT NULL, -- 'deposit' or 'withdrawal'
amount NUMERIC(12, 2) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
INSERT INTO account_events (account_id, event_type, amount, created_at) VALUES
(1, 'deposit', 1000.00, '2024-01-01 09:00:00+00'),
(1, 'deposit', 500.00, '2024-01-03 14:00:00+00'),
(1, 'withdrawal', 200.00, '2024-01-05 10:00:00+00'),
(1, 'deposit', 300.00, '2024-01-07 11:00:00+00'),
(1, 'withdrawal', 150.00, '2024-01-09 16:00:00+00');การรวมเหตุการณ์เป็นยอดคงเหลือ
หากต้องการสร้างยอดคงเหลือปัจจุบันขึ้นใหม่ เราจะรวมเหตุการณ์ทั้งหมดเข้าด้วยกัน รายการฝากจะเพิ่มยอดคงเหลือ ส่วนรายการถอนจะลดยอดคงเหลือ นิพจน์ CASE ช่วยให้เราจัดการเหตุการณ์แต่ละประเภทด้วยเครื่องหมายที่ถูกต้องก่อนนำมารวมกัน
คิวรีเดียวนี้ให้สถานะปัจจุบันที่คำนวณจากบันทึกเหตุการณ์ในอดีตทั้งหมด
SELECT
account_id,
SUM(
CASE event_type
WHEN 'deposit' THEN amount
WHEN 'withdrawal' THEN -amount
ELSE 0
END
) AS current_balance
FROM account_events
WHERE account_id = 1
GROUP BY account_id;สถานะ ณ เวลาใดเวลาหนึ่ง
หนึ่งในคุณสมบัติที่ทรงพลังที่สุดของการจัดเก็บเหตุการณ์คือความสามารถในการสร้างสถานะขึ้นใหม่ ณ เวลาใดก็ได้ เพียงเพิ่มตัวกรอง WHERE created_at <= :target_time ก่อนการรวมข้อมูล
วิธีนี้ทำให้คุณสามารถใช้คิวรีข้ามเวลาได้โดยไม่ต้องเปลี่ยนแปลงโครงสร้างฐานข้อมูลเพิ่มเติม — ประวัติมีอยู่ในบันทึกเหตุการณ์แล้ว
-- What was the balance at the end of January 5th?
SELECT
account_id,
SUM(
CASE event_type
WHEN 'deposit' THEN amount
WHEN 'withdrawal' THEN -amount
ELSE 0
END
) AS balance_at_snapshot
FROM account_events
WHERE account_id = 1
AND created_at <= '2024-01-05 23:59:59+00'
GROUP BY account_id;ยอดคงเหลือสะสมด้วยฟังก์ชันหน้าต่าง
แทนที่จะคำนวณเพียงยอดรวมเดียว เราสามารถคำนวณยอดคงเหลือสะสม ซึ่งเป็นยอดคงเหลือหลังเหตุการณ์แต่ละรายการได้ ฟังก์ชันหน้าต่าง SUM(...) OVER (ORDER BY ...) จะคำนวณผลรวมสะสมเมื่อเหตุการณ์ต่าง ๆ เพิ่มขึ้นตามลำดับเวลา
วิธีนี้มีประโยชน์อย่างยิ่งสำหรับบันทึกการตรวจสอบและการแก้ไขจุดบกพร่องของการเปลี่ยนแปลงสถานะ
SELECT
event_id,
created_at,
event_type,
amount,
SUM(
CASE event_type
WHEN 'deposit' THEN amount
WHEN 'withdrawal' THEN -amount
ELSE 0
END
) OVER (PARTITION BY account_id ORDER BY created_at, event_id)
AS running_balance
FROM account_events
WHERE account_id = 1
ORDER BY created_at, event_id;การจัดเก็บสถานะจริงในตารางภาพสถานะ
การเล่นเหตุการณ์ทั้งหมดซ้ำทุกครั้งที่มีการคิวรีอาจใช้เวลามากขึ้นเมื่อบันทึกมีขนาดใหญ่ขึ้น การเพิ่มประสิทธิภาพที่ใช้กันทั่วไปคือการจัดเก็บสถานะปัจจุบันเป็นข้อมูลจริงในตารางภาพสถานะ แล้วสร้างตารางนี้ขึ้นใหม่เป็นระยะ ๆ หรือตามต้องการ
ภาพสถานะจะจัดเก็บผลลัพธ์จากการรวมเหตุการณ์ คิวรีจึงอ่านข้อมูลจากภาพสถานะแทนการเล่นบันทึกทั้งหมดซ้ำทุกครั้ง
CREATE TABLE account_snapshots (
account_id INT PRIMARY KEY,
current_balance NUMERIC(12, 2) NOT NULL,
as_of_event_id INT NOT NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Populate / refresh the snapshot from the event log
INSERT INTO account_snapshots (account_id, current_balance, as_of_event_id, updated_at)
SELECT
account_id,
SUM(CASE event_type WHEN 'deposit' THEN amount WHEN 'withdrawal' THEN -amount ELSE 0 END),
MAX(event_id),
NOW()
FROM account_events
GROUP BY account_id
ON CONFLICT (account_id) DO UPDATE
SET current_balance = EXCLUDED.current_balance,
as_of_event_id = EXCLUDED.as_of_event_id,
updated_at = EXCLUDED.updated_at;การปรับปรุงภาพสถานะแบบเพิ่มทีละส่วน
เมื่อมีเหตุการณ์ใหม่เข้ามา คุณไม่จำเป็นต้องเล่นประวัติทั้งหมดซ้ำ หากคุณบันทึก event_id ล่าสุดที่ประมวลผลไว้ในภาพสถานะ คุณก็สามารถใช้เฉพาะส่วนต่างได้ ซึ่งก็คือเหตุการณ์ที่เกิดขึ้นหลังจากสร้างภาพสถานะ
รูปแบบการปรับปรุงแบบเพิ่มทีละส่วนนี้ช่วยให้การปรับปรุงภาพสถานะทำได้รวดเร็ว แม้บันทึกจะมีขนาดใหญ่
-- Apply only new events since the last snapshot
UPDATE account_snapshots AS snap
SET
current_balance = snap.current_balance + delta.net,
as_of_event_id = delta.max_event_id,
updated_at = NOW()
FROM (
SELECT
ae.account_id,
SUM(CASE ae.event_type WHEN 'deposit' THEN ae.amount WHEN 'withdrawal' THEN -ae.amount ELSE 0 END) AS net,
MAX(ae.event_id) AS max_event_id
FROM account_events ae
JOIN account_snapshots s ON s.account_id = ae.account_id
WHERE ae.event_id > s.as_of_event_id
GROUP BY ae.account_id
) AS delta
WHERE snap.account_id = delta.account_id;ตารางตามเวลาและ SYSTEM VERSIONING
SQL:2011 ได้เปิดตัวตารางตามเวลาที่มีรุ่นจัดการโดยระบบ ซึ่งฐานข้อมูลจะดูแลเอง ทุกแถวจะมีคอลัมน์ valid_from และ valid_to โดยอัตโนมัติ และระบบฐานข้อมูลจะเป็นผู้จัดการคอลัมน์เหล่านี้
PostgreSQL ไม่รองรับคุณสมบัตินี้โดยตรง แต่คุณสามารถจำลองการทำงานได้ ฐานข้อมูลอื่น ๆ เช่น MariaDB และเซิร์ฟเวอร์ SQL รองรับ WITH SYSTEM VERSIONING โดยตรง
-- Emulating a temporal table in PostgreSQL
CREATE TABLE account_state_history (
account_id INT NOT NULL,
current_balance NUMERIC(12, 2) NOT NULL,
valid_from TIMESTAMPTZ NOT NULL,
valid_to TIMESTAMPTZ NOT NULL DEFAULT 'infinity'
);
-- Insert initial state
INSERT INTO account_state_history (account_id, current_balance, valid_from)
VALUES (1, 1000.00, '2024-01-01 09:00:00+00');
-- On update: close old row, insert new row
UPDATE account_state_history
SET valid_to = '2024-01-03 14:00:00+00'
WHERE account_id = 1 AND valid_to = 'infinity';
INSERT INTO account_state_history (account_id, current_balance, valid_from)
VALUES (1, 1500.00, '2024-01-03 14:00:00+00');การคิวรีประวัติตามเวลา
เมื่อมีตารางตามเวลาที่จำลองขึ้นแล้ว คุณสามารถตรวจสอบว่ายอดคงเหลือเป็นเท่าใดในช่วงเวลาใด ๆ ที่ผ่านมาได้ โดยกรองตามช่วงเวลาที่มีผล แถวที่ช่วงเวลาของมันครอบคลุมเวลาที่ต้องการ คือสถานะ ณ เวลานั้น
รูปแบบนี้แยกตรรกะของคิวรีออกจากการเล่นเหตุการณ์ซ้ำ — ตารางประวัติสถานะได้รวมข้อมูลไว้ล่วงหน้าแล้ว
-- What was the account balance on January 4th?
SELECT
account_id,
current_balance,
valid_from,
valid_to
FROM account_state_history
WHERE account_id = 1
AND valid_from <= '2024-01-04 00:00:00+00'
AND valid_to > '2024-01-04 00:00:00+00';การจัดเก็บเหตุการณ์กับหลายเอนทิตี
ระบบจริงมักติดตามเหตุการณ์ของเอนทิตีจำนวนมากพร้อมกัน บันทึกเหตุการณ์ร่วมที่มีคอลัมน์ entity_id และ entity_type ช่วยให้คุณสร้างสถานะของวัตถุใด ๆขึ้นใหม่จากตารางเดียว
ในที่นี้ เราติดตามการเคลื่อนไหวของสินค้าคงคลังในหลายผลิตภัณฑ์ การสร้างยอดคงคลังปัจจุบันของแต่ละผลิตภัณฑ์ขึ้นใหม่ก็เป็นเพียงการรวมข้อมูลแบบจัดกลุ่มอีกครั้ง
CREATE TABLE inventory_events (
event_id SERIAL PRIMARY KEY,
product_id INT NOT NULL,
event_type VARCHAR(20) NOT NULL, -- 'received', 'shipped', 'adjusted'
quantity INT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
INSERT INTO inventory_events (product_id, event_type, quantity, created_at) VALUES
(101, 'received', 200, '2024-03-01 08:00:00+00'),
(101, 'shipped', 50, '2024-03-02 12:00:00+00'),
(101, 'shipped', 30, '2024-03-04 15:00:00+00'),
(102, 'received', 150, '2024-03-01 08:00:00+00'),
(102, 'adjusted', -10, '2024-03-03 09:00:00+00');
-- Rebuild current stock for all products
SELECT
product_id,
SUM(CASE event_type WHEN 'received' THEN quantity WHEN 'shipped' THEN -quantity ELSE quantity END) AS stock_on_hand
FROM inventory_events
GROUP BY product_id
ORDER BY product_id;ใช้ CTE เพื่อความชัดเจน
คิวรีสร้างสถานะใหม่อาจมีความซับซ้อนมากขึ้น การครอบขั้นตอนการรวมข้อมูลไว้ใน CTE ช่วยให้อ่านง่ายขึ้น และช่วยให้คุณเชื่อมสถานะที่สร้างใหม่เข้ากับตารางอื่นได้อย่างเป็นระเบียบ
ในที่นี้ เราสร้างยอดคงเหลือของบัญชีขึ้นใหม่ แล้วเชื่อมเข้ากับตารางอ้างอิงบัญชีเพื่อรวมชื่อเจ้าของไว้ในผลลัพธ์
CREATE TABLE accounts (
account_id INT PRIMARY KEY,
owner_name VARCHAR(100) NOT NULL
);
INSERT INTO accounts (account_id, owner_name) VALUES
(1, 'Alice'),
(2, 'Bob');
INSERT INTO account_events (account_id, event_type, amount, created_at) VALUES
(2, 'deposit', 2000.00, '2024-01-02 10:00:00+00'),
(2, 'withdrawal', 400.00, '2024-01-06 11:00:00+00');
WITH rebuilt_balances AS (
SELECT
account_id,
SUM(CASE event_type WHEN 'deposit' THEN amount WHEN 'withdrawal' THEN -amount ELSE 0 END) AS balance
FROM account_events
GROUP BY account_id
)
SELECT
a.account_id,
a.owner_name,
rb.balance
FROM accounts a
JOIN rebuilt_balances rb USING (account_id)
ORDER BY a.account_id;ตรวจสอบความเข้าใจ
ทดสอบความเข้าใจของคุณเกี่ยวกับการสร้างสถานะใหม่จากเหตุการณ์ด้วย SQL
สรุปบทเรียน
ในบทเรียนนี้ คุณได้เรียนรู้วิธีสร้างสถานะปัจจุบันและสถานะในอดีตขึ้นใหม่จากบันทึกเหตุการณ์ที่แก้ไขไม่ได้ โดยใช้ SQL
ประเด็นสำคัญ:
- สถานะได้มาจากการรวมเหตุการณ์ (การรวมข้อมูล) ด้วยนิพจน์
CASEที่กำหนดเครื่องหมายไว้ภายในSUM - การเพิ่มตัวกรองเวลาเปิดให้ใช้คิวรี ณ เวลาใดเวลาหนึ่งได้โดยไม่ต้องทำอะไรเพิ่มเติม
- ฟังก์ชันหน้าต่างสร้างสถานะสะสมหลังเหตุการณ์แต่ละรายการ
- ตารางภาพสถานะจัดเก็บผลลัพธ์ที่รวมแล้วเป็นข้อมูลจริงเพื่อประสิทธิภาพ ส่วนการปรับปรุงแบบเพิ่มทีละส่วนจะใช้เฉพาะเหตุการณ์ใหม่
- ตารางตามเวลาที่จำลองขึ้นจะจัดเก็บแถวสถานะที่รวมไว้ล่วงหน้า พร้อมช่วงเวลาที่มีผล เพื่อให้ค้นหาประวัติได้อย่างรวดเร็ว
- CTE ช่วยให้คิวรีสร้างสถานะใหม่อ่านง่ายเมื่อต้องเชื่อมสถานะที่คำนวณได้เข้ากับตารางอื่น
รูปแบบเหล่านี้เป็นพื้นฐานของการออกแบบฐานข้อมูลที่ใช้การจัดเก็บเหตุการณ์และเอื้อต่อการตรวจสอบ
เรียนรู้ SQL ด้วย AI tutor — ฟรี
เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป
- คอร์ส
- 46
- บทเรียน
- 183
คำถามที่พบบ่อย
บทเรียน “การสร้างสถานะขึ้นใหม่จากเหตุการณ์” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การสร้างสถานะขึ้นใหม่จากเหตุการณ์” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส SQL Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส SQL Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การสร้างสถานะขึ้นใหม่จากเหตุการณ์”
รวมเหตุการณ์เข้ากับสถานะปัจจุบัน คุณปฏิบัติ SQL Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน SQL Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน SQL Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน
บทเรียน “การสร้างสถานะขึ้นใหม่จากเหตุการณ์” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน SQL Academy นี้ได้ไหม
ได้ บทเรียน SQL Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- เหตุผลที่ควรเก็บประวัติ
- ตารางเหตุการณ์แบบเพิ่มข้อมูลอย่างเดียว
- แถวแบบเวลาสัมพันธ์และแบบมีรุ่น
- การสร้างสถานะขึ้นใหม่จากเหตุการณ์