2013-06-12 32 views
7

Tôi đang tìm cách thiết kế cơ sở dữ liệu theo dõi mọi tập hợp thay đổi để tôi có thể tham khảo lại chúng trong tương lai. Vì vậy, ví dụ:Thiết kế cơ sở dữ liệu với Lịch sử thay đổi

Database A 

+==========+========+==========+ 
| ID  | Name | Property | 

    1  Kyle  30 

Nếu tôi thay đổi trường 'sở hữu' của hàng đến 50, nó nên cập nhật hàng để:

1 Kyle 50 

Nhưng nên lưu thực tế là tài sản của hàng là 30 tại một số điểm trong thời gian. Sau đó, nếu hàng một lần nữa được cập nhật để 70:

1 Kyle 70 

Cả hai sự kiện mà tài sản của hàng là 50 và 70 cần được bảo tồn, như vậy mà với một số truy vấn tôi có thể lấy:

1 Kyle 30 
1 Kyle 50 

Nó nên nhận ra rằng đây là những "mục tương tự" ở các thời điểm khác nhau.

Chỉnh sửa: lịch sử này sẽ cần phải được trình bày cho người dùng tại một số điểm trong thời gian rất lý tưởng, cần có một sự hiểu biết trong đó hàng thuộc cùng một "cụm sửa đổi"

cách tốt nhất để là gì tiếp cận thiết kế của cơ sở dữ liệu này?

+0

Ứng dụng của bạn có cần phải hiểu lịch sử (tức là trình bày lịch sử này cho người dùng cuối) hay là mục đích kiểm toán? – Matthew

+0

Đó có phải là một yêu cầu mà điều này được lưu trữ trong DB? Thông thường điều này được thực hiện tại ứng dụng để nó có thể được kiểm soát phiên bản và được áp dụng giữa nhiều nhà phát triển. –

+0

Có, ứng dụng sẽ cần phải trình bày lịch sử này cho người dùng. –

Trả lời

12

Một cách là phải có một MyTableNameHistory cho mỗi bảng trong cơ sở dữ liệu của bạn, và làm cho sơ đồ của nó giống với sơ đồ của bảng MyTableName, ngoại trừ rằng Primary Key của bảng Lịch sử có một cột bổ sung tên effectiveUtc như một DateTime. Ví dụ, nếu bạn có một bảng tên Employee,

Create Table Employee 
{ 
    employeeId integer Primary Key Not Null, 
    firstName varChar(20) null, 
    lastName varChar(30) Not null, 
    HireDate smallDateTime null, 
    DepartmentId integer null 
} 

Sau đó bảng History sẽ

Create Table EmployeeHistory 
{ 
    employeeId integer Not Null, 
    effectiveUtc DateTime Not Null, 
    firstName varChar(20) null, 
    lastName varChar(30) Not null, 
    HireDate smallDateTime null, 
    DepartmentId integer null, 
    Primary Key (employeeId , effectiveUtc) 
} 

Sau đó, bạn có thể đặt một kích hoạt trên bảng Employee, do đó mỗi khi bạn chèn, cập nhật hoặc xóa bất kỳ thứ gì trong bảng Employee, một bản ghi mới được chèn vào bảng EmployeeHistory với các giá trị chính xác giống nhau cho tất cả các trường thông thường và giờ hiện tại UTC trong cột effectiveUtc.

Sau đó, để tìm các giá trị tại bất kỳ thời điểm nào trong quá khứ, bạn chỉ cần chọn bản ghi từ bảng lịch sử có giá trị hiệu quảUtc là giá trị cao nhất trước ngày giờ bạn muốn giá trị.

Select * from EmployeeHistory h 
Where EmployeeId = @EmployeeId 
    And effectiveUtc = 
    (Select Max(effectiveUtc) 
    From EmployeeHistory 
    Where EmployeeId = h.EmployeeId 
     And effcetiveUtc < @AsOfUtcDate) 
+1

Nhưng ... truy vấn không hiệu quả và lược đồ không cho phép dễ dàng có được cụm sửa đổi của OP (như sử dụng truy vấn dễ dàng ghi), tức là biết khi nào một bộ phận xuất hiện, khi nó bị xóa và khi nó được tạo lại. .. với tất cả các nhân viên bên trong nó tại thời điểm 't'. –

+0

Truy vấn có thể được đơn giản hóa bằng cách sử dụng mệnh đề 'DICTINCT ON()' của postgres. Một cái gì đó giống như 'Chọn khác biệt trên (EmployeeId) * từ EmployeeHistory nơi effectUtc <= @AsOfUtcDate thứ tự của EmployeeId, effectUtc desc' –

+0

@Igor Typo:' DISTINCT ON() '(bởi vì copy-paste không phải là thói quen xấu của tôi) – luckydonald

1

Cách tốt nhất phụ thuộc vào những gì bạn đang làm. Bạn muốn nhìn sâu hơn vào kích thước dần thay đổi:

https://en.wikipedia.org/wiki/Slowly_changing_dimension

Trong Postgres 9.2 không bỏ lỡ kiểu tsrange, quá. Nó cho phép hợp nhất start_dateend_date thành một cột và lập chỉ mục nội dung có chỉ số GIST (hoặc GIN) cùng với ràng buộc loại trừ để tránh phạm vi ngày trùng lặp.


Edit:

cần có một sự hiểu biết trong đó hàng thuộc cùng một "cụm sửa đổi"

Trong trường hợp này bạn muốn phạm vi ngày trong một cách này hay cách khác trong bảng của bạn, thay vì số sửa đổi hoặc cờ sống, nếu không bạn sẽ kết thúc sao chép dữ liệu liên quan trên toàn bộ địa điểm.

Trên ghi chú riêng, hãy xem xét phân biệt đối xử với bảng kiểm tra từ dữ liệu trực tiếp, thay vì lưu trữ mọi thứ trong cùng một bảng. Khó thực hiện và quản lý hơn, nhưng nó làm cho các truy vấn hiệu quả hơn trên dữ liệu trực tiếp.


Xem bài liên quan này, quá: Temporal database design, with a twist (live vs draft rows)

1

Một trong những cách để ghi lại tất cả các thay đổi này là để tạo ra cái gọi là audit triggers. Những trình kích hoạt như vậy có thể ghi lại bất kỳ sự thay đổi nào đối với bảng mà chúng đang ở trên một bảng nhật ký riêng (có thể được truy vấn để xem lịch sử của các thay đổi).

Chi tiết về việc triển khai here.

0

Để thêm vào Charles' answer, tôi sẽ sử dụng một Entity-Attribute-Value model thay vì tạo ra một bảng lịch sử khác nhau cho mỗi bảng khác trong cơ sở dữ liệu của bạn.

Về cơ bản, bạn sẽ tạo mộtHistory bảng như sau:

Create Table History 
{ 
    tableId varChar(64) Not Null, 
    recordId varChar(64) Not Null, 
    changedAttribute varChar(64) Not Null, 
    newValue varChar(64) Not Null, 
    effectiveUtc DateTime Not Null, 
    Primary Key (tableId , recordId , changedAttribute, effectiveUtc) 
} 

Sau đó, bạn sẽ tạo ra một kỷ lục History bất cứ lúc nào bạn tạo hoặc sửa đổi dữ liệu trong một bảng của bạn.

Để làm theo ví dụ, khi bạn thêm 'Kyle' vào bảng Employee, bạn sẽ tạo hai bản ghi (một cho mỗi thuộc tính không phải id) và sau đó bạn sẽ tạo bản ghi mới mỗi lần thay đổi thuộc tính:

thay đổi
History 
+==========+==========+==================+==========+==============+ 
| tableId | recordId | changedAttribute | newValue | effectiveUtc | 
| Employee | 1  | Name    | Kyle  | N   | 
| Employee | 1  | Property   | 30  | N   | 
| Employee | 1  | Property   | 50  | N+1   | 
| Employee | 1  | Property   | 70  | N+2   | 

Ngoài ra, như a_horse_with_no_name gợi ý, nếu bạn không muốn lưu trữ một kỷ lục mới History cho mỗi thay đổi lĩnh vực, bạn có thể lưu trữ nhóm (ví dụ như thay đổi Name để 'Kyle' và Property-30 trong cùng cập nhật) dưới dạng một bản ghi. Trong trường hợp này, bạn sẽ cần phải diễn tả tập hợp các thay đổi trong JSON hoặc một số định dạng blob khác. Điều này sẽ hợp nhất các trường changedAttributenewValue thành một (changedValues). Ví dụ:

History 
+==========+==========+================================+==============+ 
| tableId | recordId | changedValues     | effectiveUtc | 
| Employee | 1  | { Name: 'Kyle', Property: 30 } | N   | 

Đây có lẽ là khó hơn việc tạo ra một bảng History cho mỗi bảng khác trong cơ sở dữ liệu của bạn, nhưng nó có nhiều lợi ích:

  • thêm lĩnh vực mới để bảng trong cơ sở dữ liệu của bạn giành chiến thắng' t yêu cầu thêm các lĩnh vực cùng một bảng
  • bảng ít sử dụng
  • Nó dễ dàng hơn để tương quan bản cập nhật cho các bảng khác nhau theo thời gian
+2

Nó có thể hiệu quả hơn để lưu trữ tất cả các giá trị hàng trong một cột JSON hoặc hstore duy nhất thay vì một hàng cho mỗi cột được sửa đổi. ví dụ. theo mẫu được sử dụng trong các trình kích hoạt kiểm tra khác nhau, hãy xem: http://okbob.blogspot.de/2015/01/most-simply-implementation-of-history.html hoặc http://8kb.co.uk/blog/2015/ 01/19/copy-pavel-stehules-đơn giản-lịch sử-bảng-nhưng-với-the-jsonb-type/hoặc http://cjauvin.blogspot.de/2013/05/impossibly-lean-audit-system-for .html –

+0

@a_horse_with_no_name Đúng. Điều đó chắc chắn sẽ làm việc quá. Tôi cũng sẽ thêm ghi chú giải thích tùy chọn đó. Cảm ơn! –

Các vấn đề liên quan