การออกแบบฐานข้อมูล: วิธีจัดการกับปัญหา "เก็บถาวร"?


18

ฉันค่อนข้างมั่นใจว่ามีแอปพลิเคชั่นมากมายแอปพลิเคชันที่สำคัญธนาคารและอื่น ๆ ทำสิ่งนี้เป็นประจำทุกวัน

แนวคิดเบื้องหลังทั้งหมดคือ:

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

และอื่น ๆ

นี่คือสิ่งที่ฉันต้องการจะทำและฉันจะอธิบายปัญหาที่ฉันเผชิญ

ตารางทั้งหมดของฉันจะมีคอลัมน์เหล่านี้:

  • id
  • id_origin
  • date of creation
  • start date of validity
  • start end of validity

และนี่คือแนวคิดสำหรับการดำเนินการ CRUD:

  • สร้าง = แทรกแถวใหม่ด้วยid_origin= id, date of creation= ตอนนี้start date of validity= ตอนนี้end date of validity= null (= หมายความว่าเป็นระเบียนที่ใช้งานในปัจจุบัน)
  • ปรับปรุง =
    • read = อ่านบันทึกทั้งหมดที่มีend date of validity== null
    • อัปเดตระเบียน "ปัจจุบัน" end date of validity= null ด้วยend date of validity= ทันที
    • สร้างรายการใหม่ด้วยค่าใหม่และend date of validity= null (= หมายถึงเป็นระเบียนที่ใช้งานอยู่ในปัจจุบัน)
  • ลบ = อัปเดตระเบียน "ปัจจุบัน" end date of validity= null ด้วยend date of validity= ตอนนี้

ดังนั้นนี่คือปัญหาของฉัน: กับการเชื่อมโยงหลายต่อหลายคน ลองมาตัวอย่างด้วยค่า:

  • ตาราง A (id = 1, id_origin = 1, start = now, end = null)
  • ตาราง A_B (เริ่ม = ตอนนี้สิ้นสุด = null, id_A = 1, id_B = 48)
  • ตาราง B (id = 48, id_origin = 48, start = now, end = null)

ตอนนี้ฉันต้องการอัปเดตตาราง A, บันทึก id = 1

  • ฉันทำเครื่องหมายบันทึก id = 1 ด้วยสิ้นสุด = ตอนนี้
  • ฉันใส่ค่าใหม่ลงในตาราง A และ ... เจ้ากรรมฉันสูญเสียความสัมพันธ์ของฉันไปแล้ว A_B ยกเว้นว่าฉันทำซ้ำความสัมพันธ์เช่นกัน ... สิ่งนี้จะจบลงที่ตาราง:

  • ตาราง A (id = 1, id_origin = 1, start = now, end = now + 8mn)

  • ตาราง A (id = 2, id_origin = 1, start = now + 8mn, end = null)
  • ตาราง A_B (เริ่ม = ตอนนี้สิ้นสุด = null, id_A = 1, id_B = 48)
  • ตาราง A_B (เริ่ม = ตอนนี้สิ้นสุด = null, id_A = 2, id_B = 48)
  • ตาราง B (id = 48, id_origin = 48, start = now, end = null)

และ ... ฉันมีปัญหาอื่น: ความสัมพันธ์ A_B: ฉันจะทำเครื่องหมาย (id_A = 1, id_B = 48) ว่าล้าสมัยหรือไม่ (A - id = 1 ล้าสมัย แต่ไม่ใช่ B - 48)

วิธีจัดการกับสิ่งนี้?

ฉันต้องออกแบบสิ่งเหล่านี้ในขนาดใหญ่: ผลิตภัณฑ์พันธมิตรและอื่น ๆ

ประสบการณ์ของคุณเกี่ยวกับเรื่องนี้คืออะไร? คุณจะทำอย่างไร (คุณทำอย่างไร)

- แก้ไข

ฉันได้พบบทความที่น่าสนใจนี้แต่ไม่ได้จัดการอย่างถูกต้องกับ "cascasding obsolescence" (= สิ่งที่ฉันถามจริง ๆ )


วิธีการเกี่ยวกับการคัดลอกข้อมูลของบันทึกการปรับปรุงก่อนที่จะมีการปรับปรุงเป็นใหม่ที่มี ID ใหม่ที่เก็บรายการที่เชื่อมโยงของประวัติด้วยเขตข้อมูล id_hist_prev ดังนั้นรหัสของบันทึกปัจจุบันจึงไม่เปลี่ยนแปลง

คุณคิดที่จะใช้วงล้อใหม่หรือไม่ตัวอย่างเช่นFlashback Data Archiveบน Oracle?
แจ็คดักลาส

คำตอบ:


4

ยังไม่ชัดเจนสำหรับฉันหากข้อกำหนดเหล่านี้มีวัตถุประสงค์เพื่อการตรวจสอบหรือเพียงอ้างอิงทางประวัติศาสตร์ง่ายๆเช่นกับ CRM และตะกร้าสินค้า

ทั้งสองวิธีพิจารณามีตาราง main และ main_archive สำหรับแต่ละพื้นที่หลักที่จำเป็นต้องใช้ "Main" จะมีเฉพาะรายการปัจจุบัน / ที่ใช้งานอยู่ในขณะที่ "main_archive" จะมีสำเนาของทุกสิ่งที่เคยเป็นหลัก การแทรก / อัพเดตลงใน main_archive สามารถเป็นทริกเกอร์จากการแทรก / อัปเดตเป็นหลัก การลบกับ main_archive นั้นจะสามารถทำงานได้ในช่วงระยะเวลาที่นานขึ้นหากเคย

สำหรับปัญหาการอ้างอิงเช่น Cust X ซื้อผลิตภัณฑ์ Y วิธีที่ง่ายที่สุดในการแก้ปัญหาการอ้างอิงของคุณ cust_archive -> product_archive คือการไม่ลบรายการจาก product_archive โดยทั่วไปแล้วปั่นควรจะต่ำกว่ามากในตารางนั้นดังนั้นขนาดไม่ควรกังวลมากเกินไป

HTH


2
คำตอบที่ดี แต่ฉันต้องการเพิ่มว่าประโยชน์ของการมีตารางเก็บถาวรก็คือพวกเขามักจะถูกทำให้เป็นปกติทำให้การรายงานข้อมูลดังกล่าวมีประสิทธิภาพมากขึ้น พิจารณาความต้องการการรายงานแอปพลิเคชันของคุณด้วยวิธีนี้เช่นกัน
maple_shaft

1
ในฐานข้อมูลส่วนใหญ่ฉันออกแบบตาราง 'main' ทั้งหมดมีคำนำหน้าชื่อผลิตภัณฑ์เหมือนLP_กันและทุกตารางสำคัญมีความหมายเทียบเท่าLH_โดยทริกเกอร์การแทรกแถวประวัติในการแทรกปรับปรุงลบ มันใช้งานไม่ได้กับทุกกรณี แต่มันก็เป็นแบบอย่างที่ดีสำหรับสิ่งที่ฉันทำ

ฉันเห็นด้วย - หากคำค้นหาส่วนใหญ่ใช้สำหรับแถว "ปัจจุบัน" คุณอาจได้รับประโยชน์อย่างสมบูรณ์โดยแบ่งพาร์ติชันปัจจุบันออกจากประวัติในสองตาราง มุมมองที่สามารถรวมพวกเขากลับมารวมกันเพื่อความสะดวก วิธีนี้หน้าข้อมูลที่มีแถวปัจจุบันทั้งหมดเข้าด้วยกันและอาจอยู่ในแคชที่ดีขึ้นและคุณไม่จำเป็นต้องมีการค้นหาที่เหมาะสมสำหรับข้อมูลปัจจุบันด้วยตรรกะวันที่
onupdatecascade

1
@onupdatecascade: โปรดทราบว่า (อย่างน้อยใน RDBMS บางรายการ) คุณสามารถใส่ดัชนีในUNIONมุมมองนั้นซึ่งช่วยให้คุณทำสิ่งที่ยอดเยี่ยมเช่นบังคับใช้ข้อ จำกัด ที่ไม่ซ้ำกันทั้งในปัจจุบันและในอดีต
Jon of All Trades

5 ปีต่อมาฉันทำสิ่งต่าง ๆ มากมายและตลอดเวลาที่คุณได้รับความคิดของคุณกลับมา สิ่งเดียวที่ฉันเปลี่ยนคือในตารางประวัติฉันมีคอลัมน์ " id" และ " id_ref" id_refเป็นการอ้างอิงถึงความคิดที่แท้จริงของตาราง ตัวอย่าง: และperson person_hในperson_hฉันมี " id" และ " id_ref" ที่id_refเกี่ยวข้องกับ ' person.id' ดังนั้นฉันจึงสามารถมีหลายแถวที่มีเหมือนกันperson.id(= เมื่อมีการpersonแก้ไขแถว) และทุกidตารางของฉันทั้งหมดเป็น autoinc
Olivier Pons

2

สิ่งนี้มีการทับซ้อนกับการเขียนโปรแกรมฟังก์ชั่น; โดยเฉพาะแนวคิดของการไม่เปลี่ยนรูป

คุณมีตารางหนึ่งที่เรียกว่าPRODUCTและอีกหนึ่งเรียกว่าPRODUCTVERSIONหรือคล้ายกัน เมื่อคุณเปลี่ยนผลิตภัณฑ์ที่คุณไม่ได้ทำการอัพเดตคุณเพียงแค่แทรกPRODUCTVERSIONแถวใหม่ หากต้องการรับข้อมูลล่าสุดคุณสามารถสร้างดัชนีตารางตามหมายเลขเวอร์ชัน (เรียงลำดับ), ประทับเวลา (เรียงลำดับ) หรือคุณสามารถตั้งค่าสถานะ ( LatestVersion)

ตอนนี้ถ้าคุณมีสิ่งที่อ้างอิงถึงผลิตภัณฑ์คุณสามารถตัดสินใจว่าจะให้ตารางใดชี้ไปที่ มันชี้ไปที่PRODUCTเอนทิตี (หมายถึงผลิตภัณฑ์นี้เสมอ) หรือPRODUCTVERSIONเอนทิตี (หมายถึงผลิตภัณฑ์รุ่นนี้เท่านั้น)?

มันซับซ้อน เกิดอะไรขึ้นถ้าคุณมีภาพของผลิตภัณฑ์? พวกเขาจะต้องชี้ไปที่ตารางเวอร์ชันเนื่องจากอาจมีการเปลี่ยนแปลง แต่ในหลายกรณีพวกเขาจะไม่และคุณไม่ต้องการทำซ้ำข้อมูลโดยไม่จำเป็น นั่นหมายความว่าคุณต้องการPICTUREโต๊ะและPRODUCTVERSIONPICTUREความสัมพันธ์แบบกลุ่มต่อกลุ่ม


1

ฉันใช้ทุกสิ่งจากที่นี่ด้วย 4 ฟิลด์ที่อยู่ในตารางทั้งหมดของฉัน:

  • รหัส
  • date_creation
  • date_validity_start
  • date_validity_end

ทุกครั้งที่มีการบันทึกจะต้องมีการปรับเปลี่ยนผมซ้ำทำเครื่องหมายซ้ำบันทึกไว้เป็น "เก่า" = date_validity_end=NOW()และหนึ่งในปัจจุบันเป็นหนึ่งที่ดีและdate_validity_start=NOW()date_validity_end=NULL

เคล็ดลับเป็นเรื่องเกี่ยวกับความสัมพันธ์หลายต่อหลายคนและหนึ่งต่อหลายความสัมพันธ์: มันทำงานได้โดยไม่ต้องสัมผัสพวกเขา! มันคือทั้งหมดที่เกี่ยวกับการค้นหาที่ซับซ้อนมากขึ้น: การสืบค้นระเบียนในวันที่ที่แน่นอน (= ไม่ใช่ตอนนี้) ฉันมีสำหรับการเข้าร่วมแต่ละครั้งและสำหรับตารางหลักเพื่อเพิ่มข้อ จำกัด เหล่านั้น:

WHERE (
  (date_validity_start<=:dateparam AND date_validity_end IS NULL)
  OR
  (date_validity_start<=:dateparam AND date_validity_start>=:dateparam)
)

ดังนั้นด้วยผลิตภัณฑ์และคุณลักษณะ (หลายต่อหลายความสัมพันธ์):

SELECT p.*,a.*

FROM products p

JOIN products_attributes pa
ON pa.id_product = p.id
AND (
  (pa.date_validity_start<=:dateparam AND pa.date_validity_end IS NULL)
  OR
  (pa.date_validity_start<=:dateparam AND pa.date_validity_start>=:dateparam)
)

JOIN attributes a
ON a.id = pa.id_attribute
AND (
  (a.date_validity_start<=:dateparam AND a.date_validity_end IS NULL)
  OR
  (a.date_validity_start<=:dateparam AND a.date_validity_start>=:dateparam)
)

WHERE (
  (p.date_validity_start<=:dateparam AND p.date_validity_end IS NULL)
  OR
  (p.date_validity_start<=:dateparam AND p.date_validity_start>=:dateparam)
)

0

แล้วเรื่องนี้ล่ะ ดูเหมือนง่ายและมีประสิทธิภาพสำหรับสิ่งที่ฉันทำในอดีต ในตาราง "ประวัติ" ของคุณใช้ PK อื่น ดังนั้นฟิลด์ "CustomerID" ของคุณคือ PK ในตารางลูกค้าของคุณ แต่ในตาราง "ประวัติ" PK ของคุณคือ "NewCustomerID" "CustomerID" เป็นเพียงอีกฟิลด์อ่านอย่างเดียว สิ่งนี้ทำให้ "CustomerID" ไม่เปลี่ยนแปลงในประวัติและความสัมพันธ์ทั้งหมดของคุณยังคงอยู่


ความคิดที่ดีมาก สิ่งที่ฉันทำมีลักษณะคล้ายกันมาก: ฉันทำซ้ำบันทึกและทำเครื่องหมายใหม่เป็น "ล้าสมัย" เพื่อให้ระเบียนปัจจุบันยังคงเหมือนเดิม หมายเหตุฉันต้องการสร้างทริกเกอร์ในแต่ละตาราง แต่ mysql ห้ามปรับเปลี่ยนตารางเมื่อคุณเป็นทริกเกอร์ของตารางนี้ PostGRESQL ทำสิ่งนี้ เซิร์ฟเวอร์ SQL ทำสิ่งนี้ ออราเคิลทำเช่นนี้ ในระยะสั้น MySQL ยังคงมีวิธีไปนานและครั้งต่อไปฉันจะคิดสองครั้งเมื่อเลือกเซิร์ฟเวอร์ฐานข้อมูลของฉัน
Olivier Pons
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.