Menyimpan Alamat Penagihan Praktik Terbaik di Tabel Pesanan

10

Adakah yang bisa membantu saya memahami jawaban pengguna ini untuk tabel CustomerLocation . Saya benar-benar menginginkan metode yang baik untuk menyimpan alamat di tabel pesanan.

Apa yang saya cari adalah bagaimana saya bisa mengatur alamat saya sehingga ketika saya mengeditnya, pesanan tidak dipengaruhi oleh kenyataan bahwa pelanggan memperbarui alamatnya atau pindah.

Skema saya mirip dengan:

 Person           |EntityID|
 EntityAddress    |EntityID|AddressID|
 Address          |AddressID|AddressType|AddressLine1|AddressLine2|
 Order            |OrderID|BillingAddressID|
Komunitas
sumber

Jawaban:

16

Berbicara secara konseptual, meskipun dalam lingkungan bisnis Anda, Pesanan dan Alamat adalah gagasan yang terkait erat, keduanya pada dasarnya adalah dua jenis entitas yang terpisah, masing-masing dengan sekumpulan properti (atau atribut) yang berlaku sendiri dan kendala.

Oleh karena itu, seperti yang dinyatakan sebelumnya dalam komentar, saya setuju dengan @Erik , dan Anda harus mengatur tata letak logis dari basis data Anda di antara elemen-elemen lain:

  • satu meja diskrit untuk menyimpan potongan informasi Alamat ;
  • satu meja untuk menyimpan detail khusus Pelanggan ;
  • satu tabel untuk menyertakan poin data Pemesanan ; dan
  • satu tabel berisi fakta tentang hubungan antara Pelanggan dan Alamat ;

karena saya akan mencontohkan di bawah ini.

Diagram Eksposisi IDEF1X

Sebuah gambar bernilai ribuan kata, jadi saya membuat diagram IDEF1X yang ditunjukkan pada Gambar 1 untuk mengilustrasikan beberapa kemungkinan yang dibuka oleh saran saya:

Gambar 1 - Pelanggan, Pesanan dan Alamat Diagram IDEF1X Ekspositoris

Pelanggan , Alamat dan asosiasinya

Seperti yang ditunjukkan, saya digambarkan sebuah asosiasi dengan banyak-ke-banyak (M: N) rasio kardinalitas antara jenis entitas Pelanggan yang dan Alamat ; pendekatan ini akan memberikan fleksibilitas di masa depan karena, seperti yang Anda tahu, Pelanggan dapat menyimpan beberapa Alamat dari waktu ke waktu, atau bahkan secara bersamaan, dan Alamat yang sama dapat dibagikan oleh banyak Pelanggan .

Alamat tertentu dapat digunakan dalam beberapa cara oleh Pelanggan satu-ke-banyak (1: M) ; misalnya, dapat didefinisikan sebagai Fisik , dan / atau dapat diatur untuk Pengiriman , dan / atau untuk Penagihan . Mungkin, instance Alamat yang sama dapat melayani masing-masing tujuan yang disebutkan pada saat yang sama, atau mungkin mencakup dua penggunaan sementara kejadian Alamat yang berbeda mencakup yang lainnya.

a Dalam beberapa lingkungan bisnis, Pelanggan dapat berupa Orang atau Organisasi (situasi yang menyiratkan pengaturan yang sedikit berbeda, sebagaimana dirinci dalam jawaban ini tentang struktur subtipe-supertipe) tetapi dengan tujuan memberikan contoh yang disederhanakan, saya memutuskan tidak memasukkan kemungkinan itu di sini. Jika Anda perlu membahas situasi itu dalam basis data Anda, pos tautan sebelumnya menunjukkan metode untuk menyelesaikan persyaratan tersebut.

Peran Pemesanan , Alamat , Alamat Pelanggan , dan Alamat

Umumnya, Pesanan hanya membutuhkan dua jenis Alamat , satu untuk Pengiriman dan satu untuk Penagihan . Dengan cara ini, instance Alamat yang sama dapat mengisi kedua Peran untuk Pesanan individual , tetapi setiap Peran digambarkan oleh masing-masing properti, yaitu, ShippingAddressId atau BillingAddressId .

Pesanan terhubung dengan Alamat melalui jenis entitas asosiatif PelangganAddress dengan bantuan dua KUNCI ASING multi-properti, yaitu,

  • ( Nomor Pelanggan , ShippingAddressId ), dan ( Nomor Pelanggan , BillingAddressId ),

keduanya menunjuk ke multi-properti KUNCI PRIMER PelangganAddress ditampilkan sebagai

  • ( Nomor Pelanggan , AddressId )

... yang membantu mewakili aturan bisnis yang menetapkan bahwa (a) mesin virtual Order harus dihubungkan secara eksklusif dengan (b) Alamat yang sebelumnya terkait dengan Pelanggan spesifik yang membuat Pesanan itu , dan tidak pernah dengan (c) non- Pelanggan acak - Alamat terkait .

Riwayat untuk (1) Alamat dan untuk (2) asosiasi CustomerAddress

Jika Anda ingin memberikan kemungkinan mengubah informasi kepingan Alamat , maka Anda harus melacak semua perubahan data. Dengan cara ini saya menggambarkan Alamat sebagai tipe entitas yang "dapat diaudit" yang mempertahankan AddressHistory sendiri .

Karena sifat koneksi antara Pelanggan dan Alamat juga dapat mengalami satu atau beberapa modifikasi, saya juga menggambarkan kemungkinan menangani asosiasi seperti yang "dapat diaudit" berdasarkan tipe entitas CustomerAddressHistory .

Dalam hal ini, berbagai faktor dibahas dalam Tanya Jawab no. 1 dan T&J no. 2 , - keduanya tentang mengaktifkan kapabilitas temporal dalam basis data - benar-benar relevan.

Ilustrasi tata letak logis SQL-DDL

Akibatnya, dalam hal diagram yang ditampilkan dan dijelaskan di atas, saya menyatakan pengaturan level logis berikut (yang dapat Anda sesuaikan untuk memenuhi kebutuhan Anda dengan tepat):

-- You should determine which are the most fitting 
-- data types and sizes for all your table columns 
-- depending on your business context characteristics.

-- Also, you should make accurate tests to define the 
-- most convenient INDEX strategies based on the exact 
-- data manipulation tendencies of your business domain.

-- As one would expect, you are free to utilize 
-- your preferred (or required) naming conventions. 

CREATE TABLE Customer (
    CustomerNumber      INT      NOT NULL,
    SpecificAttribute   CHAR(30) NOT NULL,
    ParticularAttribute CHAR(30) NOT NULL,  
    CreatedDateTime     DATETIME NOT NULL,
    -- 
    CONSTRAINT Customer_PK PRIMARY KEY (CustomerNumber)
);

CREATE TABLE Address (
    AddressId           INT      NOT NULL,
    SpecificAttribute   CHAR(30) NOT NULL,
    ParticularAttribute CHAR(30) NOT NULL,  
    CreatedDateTime     DATETIME NOT NULL,  
    -- 
    CONSTRAINT Address_PK PRIMARY KEY (AddressId)
);

CREATE TABLE CustomerAddress (
    CustomerNumber  INT      NOT NULL,  
    AddressId       INT      NOT NULL,
    IsPhysical      BIT      NOT NULL,
    IsShipping      BIT      NOT NULL,  
    IsBilling       BIT      NOT NULL,
    IsActive        BIT      NOT NULL,
    CreatedDateTime DATETIME NOT NULL,  
    -- 
    CONSTRAINT CustomerAddress_PK           PRIMARY KEY (CustomerNumber, AddressId),
    CONSTRAINT CustomerAddressToCustomer_FK FOREIGN KEY (CustomerNumber)
        REFERENCES Customer (CustomerNumber),
    CONSTRAINT CustomerAddressToAddress_FK  FOREIGN KEY (AddressId)
        REFERENCES Address  (AddressId)  
);

CREATE TABLE MyOrder (
    CustomerNumber      INT      NOT NULL,  
    OrderNumber         INT      NOT NULL,
    ShippingAddressId   INT      NOT NULL,
    BillingAddressId    INT      NOT NULL,    
    SpecificAttribute   CHAR(30) NOT NULL,
    ParticularAttribute CHAR(30) NOT NULL,  
    OrderDate           DATE     NOT NULL,
    CreatedDateTime     DATETIME NOT NULL,  
    -- 
    CONSTRAINT Order_PK                  PRIMARY KEY (CustomerNumber, OrderNumber),
    CONSTRAINT OrderToCustomer_FK        FOREIGN KEY (CustomerNumber)
        REFERENCES Customer        (CustomerNumber),
    CONSTRAINT OrderToShippingAddress_FK FOREIGN KEY (CustomerNumber, ShippingAddressId)
        REFERENCES CustomerAddress (CustomerNumber, AddressId),
    CONSTRAINT OrderToBillingAddress_FK  FOREIGN KEY (CustomerNumber, BillingAddressId)
        REFERENCES CustomerAddress (CustomerNumber, AddressId)          
);

CREATE TABLE AddressHistory (
    AddressId           INT      NOT NULL,
    AuditedDateTime     DATETIME NOT NULL,
    SpecificAttribute   CHAR(30) NOT NULL,
    ParticularAttribute CHAR(30) NOT NULL,  
    CreatedDateTime     DATETIME NOT NULL,  
    -- 
    CONSTRAINT AddressHistory_PK          PRIMARY KEY (AddressId, AuditedDateTime),
    CONSTRAINT AddressHistoryToAddress_FK FOREIGN KEY (AddressId)
        REFERENCES Address  (AddressId)    
);

CREATE TABLE CustomerAddressHistory (
    CustomerNumber  INT      NOT NULL,  
    AddressId       INT      NOT NULL,
    AuditedDateTime DATETIME NOT NULL,    
    IsPhysical      BIT      NOT NULL,
    IsShipping      BIT      NOT NULL,  
    IsBilling       BIT      NOT NULL,
    IsActive        BIT      NOT NULL,
    CreatedDateTime DATETIME NOT NULL,  
    -- 
    CONSTRAINT CustomerAddressHistory_PK                  PRIMARY KEY (CustomerNumber, AddressId, AuditedDateTime),
    CONSTRAINT CustomerAddressHistoryToCustomerAddress_FK FOREIGN KEY (CustomerNumber, AddressId)
        REFERENCES CustomerAddress (CustomerNumber, AddressId)
);

Jika Anda ingin melihatnya, saya mengujinya di db <> biola yang berjalan pada SQL Server 2017.

The Historytabel

Kutipan dari pertanyaan Anda berikut ini sangat penting:

Yang saya cari adalah bagaimana saya bisa mengatur alamat saya sehingga ketika saya mengeditnya, pesanannya tidak terpengaruh oleh kenyataan bahwa seorang pelanggan memperbarui alamatnya atau pindah.

The AddressHistorydan CustomerAddressHistorymeja bantuan dalam memastikan bahwa Orde tidak terpengaruh oleh Alamat perubahan, karena semua “sebelumnya” baris harus dipertahankan di masing-masing Historymeja dan dapat bertanya bila diperlukan. Operasi UPDATE dan DELETE pada dua tabel ini harus dilarang (mencoba mengubah sejarah bahkan dapat memiliki implikasi hukum negatif).

The Interval mencakup antara nilai tertutup dalam AddressHistory.CreatedDateTimedan AddressHistory.AuditedDateTimeberdiri untuk seluruh periode di mana sebuah “masa lalu” tertentu Addressberturut-turut dianggap “hadir”, “saat ini” atau “efektif”. Pertimbangan serupa berlaku untuk CustomerAddressHistorybaris.

Kolom CustomerAddress.IsActiveBIT (boolean) dimaksudkan untuk menunjukkan apakah suatu Addressbaris tertentu “dapat digunakan” oleh satu Customerbaris atau tidak; mis., jika disetel ke 'false', itu akan menyampaikan fakta bahwa Pelanggan tidak lagi menggunakan Alamat itu dan karenanya tidak dapat digunakan untuk Pesanan baru .


Catatan : Di sisi lain, saya telah melihat beberapa sistem di mana setiap kali Orde baru diberlakukan, informasi Alamat harus dimasukkan (beberapa kali berulang-ulang), dan Alamat yang digunakan untuk Pesanan sebelumnya tidak pernah dihapus (karenanya yang Pesanan tidak terpengaruh oleh Alamat perubahan).

Tindakan ini jelas dapat melibatkan volume redundansi yang substansial, tetapi ada kemungkinan bahwa - tergantung pada persyaratan informasi yang tepat dari domain bisnis Anda - dapat bekerja, jadi Anda mungkin ingin mengevaluasi pro dan kontranya juga.


Penerimaan data

Versi "sekarang", "saat ini" atau "efektif" dari kejadian Alamat harus dimasukkan sebagai baris dalam Addresstabel, tetapi PILIH "status" sebelumnya dari Alamat DARI tabel AddressHistory(atau dari CustomerAddressHistory) itu mudah, dan itu mungkin menjadi latihan yang menarik untuk meningkatkan keterampilan coding SQL Anda.

Sehubungan dengan salah satu situasi yang Anda sebutkan dalam komentar, jika Anda ingin mengambil "versi kedua hingga terakhir" dari satu Addressbaris dari AddressHistory, Anda harus mempertimbangkan MAX(AddressHistory.AuditedDateTime)dan AddressHistory.AddressIdyang cocok dengan Address.AddressIdnilai tertentu yang ada.

Dalam hal ini-setidaknya ketika membangun relasional database-, sangat nyaman untuk pertama menentukan sesuai konseptual skema (berdasarkan berlaku aturan bisnis ) dan setelah itu mendeklarasikan selanjutnya logis pengaturan DDL. Setelah Anda mendapatkan versi yang stabil dan dapat diandalkan dari elemen-elemen fundamental ini (yang, tentu saja, dapat berkembang dari waktu ke waktu), sekarang saatnya untuk menganalisis dan menentukan cara terbaik untuk memanipulasi (melalui INSERT, UPDATE, HAPUS, dan SELECT operasi atau kombinasi daripadanya) tentang data.

Persepsi pengguna akhir, pandangan dan bantuan program aplikasi

Jelas, pada tingkat eksternal abstraksi, informasi alamat dianggap (oleh pengguna akhir) sebagai bagian dari suatu Pesanan , dan tidak ada yang salah dengan itu, tetapi itu tidak berarti bahwa pemodel harus merancang bagian-bagian penting dari database yang dimaksud seperti itu. Pada titik ini, jika perlu, misalnya, mencetak Pesanan "penuh" (sangat layak), Anda dapat "mereproduksi" itu sesuai permintaan dengan bantuan beberapa operator BERGABUNG dan klausa WHERE (mengingat periode validitas yang bersangkutan) , dll.) mungkin diperbaiki dalam pandangan untuk konsumsi di masa mendatang, mengirimkan hasil terkait diatur ke program aplikasi terkait yang, pada gilirannya, dapat meningkatkan formatnya sesuai kebutuhan.

Tentu saja, program aplikasi akan sangat membantu juga ketika Pesanan sedang dilakukan; misalnya, jendela aplikasi desktop / seluler atau halaman web dapat:

  • hanya menampilkan Alamat yang telah ditetapkan oleh Pelanggan yang terlibat sebagai "dapat digunakan" (via CustomerAddress.IsActive);
  • daftar bersama semua Alamat yang telah diaktifkan Pelanggan untuk layanan penagihan (via CustomerAddress.IsBilling); dan
  • kelompokkan semua Alamat yang telah ditentukan Pelanggan untuk layanan pengiriman (via CustomerAddress.IsShipping);

memfasilitasi dengan cara ini semua proses yang terlibat di GUI (yaitu, tingkat eksternal abstraksi sistem komputerisasi).


Bacaan yang disarankan

Anda meminta (dalam komentar yang sekarang dihapus) beberapa petunjuk tentang literatur database suara; Oleh karena itu, seperti untuk teoritis bahan, saya sangat menyarankan bahwa Anda membaca semua karya yang ditulis oleh Dr EF Codd , seorang Turing Award penerima dan, tentu saja, satu-satunya pencetus dari model relasional data (mungkin sekarang lebih relevan daripada sebelumnya). Daftar ini mencakup beberapa artikel dan makalahnya yang sangat berpengaruh.

Dua karya penting yang tidak termasuk dalam daftar tersebut di atas adalah, tepatnya, ACM Turing Award Lecture-nya yang berjudul Relational Database: A Practical Foundation for Productivity , dari 1981, dan bukunya dengan denominasi The Relational Model for Database Management: Versi 2 , yang diterbitkan pada tahun 1990.

Di depan desain konseptual , Definisi Terpadu untuk Pemodelan Informasi (IDEF1X) adalah teknik yang sangat direkomendasikan yang didefinisikan sebagai standar pada bulan Desember 1993 oleh Institut Nasional Standar dan Teknologi (NIST).

MDCCL
sumber
1
Maaf, saya tahu posnya lebih tua, tetapi mengapa Anda merujuk (misalnya: Alamat REFERENSI (AddressId)) di MyOrder? Kenapa tidak CustomerAddress?
Shadrix
1
Jangan khawatir, dan tangkapan yang bagus: Faktanya, keduanya MyOrder.ShippingAddressIddan MyOrder.BillingAddressIdharus membuat referensi ke CustomerAddress.AddressId(dan bukan ke Address.AddressId); dengan cara ini orang memastikan bahwa Pesanan secara eksklusif dapat dikaitkan dengan Alamat yang sebelumnya terhubung dengan Pelanggan yang membuat Pesanan itu . Diagram menyarankan pengaturan ini, sehingga DDL akan lebih akurat. Terima kasih telah meminta klarifikasi itu.
MDCCL
2
@ Shadrix Saya baru saja mengedit posting, jika Anda ingin melihatnya.
MDCCL
@ MBCCL Ketika Anda mengatakan tidak ada UPDATE dan HAPUS di atas Historymeja, haruskah itu sama dengan Addressmeja? Bagaimana jika Pelanggan memesan sesuatu dan kemudian hanya mengubah kode pos atau kota hanya satu bidang. Kita harus memasukkan alamat yang ada ke dalam Historydan kemudian membuat masukkan baru ke dalam Addresstabel, kan?
Mike Ross
1
OTOH, jika seorang Pelanggan ingin mengubah satu atau lebih informasi tentang Alamat yang diberikan , seseorang harus memastikan bahwa (a) Addressbaris terkait yang "hadir" sampai modifikasi yang terjadi dimasukkan ke dalam AddressHistorytabel, dan juga bahwa (b ) Addressbaris yang dimaksud adalah DIPERBARUI dengan nilai baru. Akan menguntungkan untuk melakukan proses ini sebagai satu unit kerja di dalam suatu transaksi.
MDCCL
3

Jawaban ini dikompilasi dari komentar ke pertanyaan.

Salah satu solusinya adalah dengan menggunakan FK ke tabel alamat di tabel pesanan. Itu akan memungkinkan Anda melihat alamat yang digunakan untuk pesanan, dan memisahkan alamat dari alamat Pengguna saat ini.

Untuk membuat ini berfungsi, Anda harus memasukkan alamat baru dan menautkan alamat baru itu ke tabel Pengguna. Ini berarti bahwa alamat ditulis satu kali dan hasil edit adalah ilusi bagi pengguna akhir. Anda dapat secara efektif menyimpan riwayat semua alamat yang dikaitkan dengan pengguna dengan memindahkan asosiasi dari tabel Pengguna ke tabel asosiasi dengan cap waktu. Itu akan memberi Anda sejarah pengeditan / alamat, dan memelihara data yang tidak dapat diubah di tabel Alamat.

@MDCCL menyatakan:

[Anda harus] mengatur struktur basis data Anda memiliki satu tabel untuk menyimpan data terkait Pesanan, dan tabel lain untuk menyimpan informasi Alamat. Dan, ya, Anda pasti dapat memiliki tabel yang mewakili hubungan banyak-ke-banyak antara kedua jenis entitas ini. Jika Pengguna dapat mengubah atribut Alamatnya, maka Anda harus melacak modifikasi tersebut, sehingga Anda harus mengaktifkan yang sesuai AddressHistory. Posting ini terkait dengan aspek yang terakhir.

MDCCL juga memberikan ikhtisar tentang cara menemukan alamat saat ini untuk pengguna di sini:

Untuk mengambil versi terbaru dari tabel Riwayat yang Anda miliki, Anda harus memperhitungkan MAX(AuditedDateTime)yang sesuai AddressId. Langkah pertama adalah memodelkan / merancang pengaturan konseptual dan logis terbaik Anda, langkah kedua adalah menemukan cara yang tepat untuk INSERT, UPDATE, DELETE dan SELECT data Anda.

Erik
sumber