Tabela zamówień eCommerce. Zapisać ceny lub skorzystać z tabeli audytu / historii?

13

Projektuję mój pierwszy schemat eCommerce. Od jakiegoś czasu czytam ten temat i jestem trochę zdezorientowany relacjami między order_line_itemaproduct

productPuszki zostały zakupione. Ma różne szczegóły, ale najważniejsze jest unit_price.

order_line_itemPosiada klucz obcy do product_idzakupiona, to quantityzakupiono i unit_priceprzy momencie klient zakupił produkt.

Większość z tego co czytałem, mówi, że unit_pricena order_line_itemnależy wyraźnie dodanej (czyli nie poprzez odwołanie product_id). Ma to sens, ponieważ sklep może w przyszłości zmienić cenę, co zepsuje raporty zamówień, śledzenie, integralność itp.

Nie rozumiem tylko, dlaczego bezpośrednio zapisać unit_pricewartość order_line_item?

Czy nie byłoby lepiej stworzyć tabelę audytu / historii, która dokumentuje unit_pricezmianę product?

Po utworzeniu an dodawany jest order_line_itemklucz obcy product_audittabeli i stamtąd można pobrać cenę (przez odniesienie).

Wydaje mi się, że stosowanie tego podejścia ma wiele zalet (mniej powielania danych, historii zmian cen itp.), Więc dlaczego nie jest częściej stosowane? Nie spotkałem się z przykładem schematu eCommerce, który wykorzystuje to podejście, czy coś mi brakuje?

UDPATE: Wygląda na to, że moje pytanie dotyczy powolnie zmieniającego się wymiaru . Nadal jestem zdezorientowany, ponieważ powolnie zmieniający się wymiar dotyczy hurtowni danych i OLAP. Czy zatem w mojej głównej bazie danych procesów biznesowych (OLTP) można zastosować typy Slowy Changing Dimension? Zastanawiam się, czy pomieszam wiele pojęć, Byłbym bardzo wdzięczny za pewne wskazówki.

Gaz_Edge
źródło
Właściwie zrobiłbym oba: przechowywać cenę sprzedaży w wierszu zamówienia i przechowywać historię cen produktów. Ponieważ oba służą różnym celom. Przechowywanie ceny sprzedaży sprawi, że zapytania dotyczące zamówień będą znacznie łatwiejsze i szybsze. Z drugiej strony możesz chcieć odzyskać stare ceny, nawet dla produktów, które nie zostały sprzedane.
a_horse_w_no_name
Z pewnością ułatwienie zapytań o zamówienie nie może być jedynym motorem za każdym razem oszczędzania ostatecznej ceny. W końcu jest to relacyjna baza danych - zapytania nie byłyby o wiele trudniejsze.
Gaz_Edge
3
Przechowywanie ceny sprzedaży w wierszu zamówienia nie jest „nierelacyjne” (i uważam, że jest znormalizowane, a moim zdaniem ceny sprzedaży są bezpośrednim atrybutem wiersza zamówienia). Sztuka korzystania z relacyjnej bazy danych polega na znajomości ograniczeń. Jeśli prowadzisz sklep ze 100 produktami i 5 zamówieniami dziennie, przechowywanie i wyszukiwanie starych cen nie stanowi problemu. Jeśli prowadzisz targowisko z milionami produktów, tysiącami dealerów i setkami zamówień na minutę, to sprawdzenie historii będzie problemem.
a_horse_w_no_name
Gdzie będziesz przechowywać ceny historyczne? Z tego, co czytam, potrzebowałbym dwóch baz danych. Jeden dla OLTP jeden dla OLAP. Historia idzie w OLAP?
Gaz_Edge

Odpowiedzi:

10

Jak już zidentyfikowałeś, zapisanie ceny na zamówieniu ułatwia techniczną realizację. Istnieje wiele powodów biznesowych, dla których może to być korzystne.

Oprócz transakcji internetowych wiele firm wspiera sprzedaż innymi kanałami, np .:

  • Przez telefon
  • Przedstawiciele handlowi „w drodze”
  • W fizycznej lokalizacji (np. Sklep, biuro)

W takich przypadkach zamówienie może zostać wprowadzone do systemu w pewnym momencie po transakcji. W takich okolicznościach prawidłowe określenie historycznego rekordu ceny może być trudne - niemożliwe jest zapisanie ceny jednostkowej bezpośrednio na zamówieniu.

Wiele kanałów często stanowi kolejne wyzwanie - różne ceny tego samego produktu. Opłaty za zamówienia telefoniczne są powszechne - i niektórzy klienci mogą negocjować sobie zniżki. Możliwe, że możesz przedstawić wszystkie możliwe ceny dla wszystkich kanałów w schemacie produktu, ale włączenie ich do swoich tabel zamówień może stać się (bardzo) złożone.

Wszędzie tam, gdzie negocjacje są dozwolone, bardzo trudno jest połączyć historię cen z ustaloną ceną zamówienia (chyba że agenci mają bardzo wąskie limity negocjacji). Musisz zapisać cenę na samym zamówieniu.

Nawet jeśli obsługujesz tylko transakcje internetowe i masz stosunkowo prostą strukturę cenową, nadal istnieje ciekawy problem do rozwiązania - jak należy sobie poradzić ze wzrostem cen w transakcjach lotniczych? Czy firma nalega, aby klient musiał zapłacić podwyżki, czy też honoruje pierwotną cenę (kiedy produkt został dodany do koszyka)? Jeśli jest to ten ostatni, jego techniczna implementacja jest skomplikowana - musisz znaleźć sposób, aby upewnić się, że poprawnie utrzymujesz wersję cenową w sesji.

Wreszcie wiele firm zaczyna stosować bardzo dynamiczne ceny. Nie może istnieć jedna stała cena dla danego produktu - zawsze jest obliczana w czasie wykonywania na podstawie czynników takich jak pora dnia, popyt na produkt i tak dalej. W takich przypadkach cena nie może być przechowywana w stosunku do produktu!

Chris Saxon
źródło
3

Dodam kilka praktycznych punktów, które widziałem.

  1. Produkty są przejściowe.

    To, co mogą oznaczać dzisiaj, może nie być takie samo, jak to, co oznaczało rok temu. Ten sam kod SKU (i stąd ID_produktu) może odnosić się do innego wariantu / rodzaju produktu na różnych etapach.

    Nie wszyscy rozumieją wszystkie aktualne obawy; dlatego użytkownik może zmienić atrybuty oryginalnego produktu zamiast tworzyć nowy z własnej niewiedzy. Wiele razy może się to zdarzyć z powodu planu, w którym użytkownik jest uruchomiony (hej! Mogę mieć tylko 100 SKU, więc dlaczego nie zmieniać starych zamiast uaktualniać plan). Widzicie, w wielu wózkach , produkt nigdy nie będzie oznaczał tego samego na zawsze.

  2. Różne ceny w zależności od warunków zamówienia i wysyłki

    Jak wspomniał użytkownik @Chris, w różnych scenariuszach mogą obowiązywać różne ceny.

    W większości wózków znajdują się co najmniej 3 różne pola - cena jednostkowa, kwota rabatu i cena z rabatem. W bardziej zaawansowanych znajdziesz jeszcze 2 - cena jednostkowa z podatkiem, cena z rabatem z podatkiem. Możesz znaleźć jeszcze kilka pól opisujących opłaty za metodę wysyłki i dodatkowe opłaty za metodę płatności. Procenty podatkowe mogą się różnić w zależności od stanu, produktu, kraju, metody wysyłki itd., Podobnie jak inne kategorie kosztów. Podobnie rabaty mogą się różnić w zależności od położenia geograficznego, promocji, czasu sprzedaży i tak dalej. W związku z tym istnieją informacje, które można uzyskać tylko na poziomie zamówienia, a tych połączonych informacji nie można wygenerować z danych w samej tabeli produktów.

  3. Rozdzielenie obaw

    Wiele koszyków jest implementowanych w taki sposób, aby różne zespoły mogły kontrolować różne części danych. Ktoś zarządzający systemem zamówień nie zawsze musi wiedzieć, jakie wszystkie produkty są w magazynie, jakie były ceny w różnym czasie, jakie są alternatywy dla danego SKU i tak dalej. Przechowywanie danych związanych z produktem wraz z danymi zamówienia pomaga osiągnąć rozdzielenie obaw. Może to być również prawdą na etapach rozwoju, jeśli różne zespoły zarządzają różnymi częściami systemu.

  4. Łatwiejsza skalowalność w wielu systemach

    Często system zarządzania zamówieniami, silnik reguł, silnik katalogu, system zarządzania treścią są zbudowane / utrzymywane jako osobne systemy. Pomaga to zoptymalizować pod kątem różnych warunków obciążenia i wygenerować specjalistyczną inteligencję dla każdego systemu. Zatem jeden system nie może być objęty okupem z powodu niedostępności informacji z innego systemu.

  5. Szybszy rozwój i czas działania

    Użyłem tutaj terminu „czas programowania”, chociaż użycie „czasu debugowania” byłoby bardziej trafne. Ilekroć dzieje się jakikolwiek nowy rozwój, będzie to szybsze, jeśli potrzebne dane będą dostępne bez dodawania własnej złożoności, ponieważ wtedy będą występowały stosunkowo mniejsze cykle debugowania.

    Wyobraź sobie, że zostałeś poproszony o generowanie raportów na żądanie dla rabatów oferowanych na codzień dla danego miesiąca pół roku wstecz. Jeśli masz oryginalną cenę, obniżoną cenę w 1-2 tabelach wraz z zamówieniem, szczegóły zamówienia, jest to całkiem proste. Jeśli jednak musisz przejść i pobrać ceny z innego stołu, a następnie odpowiednie rabaty z innego stołu, a następnie dowiedzieć się szczegółów, zarówno rozwój, jak i czas działania będą wyższe.

Dobry projekt powinien próbować zoptymalizować zarówno na przyszłość, jak i na teraźniejszość.

mu 無
źródło
2

Może to kosztować więcej miejsca w magazynie, ale wolę przechowywać wszystkie istotne szczegóły sprzedaży z samą transakcją, aby jeśli z jakiegokolwiek powodu nasza ścieżka audytu uległa uszkodzeniu lub administrator zignorował istniejące zabezpieczenia, szczegóły dotyczące dostępne są sprzedaż: używana waluta, cena jednostkowa, ilość, zastosowane podatki i jaką wartość uzyskali itp. Zazwyczaj przechowuję ten plik jako XML, aby był elastyczny w zależności od sprzedaży.


EDYCJA: Aby rozwinąć to, co krótko powiedziałem powyżej, w moim komentarzu uzupełniającym poniżej oraz o tym, czego dotyczyła @a_horse_w_na_nazwie, nadmiarowość danych transakcyjnych jest nie tylko ważna, ale także konieczna w skali.

Zakładam, że budujesz przy użyciu OOP, więc prawdopodobnie powinieneś mieć obiekt transakcji i albo obejmujący wszystko produkt i / lub obiekt ceny. Z własnego doświadczenia wolę bycie gadatliwym w swojej historii, przechowywanie jest stosunkowo tanie.

Stworzyliśmy historię obiektów, którą możesz ułatwić za pomocą istniejącego RDBMS lub jakiegoś smaku magazynu wartości kluczy NOSQL (lub jeszcze lepiej RDBMS, który pozwala na połączenia typu NoSQL, takie jak handlersocket lub memcache), i przechowujemy historię obiektów w ten sposób, przy każdym detalu i zmianie ceny w jednym miejscu łatwo i szybko dostępne. Jeśli mówisz poważnie, możesz nawet użyć DIFF, aby zaoszczędzić na pamięci i przechowywać zmiany tylko do przodu, chociaż ma to swoje własne zastrzeżenia. To powinno zadbać o twoją historię, a zaletą serializowanych obiektów jest to, że twój system będzie / powinien być w stanie przywrócić je jako obiekty, które były przechowywane. To zajmuje się historią.

Jeśli chodzi o moją sugestię, przechowywanie szczegółów transakcji, takich jak podatki, waluta itp. Wraz z samą transakcją, oznacza, że ​​nie trzeba szukać tych szczegółów w innym miejscu, obiekt transakcji będzie wiedział o jego właściwościach, a Ty możesz zająć się prezentacją zmieniające się dane według własnego uznania. Uzyskujesz szybki dostęp do migawki i dodatkowo zyskujesz na zbędnych i weryfikowalnych rekordach.

Warto, zaufaj mi!

oucil
źródło
nie ma sensu mówić, że go nie używasz, ponieważ można go usunąć. Możesz zastąpić dowolne dane. Każda strategia powinna przechowywać kopie zapasowe
Gaz_Edge
@Gaz_Edge Nie wykluczają się wzajemnie, nadal można korzystać ze ścieżki audytu i przechowywać szczegóły z transakcjami, w tym przypadku nadmiarowość jest uzasadniona. Nigdy nie zostawiaj się z danymi tego ważnego tematu w jednym punkcie awarii. W naszym przypadku używam scentralizowanego magazynu obiektów jako mechanizmu historii, zamiast próbować budować historię według typu obiektu (w tym przypadku transakcji lub produktu). Będę miał cały obiekt transakcji w historii, ale wszystkie najważniejsze szczegóły z samym rekordem. To całkowicie pomija przedmioty produktu w historii.
oucil
a jeśli chciałbym generować raporty dotyczące zamówień? Na przykład ile produktów x zostało zakupionych? Lub ile zamówień przekracza twoją kwotę? Zapisywanie jako XML oznacza, że ​​tracisz możliwość zapytania o dane, chyba że się mylę
Gaz_Edge
@Gaz_Edge Nieprawda, jeśli mówisz o MySQL, masz SELECT ExtractValue(field_name, '/x/path/');możliwość filtrowania pod kątem takich rzeczy, jak wszystkie transakcje w określonej walucie lub wszystkie transakcje o określonej minimalnej wartości podatkowej lub cokolwiek innego. Raporty o większej skali można wykonać z historii obiektów. W przypadku raportów na większą skalę możesz skonfigurować elasticsearchserwer / instancję, która ma raportowanie w stylu BigData i łatwo skaluje się do wielu milionów dokumentów +.
oucil
@Gaz_Edge Powinienem także wspomnieć, że raporty, o których mówisz (zakupy ponad wartość, sprzedaż produktu itp.) Są powszechnymi raportami i powinny mieć wartości przechowywane jako wartości kolumnowe z rekordem transakcji w celu szybszego przetwarzania. Wszystko, co jest ważne, ale niekoniecznie zgłaszane często, może przejść do XML. Dane migawki są tak naprawdę tylko dla dwóch rzeczy: 1. Powoli zmieniającego się wymiaru oraz 2. Sprawdzania poprawności i porównania, gdy klient narzeka, i musisz szybko sprawdzić, kto ma rację. To nie jest do codziennego użytku.
oucil
1

Głosowałbym za zapisaniem ceny jednostkowej w elemencie zamówienia i śledzeniem historii cen produktów w osobnej tabeli. Moim uzasadnieniem jest zwiększenie elastyczności.

Nawet jeśli twoja struktura cenowa jest sztywna i dobrze zdefiniowana i nie pozwala na odmiany wspomniane powyżej przez @Chris Saxon, czy czujesz się komfortowo, że zawsze tak będzie? Nawet jeśli jesteś pewny siebie, po co malować się w kącie? Myślę, że dobrym pomysłem byłoby przechowywanie tego w szczegółach elementu zamówienia, ponieważ nie mogę wymyślić żadnego ważnego powodu, aby go rozdzielić.

Jeśli chodzi o przechowywanie historii cen, istnieje pewna wartość w przechowywaniu tego osobno, ponieważ mogą wystąpić zmiany ceny przedmiotu i nikt go nie kupił. To zdecydowanie przydatne informacje, jeśli zmiana ceny byłaby nieskuteczna. Jak wspomniano, jest to klasyczny przypadek użycia powolnie zmieniającego się wymiaru typu 2 w scenariuszu hurtowni danych. Zazwyczaj każda zmiana ceny w tabeli produktów byłaby wychwytywana, a nowy wiersz byłby dodawany do tabeli wymiarów ze zaktualizowaną ceną i datownikiem wskazującym, kiedy ta zmiana miała miejsce. W poprzednim wierszu data zakończenia byłaby aktualizowana, aby wskazać, że nie jest to już cena efektywna. Jednym z podejść byłoby więc śledzenie tego rodzaju zmian w hurtowni danych.

Jeśli jednak nie chcesz zajmować się projektowaniem schematu hurtowni danych i procesem ETL w tym samym czasie, co projektowaniem bazy danych e-commerce OLTP, ta historia z pewnością może zostać przechwycona w naszej bazie danych e-commerce. Można to zrobić zgodnie z opisem, tworząc oddzielną tabelę produktu_audit, która zwisa z tabeli produktów i zawiera daty rozpoczęcia i zakończenia, kiedy obowiązywała ta wersja produktu. Można to również zrobić w samej tabeli produktów, dodając do tabeli daty rozpoczęcia i zakończenia, aby wskazać, który produkt jest obecnie aktywny. Jednak w zależności od liczby produktów i liczby lub zmian cen, które przechodzi Twoja firma, może to spowodować, że tabela produktów będzie znacznie większa niż zamierzona, i może później powodować problemy z wydajnością zapytań.

Wreszcie, oddzielenie historii cen od rzeczywistej ceny jednostkowej w elemencie zamówienia może zdecydowanie dać inne możliwości analityczne, aby zobaczyć, kiedy produkt był sprzedawany po cenie, która była wówczas wyższa lub niższa od podanej ceny.

njkroes
źródło
Dziękuję za odpowiedź. Myślę, że strategią, którą zamierzam, będzie zaprojektowanie OLTP według książki. Naprawdę podoba mi się pomysł posiadania hurtowni danych do raportowania. Mimo że dodaje trochę dodatkowej pracy (tworzenie nowego schematu), schemat można dostosować do OLAP. W pewnym sensie unikam scenariusza, w którym próbuję wpasować kwadratowy kołek w okrągły otwór.
Gaz_Edge
@Gaz_Edge: Jestem w podobnej sytuacji, jeśli chodzi o decyzję dotyczącą mojego nowego projektu. Czy możesz podzielić się swoimi przemyśleniami na temat tego, jakie podejście projektowe zastosowałeś rok temu? Czy to działało dobrze?
Coder Absolute
@CoderAbsolute moje oryginalne rozwiązanie było bardzo zorientowane na „bazę danych”. Moja aplikacja jest teraz skoncentrowana wokół architektury zorientowanej na usługi. Mam teraz wiele małych oddzielonych schematów baz danych niż jeden ściśle powiązany. Zniknęła potrzeba 3N w jednym ogromnym schemacie. Dodaję teraz cenę jednostkową bezpośrednio do zamówienia. Utrzymywanie historycznych zmian cen produktów zniknęło, ponieważ nie było to wymogiem biznesowym. Mam nadzieję, że to pomaga.
Gaz_Edge
0

Całkowicie zgadzam się z podstawową ideą utrzymywania razem informacji związanych z zamówieniem (kontekst). Drobna uwaga: taka sytuacja pojawi się tylko wtedy, gdy projektujesz aplikację bardzo skoncentrowaną na bazie danych i wszystko kręci się wokół dużego tłustego db. Jeśli zmienisz punkt widzenia, patrząc na problematyczną domenę pod innym kątem, wyraźnie zauważysz, że zamówienie jest uchwyconą migawką wyjątkowego zdarzenia w cyklu życia aplikacji. Gdy rozwiążesz problemy na podstawie kontekstu, problemy z bazą danych staną się drugorzędne, a złożoność, której wszyscy boją się w przypadku zapytań i tworzenia raportów, będzie bezproblemowo obsługiwana w modelu domeny.

Waku-2
źródło
Niektóre małe przykłady znacznie ułatwiłyby odpowiedź. Zwłaszcza zdania typu „zamówienie to uchwycona migawka wyjątkowego wydarzenia w cyklu życia aplikacji”.
dezso,