Projektuję mój pierwszy schemat eCommerce. Od jakiegoś czasu czytam ten temat i jestem trochę zdezorientowany relacjami między order_line_item
aproduct
product
Puszki zostały zakupione. Ma różne szczegóły, ale najważniejsze jest unit_price
.
order_line_item
Posiada klucz obcy do product_id
zakupiona, to quantity
zakupiono i unit_price
przy momencie klient zakupił produkt.
Większość z tego co czytałem, mówi, że unit_price
na order_line_item
należ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_price
wartość order_line_item
?
Czy nie byłoby lepiej stworzyć tabelę audytu / historii, która dokumentuje unit_price
zmianę product
?
Po utworzeniu an dodawany jest order_line_item
klucz obcy product_audit
tabeli 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.
źródło
Odpowiedzi:
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 .:
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!
źródło
Dodam kilka praktycznych punktów, które widziałem.
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.
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.
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.
Ł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.
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ść.
źródło
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!
źródło
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ćelasticsearch
serwer / instancję, która ma raportowanie w stylu BigData i łatwo skaluje się do wielu milionów dokumentów +.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.
źródło
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.
źródło