Bagaimana saya melacak semua perubahan harga dalam db untuk mendapatkan harga produk 'x' pada tanggal 'y'

8

Saya perlu melacak perubahan harga produk sehingga saya dapat meminta db untuk harga produk pada tanggal tertentu. Informasi tersebut digunakan dalam sistem yang menghitung audit historis sehingga harus mengembalikan harga yang benar untuk produk yang benar berdasarkan tanggal pembelian.

Saya lebih suka menggunakan postgres dalam membangun db.

Saya perlu dengan desain database tetapi setiap dan semua saran praktik terbaik juga diterima.

Gunnar Norred
sumber
1
salin tulis ke meja lain. Jika tabel pricesdibuat tabel prices_historydengan kolom serupa. Hibernate Envers dapat mengotomatisasi ini untuk Anda
Neil McGuigan

Jawaban:

11

Jika saya memahami skenario dengan tepat, Anda harus mendefinisikan tabel yang mempertahankan rangkaian waktu Harga ; oleh karena itu, saya setuju, ini ada hubungannya dengan aspek temporal dari database yang Anda gunakan.

Peraturan bisnis

Mari kita mulai menganalisis situasi dari level konseptual. Jadi, jika , dalam domain bisnis Anda,

  • sebuah produk dibeli pada satu-ke-banyak Harga ,
  • setiap Harga pembelian menjadi Lancar pada Tanggal Mulai yang tepat , dan
  • yang Harga EndDate (yang menunjukkan Tanggal ketika Harga berhenti menjadi Current ) adalah sama dengan StartDate dari segera berikutnya Harga ,

maka itu artinya

  • tidak ada kesenjangan antara yang berbeda Periode selama mana Harga yang sekarang (time series adalah terus menerus atau diperbantukan ), dan
  • yang EndDate dari Harga adalah datum diturunkan.

The IDEF1X diagram yang ditunjukkan di Gambar 1 , meskipun sangat sederhana, menggambarkan skenario seperti:

Gbr. 1 - Harga Produk Diagram IDEF1X Sederhana - Skenario A

Tata letak logis ekspositori

Dan desain tingkat logis SQL-DDL berikut, berdasarkan diagram IDEF1X tersebut, menggambarkan pendekatan yang layak yang dapat Anda sesuaikan dengan kebutuhan Anda sendiri:

-- 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)
);

The Pricemeja memiliki PRIMARY KEY komposit terdiri dari dua kolom, yaitu, ProductNumber(dibatasi, pada gilirannya, sebagai FOREIGN KEY yang membuat referensi untuk Product.ProductNumber) dan StartDate(menunjukkan yang khusus Tanggal di mana tertentu Produk dibeli di tertentu Harga ) .

Dalam hal Produk yang dibeli di berbagai Harga selama sama Day , bukan StartDatekolom, Anda mungkin termasuk salah satu label sebagai StartDateTimeyang membuat Instan ketika diberikan Produk dibeli pada tepat Harga . KUNCI UTAMA kemudian harus dinyatakan sebagai (ProductNumber, StartDateTime).

Seperti yang diperagakan, tabel tersebut adalah yang biasa, karena Anda dapat mendeklarasikan operasi SELECT, INSERT, UPDATE, dan DELETE untuk memanipulasi datanya secara langsung, sehingga (a) memungkinkan menghindari pemasangan komponen tambahan dan (b) dapat digunakan di semua platform SQL utama dengan beberapa penyesuaian, jika perlu.

Sampel manipulasi data

Untuk mencontohkan beberapa operasi manipulasi yang tampaknya berguna, katakanlah Anda telah menyisipkan data berikut dalam tabel Productdan Price, masing-masing:

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);

Karena Price.EndDateini adalah titik data yang dapat diturunkan, maka Anda harus mendapatkannya melalui, tepatnya, tabel turunan yang dapat dibuat sebagai tampilan untuk menghasilkan rangkaian waktu "penuh", seperti yang dicontohkan di bawah ini:

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;

Kemudian operasi berikut yang SELECT langsung dari pandangan itu

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

memasok set hasil berikutnya:

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.

Sekarang, mari kita asumsikan bahwa Anda tertarik untuk mendapatkan seluruh Pricedata untuk yang Productdiidentifikasi oleh ProductNumber 1750 pada Date 2 Juni 2017 . Melihat bahwa Pricepernyataan (atau baris) terkini atau efektif selama seluruh Interval yang berjalan dari (i) StartDatehingga (ii) EndDate, maka operasi DML ini

 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.

menghasilkan set hasil yang mengikuti

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

yang membahas persyaratan tersebut.

Seperti yang ditunjukkan, PriceWithEndDatetampilan memainkan peran penting dalam memperoleh sebagian besar data yang dapat diderivasi, dan dapat DIPILIH DARI dengan cara yang cukup biasa.

Dengan mempertimbangkan bahwa platform preferensi Anda adalah PostgreSQL, konten ini dari situs dokumentasi resmi berisi informasi tentang pandangan "terwujud" , yang dapat membantu untuk mengoptimalkan kecepatan eksekusi melalui mekanisme tingkat fisik, jika aspek tersebut menjadi bermasalah. Sistem manajemen basis data SQL lainnya (DBMS) menawarkan instrumen fisik yang sangat mirip, meskipun terminologi yang berbeda dapat diterapkan, misalnya, pandangan "diindeks" di Microsoft SQL Server.

Anda dapat melihat contoh kode DDL dan DML yang dibahas beraksi di biola db <> ini dan dalam Biola SQL ini .

Sumber terkait

  • Dalam tanya jawab ini kami membahas konteks bisnis yang mencakup perubahan Harga Produk tetapi memiliki cakupan yang lebih luas, sehingga Anda mungkin merasa tertarik.

  • Posting Stack Overflow ini mencakup poin yang sangat relevan mengenai jenis kolom yang menyimpan datum mata uang di PostgreSQL.

Tanggapan terhadap komentar

Ini mirip dengan pekerjaan yang saya lakukan, tetapi saya merasa jauh lebih nyaman / efisien untuk bekerja dengan tabel di mana harga (dalam hal ini) memiliki kolom tanggal mulai dan kolom tanggal berakhir - jadi Anda hanya mencari baris dengan target tanggal > = tanggal mulai dan tanggal target <= tanggal akhir. Tentu saja, jika data tidak disimpan dengan bidang tersebut (termasuk tanggal akhir 31 Desember 9999, bukan Null, di mana tidak ada tanggal akhir yang sebenarnya), maka Anda harus melakukan pekerjaan untuk memproduksinya. Saya benar-benar membuatnya berjalan setiap hari, dengan tanggal akhir = tanggal hari ini secara default. Juga, uraian saya memerlukan tanggal akhir 1 = tanggal mulai 2 dikurangi 1 hari. - @Robert Carnegie , pada 2017-06-22 20: 56: 01Z

Metode yang saya usulkan di atas membahas domain bisnis dari karakteristik yang dijelaskan sebelumnya , akibatnya menerapkan saran Anda tentang mendeklarasikan EndDatesebagai kolom — yang berbeda dari “bidang” - dari tabel dasar yang dinamai Priceakan menyiratkan bahwa struktur logis dari database akan tidak mencerminkan skema konseptual dengan benar, dan skema konseptual harus didefinisikan dan tercermin dengan presisi, termasuk diferensiasi (1) informasi dasar dari (2) informasi yang dapat diturunkan .

Terlepas dari itu, tindakan semacam itu akan memperkenalkan duplikasi, karena EndDatekemudian dapat diperoleh berdasarkan (a) tabel yang dapat diturunkan dan juga berdasarkan (b) tabel dasar yang dinamai Price, dengan EndDatekolom yang kemudian diduplikasi . Sementara itu adalah suatu kemungkinan, jika seorang praktisi memutuskan untuk mengikuti pendekatan tersebut, ia harus dengan jelas memperingatkan pengguna database tentang ketidaknyamanan dan ketidakefisienan yang ditimbulkannya. Salah satu dari ketidaknyamanan dan ketidakefisienan tersebut adalah, misalnya, kebutuhan mendesak untuk mengembangkan suatu mekanisme yang memastikan, setiap saat , bahwa setiap Price.EndDatenilai sama dengan yang ada pada Price.StartDatekolom baris yang langsung berturut-turut untuk Price.ProductNumbernilai yang ada.

Sebaliknya, pekerjaan untuk menghasilkan data turunan yang dipermasalahkan seperti yang saya ajukan adalah, jujur, tidak istimewa sama sekali, dan diharuskan untuk (i) menjamin korespondensi yang benar antara tingkat abstraksi logis dan konseptual dari database dan (ii) ) memastikan integritas data, kedua aspek yang seperti disebutkan sebelumnya jelas sangat penting.

Jika aspek efisiensi yang Anda bicarakan terkait dengan kecepatan eksekusi beberapa operasi manipulasi data, maka itu harus dikelola di tempat yang tepat, yaitu, pada tingkat fisik, melalui, misalnya, strategi pengindeksan yang menguntungkan, berdasarkan pada (1) ) kecenderungan permintaan tertentu dan (2) mekanisme fisik tertentu yang disediakan oleh DBMS penggunaan. Kalau tidak, mengorbankan pemetaan konseptual-logis yang sesuai dan kompromi integritas data yang terlibat dengan mudah mengubah sistem yang kuat (yaitu, aset organisasi yang berharga) menjadi sumber daya yang tidak dapat diandalkan.

Rangkaian waktu yang terputus-putus atau terputus-putus

Di sisi lain, ada keadaan di mana mempertahankan EndDatesetiap baris dalam tabel deret waktu tidak hanya lebih nyaman dan efisien tetapi juga menuntut , meskipun tentu saja itu tergantung sepenuhnya pada persyaratan spesifik lingkungan bisnis. Salah satu contoh keadaan semacam itu muncul ketika

  • baik informasi StartDate dan EndDate disimpan sebelum (dan disimpan melalui) setiap INSERTION, dan
  • bisa ada Kesenjangan di tengah-tengah yang berbeda Periode selama mana Harga yang sekarang (yaitu, time series adalah terputus atau terpisah ).

Saya telah mewakili skenario tersebut dalam diagram IDEF1X yang ditampilkan pada Gambar 2 .

Gbr. 2 - Harga Produk Diagram IDEF1X Sederhana - Skenario B

Dalam hal itu, ya, Pricetabel hipotetis harus dideklarasikan dengan cara yang mirip dengan ini:

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)
);

Dan, ya, desain DDL logis menyederhanakan administrasi di tingkat fisik, karena Anda dapat memasang strategi pengindeksan yang mencakup EndDatekolom (yang, seperti yang ditunjukkan, dinyatakan dalam tabel dasar) dalam konfigurasi yang relatif lebih mudah .

Kemudian, operasi SELECT seperti yang di bawah ini

 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';

dapat digunakan untuk memperoleh seluruh Pricedata untuk yang Productdiidentifikasi oleh ProductNumber 1750 pada Date tanggal 2 Juni 2017 .

MDCCL
sumber
Ini mirip dengan pekerjaan yang saya lakukan, tapi saya merasa jauh lebih nyaman / efisien untuk bekerja dengan tabel di mana harga (dalam hal ini) memiliki kolom tanggal mulai dan kolom tanggal berakhir - jadi Anda hanya mencari baris dengan target tanggal > = tanggal mulai dan tanggal target <= tanggal akhir. Tentu saja, jika data tidak disimpan dengan bidang-bidang tersebut (termasuk tanggal akhir 31 Maret 9999, bukan Null, di mana tidak ada tanggal akhir yang sebenarnya), maka Anda harus melakukan pekerjaan untuk memproduksinya. Saya benar-benar membuatnya berjalan setiap hari, dengan tanggal akhir = tanggal hari ini secara default. Juga, uraian saya memerlukan tanggal akhir 1 = tanggal mulai 2 dikurangi 1 hari.
Robert Carnegie
4

Saya percaya Anda akan ingin melihat Tabel Temporal . Ini menyediakan fungsionalitas untuk melakukan apa yang Anda cari dan tersedia di Postgres dengan ekstensi yang tepat.

Konsep ini terlihat juga DB agnostik, karena ditawarkan pada berbagai platform RDBMS .

John Eisbrener
sumber
1

Saya memberi jawaban di sini yang relatif sederhana dan tidak memerlukan ekstensi khusus ke basis data (karena itu akan bekerja dengan basis data apa pun).

TommCatt
sumber