การออกแบบฐานข้อมูลสำหรับการแก้ไข?


125

เรามีข้อกำหนดในโครงการที่จะจัดเก็บการแก้ไขทั้งหมด (ประวัติการเปลี่ยนแปลง) สำหรับเอนทิตีในฐานข้อมูล ขณะนี้เรามี 2 ข้อเสนอที่ออกแบบไว้สำหรับสิ่งนี้:

เช่นสำหรับนิติบุคคล "พนักงาน"

การออกแบบ 1:

-- Holds Employee Entity
"Employees (EmployeeId, FirstName, LastName, DepartmentId, .., ..)"

-- Holds the Employee Revisions in Xml. The RevisionXML will contain
-- all data of that particular EmployeeId
"EmployeeHistories (EmployeeId, DateModified, RevisionXML)"

การออกแบบ 2:

-- Holds Employee Entity
"Employees (EmployeeId, FirstName, LastName, DepartmentId, .., ..)"

-- In this approach we have basically duplicated all the fields on Employees 
-- in the EmployeeHistories and storing the revision data.
"EmployeeHistories (EmployeeId, RevisionId, DateModified, FirstName, 
      LastName, DepartmentId, .., ..)"

มีวิธีอื่นในการทำสิ่งนี้หรือไม่?

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

และปัญหาของ "Design 2" คือเราต้องทำซ้ำแต่ละฟิลด์ในทุกเอนทิตี (เรามีเอนทิตีประมาณ 70-80 เอนทิตีที่เราต้องการรักษาการแก้ไข)


3
ที่เกี่ยวข้อง: stackoverflow.com/questions/9852703/…
Kaii

1
FYI: ในกรณีนี้อาจช่วยได้เซิร์ฟเวอร์ SQL 2008 ขึ้นไปมีเทคโนโลยีที่แสดงประวัติการเปลี่ยนแปลงบนโต๊ะ.. เยี่ยมชมsimple-talk.com/sql/learn-sql-server/…เพื่อทราบข้อมูลเพิ่มเติมและฉันแน่ใจว่า DB เช่น Oracle จะมีบางอย่างเช่นนี้
Durai Amuthan H

โปรดทราบว่าบางคอลัมน์สามารถจัดเก็บ XML หรือ JSON ได้ หากไม่เป็นเช่นนั้นตอนนี้อาจเกิดขึ้นได้ในอนาคต ตรวจสอบให้แน่ใจว่าคุณไม่จำเป็นต้องซ้อนข้อมูลดังกล่าวไว้ที่อื่น
jakubiszon

คำตอบ:


38
  1. ทำไม่ได้วางไว้ในตารางหนึ่งที่มีแอตทริบิวต์ discriminator IsCurrent สิ่งนี้ทำให้เกิดปัญหาขึ้นต้องใช้คีย์ตัวแทนและปัญหาอื่น ๆ ทุกประเภท
  2. Design 2 มีปัญหากับการเปลี่ยนแปลงสคีมา หากคุณเปลี่ยนตารางพนักงานคุณต้องเปลี่ยนตาราง EmployeeHistories และ sprocs ที่เกี่ยวข้องทั้งหมดที่ไปด้วย อาจเพิ่มความพยายามในการเปลี่ยนแปลงสคีมาของคุณเป็นสองเท่า
  3. การออกแบบ 1 ใช้งานได้ดีและหากทำอย่างถูกต้องจะไม่เสียค่าใช้จ่ายมากนักในแง่ของประสิทธิภาพการทำงาน คุณสามารถใช้สคีมา xml และแม้แต่ดัชนีเพื่อแก้ไขปัญหาด้านประสิทธิภาพที่อาจเกิดขึ้นได้ ความคิดเห็นของคุณเกี่ยวกับการแยกวิเคราะห์ xml นั้นถูกต้อง แต่คุณสามารถสร้างมุมมองได้อย่างง่ายดายโดยใช้ xquery ซึ่งคุณสามารถรวมไว้ในแบบสอบถามและเข้าร่วมได้ อะไรทำนองนี้ ...
CREATE VIEW EmployeeHistory
AS
, FirstName, , DepartmentId

SELECT EmployeeId, RevisionXML.value('(/employee/FirstName)[1]', 'varchar(50)') AS FirstName,

  RevisionXML.value('(/employee/LastName)[1]', 'varchar(100)') AS LastName,

  RevisionXML.value('(/employee/DepartmentId)[1]', 'integer') AS DepartmentId,

FROM EmployeeHistories 

25
ทำไมคุณถึงบอกว่าอย่าเก็บทั้งหมดไว้ในตารางเดียวด้วย IsCurrent trigger คุณช่วยชี้ตัวอย่างให้ฉันฟังได้ไหมว่าปัญหานี้จะเกิดขึ้นได้อย่างไร
Nathan W

@ Simon Munro แล้วคีย์หลักหรือคีย์คลัสเตอร์ล่ะ? เราสามารถเพิ่มคีย์อะไรในตารางประวัติ Design 1 เพื่อให้การค้นหาเร็วขึ้น
gotqn

ผมถือว่าง่ายSELECT * FROM EmployeeHistory WHERE LastName = 'Doe'ส่งผลให้ตารางการสแกนเต็มรูปแบบ ไม่ใช่ความคิดที่ดีที่สุดในการปรับขนาดแอปพลิเคชัน
Kaii

54

ฉันคิดว่าคำถามสำคัญที่จะถามที่นี่คือ 'ใคร / อะไรจะใช้ประวัติศาสตร์'?

หากส่วนใหญ่เป็นการรายงาน / ประวัติที่มนุษย์อ่านได้เราได้ดำเนินโครงการนี้ในอดีต ...

สร้างตารางชื่อ 'AuditTrail' หรือสิ่งที่มีฟิลด์ต่อไปนี้ ...

[ID] [int] IDENTITY(1,1) NOT NULL,
[UserID] [int] NULL,
[EventDate] [datetime] NOT NULL,
[TableName] [varchar](50) NOT NULL,
[RecordID] [varchar](20) NOT NULL,
[FieldName] [varchar](50) NULL,
[OldValue] [varchar](5000) NULL,
[NewValue] [varchar](5000) NULL

จากนั้นคุณสามารถเพิ่มคอลัมน์ 'LastUpdatedByUserID' ลงในตารางทั้งหมดของคุณซึ่งควรตั้งค่าทุกครั้งที่คุณอัปเดต / แทรกบนตาราง

จากนั้นคุณสามารถเพิ่มทริกเกอร์ในทุกตารางเพื่อตรวจจับการแทรก / อัปเดตที่เกิดขึ้นและสร้างรายการในตารางนี้สำหรับแต่ละฟิลด์ที่มีการเปลี่ยนแปลง เนื่องจากตารางนี้มี 'LastUpdateByUserID' สำหรับการอัพเดต / แทรกแต่ละรายการด้วยคุณจึงสามารถเข้าถึงค่านี้ในทริกเกอร์และใช้เมื่อเพิ่มลงในตารางการตรวจสอบ

เราใช้ฟิลด์ RecordID เพื่อเก็บค่าของฟิลด์คีย์ของตารางที่กำลังอัปเดต ถ้าเป็นคีย์รวมเราก็ทำการต่อสตริงโดยมีเครื่องหมาย '~' ระหว่างฟิลด์

ฉันแน่ใจว่าระบบนี้อาจมีข้อเสีย - สำหรับฐานข้อมูลที่อัปเดตอย่างหนักประสิทธิภาพอาจได้รับผลกระทบ แต่สำหรับเว็บแอปของฉันเราได้รับการอ่านมากกว่าการเขียนและดูเหมือนว่าจะทำงานได้ค่อนข้างดี เรายังเขียนยูทิลิตี้ VB.NET เล็กน้อยเพื่อเขียนทริกเกอร์โดยอัตโนมัติตามคำจำกัดความของตาราง

แค่คิด!


5
ไม่จำเป็นต้องจัดเก็บ NewValue เนื่องจากเก็บไว้ในตารางที่ตรวจสอบแล้ว
Petrus Theron

17
พูดอย่างเคร่งครัดนั่นคือความจริง แต่ - เมื่อมีการเปลี่ยนแปลงจำนวนมากในฟิลด์เดียวกันในช่วงเวลาหนึ่งการจัดเก็บค่าใหม่จะทำให้การสืบค้นเช่น 'แสดงการเปลี่ยนแปลงทั้งหมดที่ไบรอันทำ' ง่ายขึ้นมากเนื่องจากข้อมูลทั้งหมดเกี่ยวกับการอัปเดตหนึ่งรายการจะถูกเก็บไว้ใน หนึ่งบันทึก แค่คิด!
Chris Roberts

1
ฉันคิดว่าsysnameอาจเป็นชนิดข้อมูลที่เหมาะสมกว่าสำหรับชื่อตารางและคอลัมน์
แซม

2
@Sam โดยใช้ sysname ไม่ได้เพิ่มมูลค่าใด ๆ มันอาจจะสับสน ... stackoverflow.com/questions/5720212/…
Jowen

19

ประวัติตารางบทความในโปรแกรมฐานข้อมูลบล็อกอาจจะมีประโยชน์ - ครอบคลุมบางส่วนของจุดที่เกิดขึ้นที่นี่และกล่าวถึงการจัดเก็บสันดอน

แก้ไข

ในเรียงความHistory Tablesผู้เขียน ( Kenneth Downs ) แนะนำให้ดูแลตารางประวัติอย่างน้อยเจ็ดคอลัมน์:

  1. การประทับเวลาของการเปลี่ยนแปลง
  2. ผู้ใช้ที่ทำการเปลี่ยนแปลง
  3. โทเค็นเพื่อระบุบันทึกที่มีการเปลี่ยนแปลง (โดยที่ประวัติจะถูกเก็บแยกจากสถานะปัจจุบัน)
  4. การเปลี่ยนแปลงนั้นเป็นการแทรกอัปเดตหรือลบ
  5. ค่าเก่า
  6. ค่าใหม่
  7. เดลต้า (สำหรับการเปลี่ยนแปลงค่าตัวเลข)

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

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


14

เราได้ใช้โซลูชันที่คล้ายกับโซลูชันที่ Chris Roberts แนะนำและได้ผลดีสำหรับเรา

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

[ID] [int] IDENTITY(1,1) NOT NULL,
[UserID] [int] NULL,
[EventDate] [datetime] NOT NULL,
[TableName] [varchar](50) NOT NULL,
[RecordID] [varchar](20) NOT NULL,
[FieldName] [varchar](50) NULL,
[NewValue] [varchar](5000) NULL

สมมติว่าคุณมีตารางที่มี 20 คอลัมน์ ด้วยวิธีนี้คุณจะต้องเก็บคอลัมน์ที่แน่นอนที่เปลี่ยนแปลงแทนที่จะต้องเก็บทั้งแถว


14

หลีกเลี่ยงการออกแบบ 1; มันไม่สะดวกมากนักเมื่อคุณจำเป็นต้องย้อนกลับไปเป็นเวอร์ชันเก่าของเรกคอร์ดไม่ว่าจะโดยอัตโนมัติหรือ "ด้วยตนเอง" โดยใช้คอนโซลผู้ดูแลระบบ

ฉันไม่เห็นข้อเสียของการออกแบบ 2 จริงๆฉันคิดว่าอย่างที่สองตารางประวัติควรมีคอลัมน์ทั้งหมดที่มีอยู่ในตารางระเบียนแรก เช่นใน mysql คุณสามารถสร้างตารางที่มีโครงสร้างเดียวกันกับตารางอื่น ( create table X like Y) ได้อย่างง่ายดาย และเมื่อคุณกำลังจะเปลี่ยนโครงสร้างของตารางเรกคอร์ดในฐานข้อมูลสดของคุณคุณต้องใช้alter tableคำสั่งต่อไป - และไม่มีความพยายามอย่างมากในการเรียกใช้คำสั่งเหล่านี้สำหรับตารางประวัติของคุณ

หมายเหตุ

  • ตารางบันทึกประกอบด้วยการแก้ไขล่าสุดเท่านั้น
  • ตารางประวัติประกอบด้วยการแก้ไขระเบียนก่อนหน้านี้ทั้งหมดในตารางระเบียน
  • คีย์หลักของตารางประวัติเป็นคีย์หลักของตารางระเบียนที่มีการเพิ่ม RevisionIdคอลัมน์
  • ลองนึกถึงช่องเสริมเพิ่มเติมเช่นModifiedBy- ผู้ใช้ที่สร้างการแก้ไขโดยเฉพาะ คุณอาจต้องการมีเขตข้อมูลDeletedByสำหรับติดตามว่าใครเป็นผู้ลบการแก้ไขโดยเฉพาะ
  • ลองคิดดูว่าDateModifiedควรหมายถึงอะไร - ไม่ว่าจะหมายถึงที่ใดที่มีการสร้างการแก้ไขนี้หรือจะหมายถึงเมื่อการแก้ไขนี้ถูกแทนที่ด้วยการแก้ไขอื่น อดีตต้องการให้ฟิลด์อยู่ในตารางบันทึกและดูเหมือนจะใช้งานง่ายกว่าตั้งแต่แรกเห็น วิธีที่สองดูเหมือนจะใช้งานได้จริงกว่าสำหรับบันทึกที่ถูกลบ (วันที่ที่การแก้ไขนี้ถูกลบออก) หากคุณใช้วิธีแก้ปัญหาแรกคุณอาจต้องใช้ช่องที่สองDateDeleted(เฉพาะในกรณีที่คุณต้องการแน่นอน) ขึ้นอยู่กับคุณและสิ่งที่คุณต้องการบันทึกจริงๆ

การดำเนินการในการออกแบบ 2 เป็นเรื่องเล็กน้อยมาก:

ปรับเปลี่ยน
  • คัดลอกระเบียนจากตาราง Records ไปยังตาราง History ให้ RevisionId ใหม่ (หากยังไม่มีอยู่ในตาราง Records) จัดการ DateModified (ขึ้นอยู่กับว่าคุณตีความอย่างไรโปรดดูบันทึกด้านบน)
  • ดำเนินการอัปเดตตามปกติของระเบียนในตารางระเบียน
ลบ
  • ทำเช่นเดียวกับในขั้นตอนแรกของการดำเนินการ Modify จัดการ DateModified / DateDeleted ตามขึ้นอยู่กับการตีความที่คุณเลือก
ยกเลิกการลบ (หรือย้อนกลับ)
  • ใช้การแก้ไขสูงสุด (หรือบางอย่าง?) จากตารางประวัติและคัดลอกไปยังตารางบันทึก
รายการประวัติการแก้ไขสำหรับบันทึกเฉพาะ
  • เลือกจากตารางประวัติและตารางบันทึก
  • คิดว่าคุณคาดหวังอะไรจากการดำเนินการนี้ มันอาจจะกำหนดข้อมูลที่คุณต้องการจากฟิลด์ DateModified / DateDeleted (ดูหมายเหตุด้านบน)

หากคุณไปที่ Design 2 คำสั่ง SQL ทั้งหมดที่จำเป็นในการทำเช่นนั้นจะง่ายมากเช่นเดียวกับการบำรุงรักษา! บางทีมันจะง่ายกว่ามากถ้าคุณใช้คอลัมน์เสริม ( RevisionId, DateModified) ในตารางเรกคอร์ดด้วย - เพื่อให้ทั้งสองตารางมีโครงสร้างเดียวกัน (ยกเว้นคีย์ที่ไม่ซ้ำกัน)! สิ่งนี้จะอนุญาตให้มีคำสั่ง SQL แบบง่ายซึ่งจะทนต่อการเปลี่ยนแปลงโครงสร้างข้อมูลใด ๆ :

insert into EmployeeHistory select * from Employe where ID = XX

อย่าลืมใช้ธุรกรรม!

สำหรับการปรับขนาดโซลูชันนี้มีประสิทธิภาพมากเนื่องจากคุณไม่ได้แปลงข้อมูลใด ๆ จาก XML ไปมาเพียงแค่คัดลอกแถวในตารางทั้งหมด - แบบสอบถามง่ายๆโดยใช้ดัชนี - มีประสิทธิภาพมาก


12

หากคุณต้องจัดเก็บประวัติให้สร้างตารางเงาที่มีสคีมาเดียวกับตารางที่คุณกำลังติดตามและคอลัมน์ 'วันที่แก้ไข' และ 'ประเภทการแก้ไข' (เช่น 'ลบ', 'อัปเดต') เขียน (หรือสร้าง - ดูด้านล่าง) ชุดทริกเกอร์เพื่อเติมข้อมูลในตารางการตรวจสอบ

ค่อนข้างตรงไปตรงมาในการสร้างเครื่องมือที่จะอ่านพจนานุกรมข้อมูลระบบสำหรับตารางและสร้างสคริปต์ที่สร้างตารางเงาและชุดทริกเกอร์เพื่อเติมข้อมูล

อย่าพยายามใช้ XML สำหรับสิ่งนี้การจัดเก็บ XML มีประสิทธิภาพน้อยกว่าการจัดเก็บตารางฐานข้อมูลดั้งเดิมที่ทริกเกอร์ประเภทนี้ใช้


3
+1 เพื่อความเรียบง่าย! บางคนจะเกินวิศวกรเพราะกลัวการเปลี่ยนแปลงในภายหลังในขณะที่ส่วนใหญ่ไม่มีการเปลี่ยนแปลงเกิดขึ้นจริง! นอกจากนี้ยังง่ายกว่ามากในการจัดการประวัติในตารางเดียวและบันทึกจริงในอีกตารางหนึ่งมากกว่าการรวมไว้ในตารางเดียว (ฝันร้าย) ด้วยธงหรือสถานะบางอย่าง เรียกว่า 'KISS' และโดยปกติแล้วจะให้รางวัลคุณในระยะยาว
Jeach

+1 เห็นด้วยอย่างยิ่งกับสิ่งที่ฉันพูดในคำตอบของฉัน ! เรียบง่ายและทรงพลัง!
TMS

8

Ramesh ฉันมีส่วนร่วมในการพัฒนาระบบตามแนวทางแรก
ปรากฎว่าการจัดเก็บการแก้ไขเป็น XML นำไปสู่การเติบโตของฐานข้อมูลจำนวนมากและทำให้สิ่งต่างๆช้าลงอย่างมาก
แนวทางของฉันคือมีหนึ่งตารางต่อเอนทิตี:

Employee (Id, Name, ... , IsActive)  

โดยที่IsActiveเป็นสัญญาณของเวอร์ชันล่าสุด

หากคุณต้องการเชื่อมโยงข้อมูลเพิ่มเติมกับการแก้ไขคุณสามารถสร้างตารางแยกต่างหากที่มีข้อมูลนั้นและเชื่อมโยงกับตารางเอนทิตีโดยใช้ความสัมพันธ์ PK \ FK

ด้วยวิธีนี้คุณสามารถจัดเก็บพนักงานทุกรุ่นไว้ในตารางเดียว ข้อดีของแนวทางนี้:

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

โปรดทราบว่าคุณควรอนุญาตให้คีย์หลักไม่ซ้ำกัน


6
ฉันจะใช้คอลัมน์ "RevisionNumber" หรือ "RevisionDate" แทนหรือนอกเหนือจาก IsActive เพื่อให้คุณสามารถดูการแก้ไขทั้งหมดตามลำดับ
Sklivvz

ฉันจะใช้ "parentRowId" เพราะช่วยให้คุณเข้าถึงเวอร์ชันก่อนหน้าได้อย่างง่ายดายรวมถึงความสามารถในการค้นหาทั้งฐานและจุดสิ้นสุดได้อย่างรวดเร็ว
chacham15

6

วิธีที่ฉันเคยเห็นในอดีตก็มี

Employees (EmployeeId, DateModified, < Employee Fields > , boolean isCurrent );

คุณไม่เคย "อัปเดต" บนตารางนี้ (ยกเว้นจะเปลี่ยน isCurrent ที่ถูกต้อง) เพียงแค่แทรกแถวใหม่ สำหรับ EmployeeId ใด ๆ ที่กำหนดมีเพียง 1 แถวเท่านั้นที่สามารถมี isCurrent == 1

ความซับซ้อนของการดูแลรักษาสิ่งนี้สามารถซ่อนได้ด้วยมุมมองและทริกเกอร์ "แทนที่จะเป็น" (ใน oracle ฉันคิดว่าสิ่งที่คล้ายกัน RDBMS อื่น ๆ ) คุณยังสามารถไปที่มุมมองที่เป็นรูปธรรมได้หากตารางใหญ่เกินไปและดัชนีไม่สามารถจัดการได้) .

วิธีนี้ใช้ได้ แต่คุณสามารถตอบคำถามที่ซับซ้อนได้

โดยส่วนตัวแล้วฉันค่อนข้างชอบการออกแบบ 2 วิธีของคุณซึ่งเป็นวิธีที่ฉันเคยทำในอดีตเช่นกัน ง่ายต่อการเข้าใจใช้งานง่ายและบำรุงรักษาง่าย

นอกจากนี้ยังสร้างค่าใช้จ่ายน้อยมากสำหรับฐานข้อมูลและแอปพลิเคชันโดยเฉพาะอย่างยิ่งเมื่อดำเนินการอ่านแบบสอบถามซึ่งเป็นไปได้ว่าคุณจะทำอะไรอยู่ 99% ของเวลา

นอกจากนี้ยังค่อนข้างง่ายในการสร้างตารางประวัติและทริกเกอร์เพื่อรักษาโดยอัตโนมัติ (สมมติว่าจะทำผ่านทริกเกอร์)


4

การแก้ไขข้อมูลเป็นลักษณะของแนวคิด ' เวลาที่ถูกต้อง ' ของฐานข้อมูลชั่วคราว มีการวิจัยมากมายในเรื่องนี้และมีรูปแบบและแนวทางมากมายเกิดขึ้น ฉันเขียนคำตอบยาว ๆ พร้อมกับการอ้างอิงจำนวนมากสำหรับคำถามนี้สำหรับผู้ที่สนใจ


4

ฉันจะแบ่งปันการออกแบบของฉันกับคุณและมันแตกต่างจากการออกแบบทั้งสองของคุณตรงที่ต้องใช้ตารางเดียวต่อเอนทิตีแต่ละประเภท ฉันพบวิธีที่ดีที่สุดในการอธิบายการออกแบบฐานข้อมูลผ่าน ERD นี่คือของฉัน:

ใส่คำอธิบายภาพที่นี่

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

สองช่องของentity_idและrevision_id

แต่ละเอนทิตีในระบบของคุณจะมี ID เอนทิตีเฉพาะของตัวเอง เอนทิตีของคุณอาจผ่านการแก้ไข แต่ entity_id จะยังคงเหมือนเดิม คุณต้องเก็บรหัสเอนทิตีนี้ไว้ในตารางพนักงานของคุณ (เป็นคีย์ต่างประเทศ) คุณควรจัดเก็บประเภทของเอนทิตีของคุณในตารางเอนทิตี (เช่น 'พนักงาน') ตอนนี้สำหรับ revision_id ตามชื่อที่แสดงจะติดตามการแก้ไขเอนทิตีของคุณ วิธีที่ดีที่สุดที่ฉันพบคือการใช้รหัสพนักงานเป็น revision_id ของคุณ ซึ่งหมายความว่าคุณจะมีรหัสการแก้ไขที่ซ้ำกันสำหรับเอนทิตีประเภทต่างๆ แต่นี่ไม่ใช่เรื่องสำคัญสำหรับฉัน (ฉันไม่แน่ใจเกี่ยวกับกรณีของคุณ) ข้อควรทราบประการเดียวที่ควรทราบคือการรวมกันของ entity_id และ revision_id ควรไม่ซ้ำกัน

นอกจากนี้ยังมีฟิลด์สถานะภายในตารางentity_revisionซึ่งระบุสถานะของการแก้ไข มันสามารถมีหนึ่งในสามรัฐ: latest, obsoleteหรือdeleted (ไม่ได้อาศัยอยู่ในวันที่แก้ไขจะช่วยให้คุณจัดการที่ดีเพื่อเพิ่มคำสั่งของคุณ)

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

แทรก

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

UPDATE

การอัปเดตแต่ละรายการสำหรับเรกคอร์ดพนักงานที่มีอยู่จะถูกนำไปใช้เป็นส่วนแทรกสองรายการหนึ่งในตารางพนักงานและอีกรายการหนึ่งใน entity_revision อันที่สองจะช่วยให้คุณทราบว่าใครและเมื่อใดที่บันทึกได้รับการอัปเดต

การลบ

สำหรับการลบพนักงานบันทึกจะถูกแทรกลงใน entity_revision โดยระบุการลบและเสร็จสิ้น

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

[อัปเดต]

ด้วยพาร์ติชันที่รองรับใน MySQL เวอร์ชันใหม่ฉันเชื่อว่าการออกแบบของฉันก็มาพร้อมกับการแสดงที่ดีที่สุดเช่นกัน หนึ่งสามารถแบ่งentityตารางโดยใช้typeฟิลด์ในขณะที่พาร์ติชันentity_revisionโดยใช้stateฟิลด์ สิ่งนี้จะช่วยเพิ่มการSELECTค้นหาในขณะที่ทำให้การออกแบบเรียบง่ายและสะอาดตา


3

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

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


3

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

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

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


2

เกี่ยวกับ:

  • EmployeeID
  • DateModified
    • และ / หรือหมายเลขการแก้ไขขึ้นอยู่กับว่าคุณต้องการติดตามอย่างไร
  • ModifiedByUSerId
    • รวมทั้งข้อมูลอื่น ๆ ที่คุณต้องการติดตาม
  • ฟิลด์พนักงาน

คุณสร้างคีย์หลัก (EmployeeId, DateModified) และในการรับระเบียน "ปัจจุบัน" คุณเพียงแค่เลือก MAX (DateModified) สำหรับรหัสพนักงานแต่ละคน การจัดเก็บ IsCurrent เป็นความคิดที่ไม่ดีมากเพราะประการแรกมันสามารถคำนวณได้และประการที่สองมันง่ายเกินไปสำหรับข้อมูลที่จะไม่ซิงค์กัน

คุณยังสามารถสร้างมุมมองที่แสดงเฉพาะบันทึกล่าสุดและส่วนใหญ่ใช้ข้อมูลนั้นขณะทำงานในแอปของคุณ สิ่งที่ดีเกี่ยวกับแนวทางนี้คือคุณไม่มีข้อมูลที่ซ้ำกันและคุณไม่จำเป็นต้องรวบรวมข้อมูลจากสองที่ที่แตกต่างกัน (ปัจจุบันอยู่ในพนักงานและเก็บถาวรในประวัติพนักงาน) เพื่อรับประวัติหรือย้อนกลับทั้งหมด ฯลฯ ) .


ข้อเสียของแนวทางนี้คือตารางจะเติบโตได้เร็วกว่าถ้าคุณใช้สองตาราง
cdmckay

2

หากคุณต้องการใช้ข้อมูลประวัติ (เพื่อเหตุผลในการรายงาน) คุณควรใช้โครงสร้างดังนี้:

// Holds Employee Entity
"Employees (EmployeeId, FirstName, LastName, DepartmentId, .., ..)"

// Holds the Employee revisions in rows.
"EmployeeHistories (HistoryId, EmployeeId, DateModified, OldValue, NewValue, FieldName)"

หรือโซลูชันระดับโลกสำหรับการใช้งาน:

// Holds Employee Entity
"Employees (EmployeeId, FirstName, LastName, DepartmentId, .., ..)"

// Holds all entities revisions in rows.
"EntityChanges (EntityName, EntityId, DateModified, OldValue, NewValue, FieldName)"

คุณสามารถบันทึกการแก้ไขของคุณใน XML จากนั้นคุณจะมีบันทึกเดียวสำหรับการแก้ไขหนึ่งครั้ง จะมีลักษณะดังนี้:

// Holds Employee Entity
"Employees (EmployeeId, FirstName, LastName, DepartmentId, .., ..)"

// Holds all entities revisions in rows.
"EntityChanges (EntityName, EntityId, DateModified, XMLChanges)"

1
Better: use event sourcing :)
dariol

1

เรามีข้อกำหนดที่คล้ายกันและสิ่งที่เราพบก็คือบ่อยครั้งที่ผู้ใช้ต้องการเห็นสิ่งที่เปลี่ยนแปลงไปโดยไม่จำเป็นต้องย้อนกลับการเปลี่ยนแปลงใด ๆ

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

เมื่อใดก็ตามที่ผู้ใช้บันทึกการเปลี่ยนแปลงเราจะโหลดออบเจ็กต์เก่าซ้ำเรียกใช้การเปรียบเทียบบันทึกการเปลี่ยนแปลงและบันทึกเอนทิตี (ทั้งหมดจะทำในธุรกรรมฐานข้อมูลเดียวในกรณีที่มีปัญหาใด ๆ )

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


0

ดูเหมือนว่าคุณต้องการติดตามการเปลี่ยนแปลงของเอนทิตีที่เฉพาะเจาะจงเมื่อเวลาผ่านไปเช่น ID 3, "bob", "123 main street", อีก ID 3, "bob" "234 elm st" เป็นต้นโดยพื้นฐานแล้วความสามารถ เพื่ออ้วกประวัติการแก้ไขที่แสดงที่อยู่ทั้งหมดที่ "บ๊อบ" เคยอยู่ที่

วิธีที่ดีที่สุดในการดำเนินการนี้คือการมีฟิลด์ "เป็นปัจจุบัน" ในแต่ละระเบียนและ (อาจ) ประทับเวลาหรือ FK ไปยังตารางวันที่ / เวลา

จากนั้นส่วนแทรกจะต้องตั้งค่า "เป็นปัจจุบัน" และยกเลิกการตั้งค่าระเบียน "เป็นปัจจุบัน" ในระเบียน "ปัจจุบัน" ก่อนหน้านี้ด้วย ข้อความค้นหาต้องระบุ "เป็นปัจจุบัน" เว้นแต่ว่าคุณต้องการประวัติทั้งหมด

มีการปรับแต่งเพิ่มเติมสำหรับสิ่งนี้หากเป็นตารางที่มีขนาดใหญ่มากหรือคาดว่าจะมีการแก้ไขจำนวนมาก แต่นี่เป็นแนวทางที่ค่อนข้างมาตรฐาน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.