Làm cách nào tôi theo dõi tất cả các thay đổi về giá trong một db để có được giá của sản phẩm 'x' vào ngày 'y'


8

Tôi sẽ cần theo dõi sự thay đổi giá sản phẩm để tôi có thể truy vấn db về giá sản phẩm tại một ngày nhất định. Thông tin được sử dụng trong một hệ thống tính toán kiểm toán lịch sử để nó phải trả lại giá chính xác cho sản phẩm chính xác dựa trên ngày mua.

Tôi muốn sử dụng postgres trong việc xây dựng db.

Tôi cần thiết kế cơ sở dữ liệu nhưng bất kỳ và tất cả các đề xuất thực hành tốt nhất cũng được hoan nghênh.


1
sao chép vào ghi vào bảng khác. Nếu bảng được pricestạo một bảng prices_historyvới các cột tương tự. Hibernate Envers có thể tự động hóa việc này cho bạn
Neil McGuigan

Câu trả lời:


11

Nếu tôi hiểu kịch bản phù hợp, bạn nên xác định một bảng giữ lại chuỗi thời gian Giá ; do đó, tôi đồng ý, điều này có liên quan nhiều đến khía cạnh tạm thời của cơ sở dữ liệu bạn đang làm việc.

Quy tắc kinh doanh

Chúng ta hãy bắt đầu phân tích tình hình từ cấp độ khái niệm. Vì vậy, nếu , trong lĩnh vực kinh doanh của bạn,

  • một sản phẩm được mua tại một-nhiều giá ,
  • mỗi Giá mua trở thành Hiện tại tại một StartDate chính xác và
  • các giá EndDate (mà chỉ ra ngày khi giá không còn là hiện tại ) là tương đương với StartDate của ngay lập tức tiếp theo Giá ,

vậy có nghĩa là

  • không có khoảng trống giữa các biệt nguyệt trong đó giáhiện tại (các chuỗi thời gian là liên tục hoặc liên kết ), và
  • các EndDate của một giá là một cột mốc derivable.

Các IDEF1X sơ đồ thể hiện trong hình 1 , mặc dù rất đơn giản, mô tả một kịch bản như vậy:

Hình 1 - Giá sản phẩm Sơ đồ IDEF1X được đơn giản hóa - Kịch bản A

Bố cục logic phơi bày

Và thiết kế mức logic SQL-DDL sau đây, dựa trên sơ đồ IDEF1X đã nói, minh họa một cách tiếp cận khả thi mà bạn có thể thích ứng với nhu cầu chính xác của mình:

-- At the physical level, you should define a convenient 
-- indexing strategy based on the data manipulation tendencies
-- so that you can supply an optimal execution speed of the
-- queries declared at the logical level; thus, some testing 
-- sessions with considerable data load should be carried out.

CREATE TABLE Product (
    ProductNumber INT      NOT NULL,
    Etcetera      CHAR(30) NOT NULL,
    --
    CONSTRAINT Product_PK PRIMARY KEY (ProductNumber)
);

CREATE TABLE Price (
    ProductNumber INT  NOT NULL,
    StartDate     DATE NOT NULL,
    Amount        INT  NOT NULL, -- Retains the amount in cents, but there are other options regarding the type of use.
    --
    CONSTRAINT Price_PK            PRIMARY KEY (ProductNumber, StartDate),
    CONSTRAINT Price_to_Product_FK FOREIGN KEY (ProductNumber)
        REFERENCES Product (ProductNumber),
    CONSTRAINT AmountIsValid_CK    CHECK       (Amount >= 0)
);

Các Pricebảng có một PRIMARY KEY tổng hợp tạo thành từ hai cột, tức là ProductNumber(bị hạn chế, đến lượt nó, như một KEY NƯỚC NGOÀI mà làm cho một tham chiếu đến Product.ProductNumber) và StartDate(chỉ ra những đặc biệt ngày , trong đó một số sản phẩm được mua tại một cụ Giá ) .

Trong trường hợp Sản phẩm được mua ở các mức giá khác nhau trong cùng một ngày , thay vì StartDatecột, bạn có thể bao gồm một nhãn được StartDateTimegiữ nhãn tức thì khi một Sản phẩm nhất định được mua ở một mức giá chính xác . KEY PRIMARY sau đó sẽ phải được khai báo là (ProductNumber, StartDateTime).

Như đã trình bày, bảng đã nói ở trên là một bảng thông thường, bởi vì bạn có thể khai báo các hoạt động CHỌN, CHERTN, CẬP NHẬT và XÓA để thao tác trực tiếp dữ liệu của nó, do đó (a) cho phép tránh cài đặt các thành phần bổ sung và (b) có thể được sử dụng trong tất cả các nền tảng SQL chính với một số điều chỉnh, nếu cần thiết.

Mẫu thao tác dữ liệu

Để minh họa một số thao tác thao tác có vẻ hữu ích, hãy giả sử rằng bạn đã CHỨNG MINH dữ liệu sau trong bảng ProductPricebảng tương ứng:

INSERT INTO Product
    (ProductNumber, Etcetera)
VALUES
    (1750, 'Price time series sample'); 

INSERT INTO Price
    (ProductNumber, StartDate, Amount)
VALUES
    (1750, '20170601', 1000),
    (1750, '20170603', 3000),   
    (1750, '20170605', 4000),
    (1750, '20170607', 3000);

Vì đó Price.EndDatelà một điểm dữ liệu có thể tạo được, nên bạn phải lấy nó thông qua, chính xác, một bảng dẫn xuất có thể được tạo như một khung nhìn để tạo ra chuỗi thời gian đầy đủ của Rô-lô, như được minh họa dưới đây:

CREATE VIEW PriceWithEndDate AS

    SELECT  P.ProductNumber,
            P.Etcetera AS ProductEtcetera,
           PR.Amount   AS PriceAmount,
           PR.StartDate,
           (
                SELECT MIN(StartDate)
                      FROM Price InnerPR
                     WHERE P.ProductNumber   = InnerPR.ProductNumber
                       AND InnerPR.StartDate > PR.StartDate
           ) AS EndDate
        FROM Product P
        JOIN Price   PR
          ON P.ProductNumber = PR.ProductNumber;

Sau đó, thao tác sau đây CHỌN trực tiếp từ chế độ xem đó

  SELECT ProductNumber,
         ProductEtcetera,
         PriceAmount,
         StartDate,
         EndDate
    FROM PriceWithEndDate 
ORDER BY StartDate DESC;

cung cấp tập kết quả tiếp theo:

ProductNumber  ProductEtcetera     PriceAmount  StartDate   EndDate
-------------  ------------------  -----------  ----------  ----------
         1750  Price time series         4000  2017-06-07  NULL      -- (*) 
         1750  Price time series         3000  2017-06-05  2017-06-07
         1750  Price time series         2000  2017-06-03  2017-06-05
         1750  Price time series         1000  2017-06-01  2017-06-03

-- (*) A ‘sentinel’ value would be useful to avoid the NULL marks.

Bây giờ, chúng tôi giả định rằng bạn quan tâm đến việc lấy toàn bộ Pricedữ liệu cho lần Productđầu tiên được xác định vào năm ProductNumber 1750 vào ngày Date 2 tháng 6 năm 2017 . Thấy rằng một Pricexác nhận (hoặc hàng) là hiện tại hoặc có hiệu lực trong toàn bộ Khoảng thời gian chạy từ (i) StartDateđến (ii) EndDate, sau đó hoạt động DML này

 SELECT ProductNumber,
        ProductEtcetera,
        PriceAmount,
        StartDate,
        EndDate
   FROM PriceWithEndDate
  WHERE ProductNumber = 1750        -- (1) 
    AND StartDate    <= '20170602'  -- (2)
    AND EndDate      >= '20170602'; -- (3)

-- (1), (2) and (3): You can supply parameters in place of fixed values to make the query more versatile.

mang lại tập kết quả theo sau

ProductNumber  ProductEtcetera     PriceAmount  StartDate   EndDate
-------------  ------------------  -----------  ----------  ----------
         1750  Price time series         1000  2017-06-01  2017-06-03

Địa chỉ nào yêu cầu.

Như được hiển thị, PriceWithEndDatechế độ xem đóng vai trò tối quan trọng trong việc thu được hầu hết các dữ liệu có thể lấy được và có thể được CHỌN TỪ theo cách khá bình thường.

Có tính đến việc nền tảng ưu tiên của bạn là PostgreSQL, nội dung này từ trang tài liệu chính thức chứa thông tin về các chế độ xem được vật chất hóa , có thể giúp tối ưu hóa tốc độ thực thi bằng các cơ chế mức vật lý, nếu khía cạnh nói trở nên có vấn đề. Các hệ thống quản lý cơ sở dữ liệu SQL (DBMS) khác cung cấp các công cụ vật lý rất giống nhau, mặc dù các thuật ngữ khác nhau có thể được áp dụng, ví dụ, các khung nhìn được lập chỉ mục của Drake trong Microsoft SQL Server.

Bạn có thể thấy các mẫu mã DDL và DML được thảo luận đang hoạt động trong db <> fiddle này và trong Fiddle SQL này .

Tài nguyên liên quan

  • Trong phần hỏi đáp này, chúng tôi thảo luận về bối cảnh kinh doanh bao gồm những thay đổi của Giá sản phẩm nhưng có phạm vi rộng hơn, vì vậy bạn có thể thấy nó đáng quan tâm.

  • Các bài đăng Stack Overflow này bao gồm các điểm rất phù hợp liên quan đến loại cột chứa dữ liệu tiền tệ trong PostgreQuery.

Phản hồi ý kiến

Điều này trông tương tự như công việc tôi đã làm, nhưng tôi thấy làm việc thuận tiện / hiệu quả hơn nhiều với bảng trong đó giá (trong trường hợp này) có cột bắt đầu và cột kết thúc - vì vậy bạn chỉ tìm kiếm các hàng có mục tiêu > = startdate và targetdate <= enddate. Tất nhiên, nếu dữ liệu không được lưu trữ với các trường đó (bao gồm cả ngày kết thúc vào ngày 31 tháng 12 năm 9999, không phải là Null, nơi không có ngày kết thúc thực sự tồn tại), thì bạn phải làm việc để sản xuất nó. Tôi thực sự đã làm cho nó chạy mỗi ngày, với ngày kết thúc = ngày hôm nay theo mặc định. Ngoài ra, mô tả của tôi yêu cầu enddate 1 = startdate 2 trừ 1 ngày. - @Robert Carnegie , vào ngày 2017-06-22 20: 56: 01Z

Phương pháp tôi đề xuất ở trên giải quyết một lĩnh vực kinh doanh có các đặc điểm được mô tả trước đây , do đó áp dụng đề xuất của bạn về việc khai EndDatebáo cột như là một cột khác với trường lĩnh vực - - của bảng cơ sở có tên Pricengụ ý rằng cấu trúc logic của cơ sở dữ liệu sẽ không được phản ánh chính xác lược đồ khái niệm và lược đồ khái niệm phải được xác định và phản ánh với độ chính xác, bao gồm phân biệt (1) thông tin cơ sở từ (2) thông tin có thể dẫn xuất .

Ngoài ra, một quá trình hành động như vậy sẽ giới thiệu sự trùng lặp, vì EndDatesau đó có thể có được nhờ (a) một bảng có thể dẫn xuất và cũng nhờ (b) bảng cơ sở được đặt tên Price, với EndDatecột được sao chép . Mặc dù đó là một khả năng, nếu một học viên quyết định làm theo phương pháp đã nói, anh ta hoặc cô ta nên quyết định cảnh báo người dùng cơ sở dữ liệu về những bất tiện và không hiệu quả mà nó liên quan. Một trong những bất tiện và không hiệu quả đó là, ví dụ, nhu cầu cấp thiết là phải phát triển một cơ chế đảm bảo, mọi lúc , mỗi Price.EndDategiá trị đều bằng với Price.StartDatecột của hàng liên tiếp ngay lập tức cho Price.ProductNumbergiá trị trong tay.

Ngược lại, công việc tạo ra dữ liệu dẫn xuất theo yêu cầu mà tôi đưa ra là, trung thực, không đặc biệt, và được yêu cầu (i) đảm bảo sự tương ứng chính xác giữa mức độ trừu tượng logic và khái niệm của cơ sở dữ liệu và (ii ) đảm bảo tính toàn vẹn dữ liệu, cả hai khía cạnh như đã lưu ý trước đây đều có tầm quan trọng lớn.

Nếu khía cạnh hiệu quả mà bạn đang nói đến có liên quan đến tốc độ thực hiện của một số thao tác thao tác dữ liệu, thì nó phải được quản lý ở nơi thích hợp, ví dụ, ở cấp độ vật lý, ví dụ: chiến lược lập chỉ mục có lợi, dựa trên (1 ) các xu hướng truy vấn cụ thể và (2) các cơ chế vật lý cụ thể được cung cấp bởi DBMS sử dụng. Mặt khác, hy sinh ánh xạ logic theo khái niệm phù hợp và làm tổn hại tính toàn vẹn của dữ liệu liên quan dễ dàng biến một hệ thống mạnh mẽ (nghĩa là một tài sản tổ chức có giá trị) thành một tài nguyên không đáng tin cậy.

Chuỗi thời gian không liên tục hoặc không liên tục

Mặt khác, có những trường hợp giữ lại EndDatemỗi hàng trong bảng chuỗi thời gian không chỉ thuận tiện và hiệu quả hơn mà còn đòi hỏi , mặc dù điều đó hoàn toàn phụ thuộc vào các yêu cầu cụ thể của môi trường kinh doanh. Một ví dụ về loại tình huống đó xảy ra khi

  • cả hai phần thông tin StartDateEndDate đều được giữ trước (và được giữ lại thông qua) mỗi INSERTion và
  • có thể có những khoảng trống ở giữa các Thời kỳ riêng biệt trong đó Giáhiện tại (nghĩa là chuỗi thời gian không liên tục hoặc không liên tục ).

Tôi đã trình bày kịch bản đã nói trong sơ đồ IDEF1X được hiển thị trong Hình 2 .

Hình 2 - Giá sản phẩm Sơ đồ IDEF1X được đơn giản hóa - Kịch bản B

Trong trường hợp đó, có, Pricebảng giả thuyết phải được khai báo theo cách tương tự như sau:

CREATE TABLE Price (
    ProductNumber INT  NOT NULL,
    StartDate     DATE NOT NULL,
    EndDate       DATE NOT NULL,
    Amount        INT  NOT NULL,
    --
    CONSTRAINT Price_PK            PRIMARY KEY (ProductNumber, StartDate, EndDate),
    CONSTRAINT Price_to_Product_FK FOREIGN KEY (ProductNumber)
        REFERENCES Product (ProductNumber),
    CONSTRAINT DatesOrder_CK       CHECK       (EndDate >= StartDate)
);

Và, vâng, thiết kế DDL logic đó đơn giản hóa việc quản trị ở cấp độ vật lý, bởi vì bạn có thể đưa ra một chiến lược lập chỉ mục bao gồm EndDatecột (như được hiển thị trong bảng cơ sở) trong các cấu hình tương đối dễ dàng hơn .

Sau đó, một thao tác CHỌN như hoạt động dưới đây

 SELECT  P.ProductNumber,
         P.Etcetera,
        PR.Amount,
        PR.StartDate,
        PR.EndDate
   FROM Price   PR
   JOIN Product P
  WHERE P.ProductNumber = 1750       
    AND StartDate      <= '20170602'  
    AND EndDate        >= '20170602';

có thể được sử dụng để lấy toàn bộ Pricedữ liệu cho năm 1750Product được xác định chủ yếu vào ngày 2 tháng 6 năm 2017 .ProductNumber Date


Điều này trông tương tự như công việc tôi đã làm, nhưng tôi thấy làm việc thuận tiện / hiệu quả hơn nhiều với bảng trong đó giá (trong trường hợp này) có cột bắt đầu và cột kết thúc - vì vậy bạn chỉ tìm kiếm các hàng có mục tiêu > = startdate và targetdate <= enddate. Tất nhiên, nếu dữ liệu không được lưu trữ với các trường đó (bao gồm cả ngày 31 tháng 9 năm 1999, không phải là Null, nơi không có ngày kết thúc thực sự tồn tại), thì bạn phải làm việc để sản xuất nó. Tôi thực sự đã làm cho nó chạy mỗi ngày, với ngày kết thúc = ngày hôm nay theo mặc định. Ngoài ra, mô tả của tôi yêu cầu enddate 1 = startdate 2 trừ 1 ngày.
Robert Carnegie


1

Tôi đã đưa ra một câu trả lời ở đây tương đối đơn giản và không yêu cầu mở rộng đặc biệt cho cơ sở dữ liệu (do đó sẽ hoạt động với bất kỳ cơ sở dữ liệu nào).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.