Dokumentujemy tu działanie bazy danych dla stacjonarnego sklepu Geeks & Dragons, funkcjonującego we Wrocławiu od roku1. Zajmuje się on sprzedażą i wypożyczaniem gier planszowych (oraz podobnych) oraz organizacją turniejów w tych grach.
- Dokumentacja konstrukcji bazy danych
- Spis treści
- Schemat bazy danych
- Zawartość tabel oraz zależności funkcyjne
- Tabela
city
- Tabela
customers
- Tabela
participations
- Tabela
tournaments
- Tabela
rental
- Tabela
inventory
- Tabela
staff
- Tabela
relationships
- Tabela
partners
- Tabela
payments
- Tabela
invoices
- Tabela
maintenance_expenses
- Tabela
expense_titles
- Tabela
expense_types
- Tabela
sales
- Tabela
games
- Tabela
game_categories
- Tabela
game_types
- Tabela
game_prices
- Tabela
- Uzasadnienie normalności postaci bazy (EKNF)
Powrót do strony głównej: README.md.
Zaczniemy od schematu bazy danych, aby od początku mieć pojęcie, o jakiej strukturze relacyjnej mówimy. Diagram ER omawianej bazy danych przedstawiamy na poniższej interaktywnej wizualizacji.
UWAGA! Jeżeli używany przez Ciebie silnik MD nie generuje ilustracji z kodu mermaid
, możesz zobaczyć także zapis tej ilustracji (jest to plik svg; najlepiej otwierać go po pobraniu w przeglądarce). Dostępna jest też uproszczona wersja schematu (bez atrybutów i ich typów) w formacie png. Możesz go zobaczyć tutaj.
erDiagram
CITY ||--o{ CUSTOMERS : ""
CUSTOMERS ||--o{ PARTICIPATIONS : ""
PARTICIPATIONS ||--o{ TOURNAMENTS : ""
CUSTOMERS ||--o{ RENTAL : ""
INVENTORY ||--o{ RENTAL : ""
STAFF ||--o{ RENTAL : ""
RENTAL ||--|| PAYMENTS : ""
STAFF ||--o{ SALES : ""
SALES ||--|| INVENTORY : ""
SALES ||--|| PAYMENTS : ""
CITY ||--o{ STAFF : ""
STAFF ||--o{ RELATIONSHIPS : ""
PARTNERS ||--|{ RELATIONSHIPS : ""
PAYMENTS ||--|| INVENTORY : ""
TOURNAMENTS ||--o| PAYMENTS : ""
PAYMENTS ||--|| SALES : ""
RENTAL ||--|| PAYMENTS : ""
RENTAL ||--o| PAYMENTS : ""
PAYMENTS ||--|| PARTICIPATIONS : ""
PAYMENTS ||--|| MAINTENANCE_EXPENSES : ""
EXPENSE_TITLES ||--|{ MAINTENANCE_EXPENSES : ""
EXPENSE_TYPES ||--o{ EXPENSE_TITLES : ""
STAFF ||--o{ TOURNAMENTS : ""
TOURNAMENTS ||--|| GAMES : ""
GAME_CATEGORIES ||--o{ GAMES : ""
GAME_TYPES ||--o{ GAMES : ""
GAMES ||--o{ INVENTORY : ""
GAME_PRICES ||--|{ INVENTORY : ""
INVOICES ||--|{ PAYMENTS : ""
CITY {
INT city_id PK
VARCHAR(40) city
TIMESTAMP updated_at
}
CUSTOMERS {
INT customer_id PK
VARCHAR(30) first_name
VARCHAR(40) last_name
VARCHAR(10) phone
VARCHAR(80) email
INT city_id FK
TIMESTAMP updated_at
}
PARTICIPATIONS {
INT particip_id PK
INT tournament_id FK
INT customer_id FK
INT place
TIMESTAMP sign_up_date
INT fee_payment_id FK
TIMESTAMP updated_at
}
TOURNAMENTS {
INT tournament_id PK
VARCHAR(60) name
INT game_id FK
TIME start_time
INT matches
DECIMAL(4p2) fee
INT staff_id FK
INT expenses_payments_id FK
DATE sign_up_deadline
TIMESTAMP updated_at
}
RENTAL {
INT rental_id PK
INT inventory_id FK
INT customer_id FK
TIMESTAMP rental_date
TIMESTAMP return_date
INT staff_id FK
INT payment_id FK
INT penalty_payment_id FK
INT rate
TIMESTAMP updated_at
}
INVENTORY {
INT inventory_id PK
INT game_id FK
CHAR(1) destination
INT price_id FK
BOOLEAN price_id
INT purchase_payment_id FK
TIMESTAMP delivery_date
TIMESTAMP updated_at
}
STAFF {
INT staff_id PK
VARCHAR(30) first_name
VARCHAR(40) last_name
VARCHAR(10) phone
VARCHAR(80) email
INT city_id FK
DECIMAL(6p2) current_salary
BOOLEAN is_manager
CHAR(1) gender
DATE from_date
DATE to_date
TIMESTAMP updated_at
}
RELATIONSHIPS {
INT relationship_id PK
INT staff_id FK
INT partner_id FK
INT dates_number
TIMESTAMP updated_at
}
PARTNERS {
INT partner_id PK
VARCHAR(30) name
CHAR(1) gender
TIMESTAMP updated_at
}
PAYMENTS {
INT payment_id PK
DECIMAL(7p2) amount
INT invoice_id FK
TIMESTAMP updated_at
}
INVOICES {
INT invoice_id PK
TIMESTAMP date
TIMESTAMP updated_at
}
MAINTENANCE_EXPENSES {
INT spend_id PK
INT expense_id FK
INT date FK
TIMESTAMP date
TIMESTAMP updated_at
}
EXPENSE_TITLES {
INT title_id PK
VARCHAR(200) title
INT expenses_type_id FK
TIMESTAMP updated_at
}
EXPENSE_TYPES {
INT expenses_type_id PK
VARCHAR(40) expenses_type
TIMESTAMP updated_at
}
SALES {
INT sale_id PK
INT inventory_id FK
INT staff_id FK
INT payment_id FK
BOOLEAN return_oper
TIMESTAMP date
TIMESTAMP updated_at
}
GAMES {
INT game_id PK
VARCHAR(255) title
TEXT description
INT category_id FK
INT type_id FK
BOOLEAN competitivity
TIMESTAMP updated_at
}
GAME_CATEGORIES {
INT category_id PK
VARCHAR(40) game_category
TIMESTAMP updated_at
}
GAME_TYPES {
INT type_id PK
VARCHAR(40) game_type
TIMESTAMP updated_at
}
GAME_PRICES {
INT price_id PK
DECIMAL(5p2) current_price
TIMESTAMP updated_at
}
Następnie, po kolei opiszemy co znajduje się w poszczególnych tabelach, kreśląc generalną mechanikę, jaką przyjęliśmy. Dla każdej tabeli wypisujemy też wraz z komentarzem listę zależności funkcyjnych (a właściwie nieredundantne pokrycie). Pomijamy zatem zależności, które można wywnioskować trzema regułami Armstronga, a w szczególności oczywiście zależności trywialne.
Jest to spis wszystkich miejscowości (w naszym przypadku są to dla uproszczenia miasta Dolnego Śląska), które dotyczą bądź kiedyś dotyczyły obsługi oraz klientów.
Atrybut | Opis |
---|---|
city_id |
numer identyfikacyjny miasta (PK) |
city |
nazwa miejscowości |
updated_ad |
moment ostatniej zmiany w krotce |
Zależności funkcyjne to:
- {
city_id
} → {city
,updated_at
}
Nie może się tu wiele więcej zdarzyć. Można by też myśleć o sytuacji, gdzie klucz główny zależy od nazwy miasta. Zwróćmy jednak uwagę, że na ogół występują różne miejscowości o takich samych nazwach. Z tego powodu pomijamy tego typu zapis.
Mamy tutaj zarejestrowanych klientów sklepu, czyli uczestników gier turniejowych oraz tych, którzy choć raz wypożyczali jakiś produkt. Zapisujemy ich podstawowe dane. W uproszczeniu nie zbieramy całego ich adresu zamieszkania, a jedynie miasto. Zakładamy także, że wszyscy są z Dolnego Śląska (jest to uzasadnione przybliżenie, gdyż skala działania nie jest taka, aby posiadać klientów z całego kraju, ale też nie są oni tylko z Wrocławia).
Atrybut | Opis |
---|---|
customer_id |
numer identyfikacyjny klienta (PK) |
first_name |
imię |
last_name |
nazwisko |
phone |
numer telefonu kontaktowego |
email |
adres e-mail do kontaktu |
city_id |
numer identyfikacyjny miasta zamieszkania (FK) |
updated_at |
moment ostatniej zmiany w krotce |
Zależności funkcyjne to:
- {
customer_id
} → {first_name
,last_name
,phone
,email
,city_id
,updated_at
}
Istnieje możliwość, że adresy e-mail (email
) lub numery telefonów (phone
) nie są unikalne dla każdego uczestnika, gdyż w teorii kilka osób może korzystać z jednej skrzynki bądź telefonu przy kontakcie - na przykład jako organizacja. Przy uczestnictwie w wydarzeniach (takich jak choćby turnieje) jest to spotykana praktyka. To, że dodatkowo imiona, nazwiska, czy miasta niczego nie określają jednoznacznie, jest chyba oczywiste. Jedynie dodany osobno klucz główny może rozpoczynać nietrywialne zależności funkcyjne.
Jest to zbiór przypisań uczestników do turniejów. Każdy uczestnik może bowiem zapisać się wiele turniejów (maksymalnie jednokrotnie każdy). Co do ilości uczestników w turnieju, dozwolona jest zerowa, ale wtedy po prostu zawody mimo ogłoszenia się nie odbędą (bez konsekwencji w bazie). Maksymalnie jest zaś ona ograniczona przez ilość określonych w turnieju partii przemnożonych przez narzucony limit uczestników w konkretnej grze.
Atrybut | Opis |
---|---|
particip_id |
numer identyfikacyjny zapisu (PK) |
tournament_id |
numer identyfikacyjny turnieju (FK) |
customer_id |
numer identyfikacyjny klienta (FK) |
place |
miejsce zajęte przez uczestnika na danym turnieju |
sign_up_date |
czas zapisu danego uczestnika na turniej |
fee_payment_id |
numer identyfikacyjny płatności wpisowego za uczestnictwo (FK) |
updated_at |
moment ostatniej zmiany w krotce |
Zależności funkcyjne to:
- {
particip_id
} → {tournament_id
,customer_id
,place
,sign_up_date
,fee_payment_id
,updated_at
} - {
fee_payment_id
} → {particip_id
,tournament_id
,customer_id
,place
,sign_up_date
,updated_at
} - {
tournament_id
,customer_id
} → {particip_id
,place
,sign_up_date
,fee_payment_id
,updated_at
}
Poza działaniem opisanym wyżej, zajęte miejsca nie identyfikują żadnych wierszy. Wiele uczestników może też się w jednym momencie zapisać. Płatności zawsze są zaś dokonywane osobno.
Para identyfikatora turnieju oraz klienta jest sama w sobie kluczem kandydującym, bo określa jednoznacznie zapis.
Są to turnieje organizowane przez sklep. Jeden turniej dotyczy jednej konkretnej gry. Każdy składa się z konkretnej ilości meczy i ma jednego pracownika-opiekuna. Każdy rekord przechowuje dodatkowe dane na temat wydarzenia samego w sobie. Wydatki na organizację obejmują zakup nagród itp. (przy czym traktujemy wszystkie wydatki razem, jako jedna płatność). W jednym czasie zaś może odbywać się wyłącznie jeden turniej. Zakładamy, że lokal nie ma możliwości na więcej.
Jeżeli chodzi o schemat przeprowadzania turnieju, to dla wybranej gry rozgrywki planowane są na zasadzie drzewa. Jego wielkość zależy też od liczby zapisanych uczestników. Uczestnicy w miarę możliwości rozdzielani są tak, żeby wszystkie poziomy bazowe drzewa rozgrywek były związane z jakimś meczem (oczywiście nie zawsze z liczbą uczestników równą maksymalnej możliwej dla gry). W razie problemów z tą zasadą, któryś mecz może być opuszczony, zgodnie z regulaminem tego turnieju. Nie wpływa to jednak na zarejestrowaną dla turnieju ustaloną łączną liczbę partii.
Atrybut | Opis |
---|---|
tournament_id |
numer identyfikacyjny turnieju (PK) |
name |
nazwa turnieju |
game_id |
numer identyfikacyjny gry używanej w turnieju (FK) |
start_time |
dzień i godzina, w którym zaczyna się turniej |
matches |
ustalona liczba wszystkich partii w obrębie turnieju |
fee |
ustalona wpisowa opłata za uczestnictwo |
sign_up_deadline |
ostatni dzień, w którym otwarte są zapisy |
staff_id |
numer identyfikacyjny pracownika odpowiedzialnego za turniej (FK) |
expenses_payments_id |
numer identyfikacyjny płatności związanych z wydatkami na organizację (FK) |
updated_at |
moment ostatniej zmiany w krotce |
Zależności funkcyjne to:
- {
tournament_id
} → {name
,game_id
,start_time
,matches
,fee
,sign_up_deadline
,staff_id
,expenses_payments_id
,updated_at
} - {
start_time
} → {tournament_id
,name
,game_id
,matches
,fee
,sign_up_deadline
,staff_id
,expenses_payments_id
,updated_at
}
Sama nazwa turnieju nie identyfikuje wydarzenia, gdyż potencjalnie cykliczność może narzucić tę samą nazwę. Pozostałe (poza numerem oraz datą) atrybuty, nawet wzięte razem, nie mogą z zupełną pewnością zidentyfikować wydarzenia.
Ta tabela jest rejestrem wszystkich wypożyczeń w historii sklepu. Wypożyczana jest gra z magazynu (tylko z puli tych, które są na to przeznaczone) i wydawana klientowi na okres 72 godzin za stałą ustaloną kwotę, obliczaną dla każdej gry. Dodatkowo, każdy dzień przekroczenia terminu skutkuje kumulowanym naliczeniem kary w wysokości 30% ceny jednorazowego wypożyczenia gry. Zakładamy, że opłata za wypożyczenie naliczana jest od razu, a kara przy zwrocie produktu. Jeśli klient jest terminowy, płatność kary pozostawiona jest z pustym identyfikatorem. Przypadek klienta, który nigdy nie oddaje gry nie wpływa na mechanikę bazy. Jego płatność kary może być tylko inna, niż przewidują podstawowe zasady, ale o tym zdecyduje sąd.
Atrybut | Opis |
---|---|
rental_id |
numer identyfikacyjny wypożyczenia (PK) |
inventory_id |
numer identyfikacyjny pozycji w magazynie (FK) |
customer_id |
numer identyifkacyjny klienta (FK) |
rental_date |
data i godzina wypożyczenia |
return_date |
data i godzina zwrotu (pusta, jeśli wypożyczenie wciąż trwa) |
staff_id |
numer identyfikacyjny pracownika wydającego grę (FK) |
payment_id |
numer identyfikacyjny płatności za usługę (FK) |
penalty_payment_id |
numer identyfikacyjny ewentualnej płatności kary (FK) |
rate |
ocena gry przez klienta (od 1 do 10; opcjonalna) |
updated_at |
moment ostatniej zmiany w krotce |
Zależności funkcyjne to:
- {
rental_id
} → {inventory_id
,customer_id
,rental_date
,return_date
,staff_id
,payment_id
,penalty_payment_id
,rate
,updated_at
} - {
inventory_id
,rental_date
} → {rental_id
,customer_id
,return_date
,staff_id
,payment_id
,penalty_payment_id
,rate
,updated_at
} - {
payment_id
} → {rental_id
,inventory_id
,customer_id
,rental_date
,return_date
,staff_id
,penalty_payment_id
,rate
,updated_at
}
Konkretny produkt w jednym momencie wskazuje na wszystkie pola rekordu, bo jest unikalny. Para klienta i daty wypożyczenia, bez wskazania produktu, nie identyfikuje usługi. Klient może chcieć za jednym razem przecież kilka gier. Podobnie klient i produkt, gdyż każdy może wypożyczać produkt wiele razy. Płatność zaś identyfikuje konkretną pozycję. Przy okazji tabeli payments
omówimy, iż faktycznie klient może w teorii robić większe zakupy na jeden rachunek. W takim przypadku poszczególne "płatności" są grupowane w cały "koszyk" już w tamtej tabeli. Reszta faktów jest dość oczywista, m.in. opcjonalny identyfikator płatności kary nie może niczego wskazywać.
Wszystkie posiadane kiedykolwiek przez sklep gry, bo Geeks & Dragons ma na stanie wyłącznie gry. Te, które są cały czas na magazynie (lub są wypożyczone i jeszcze nie oddane) mają status aktywnych (active = TRUE
). Jeżeli są już zniszczone, zaginą itd., ich status jest negatywny. Pozostają wtedy zatem jedynie historycznym zapisem. Każda gra jest kiedyś zakupywana przez sklep jeżeli jest w obrocie, ma ustalaną cenę. Cena będzie oczywiście mniejsza dla wynajmu. Każdy produkt ma też osobne przeznaczenie - albo jest do sprzedaży (S
), albo na wypożyczenie (R
), albo do użytku turniejowego (T
). Nigdy te przeznaczenia nie są mieszane w jednym momencie, gdyż nie można wypożyczać produktu, który ma być używany w turnieju, a z drugiej strony, używane gry nie będą sprzedawane. Mamy więc ekskluzywność kategorii.
Atrybut | Opis |
---|---|
inventory_id |
numer identyfikacyjny produktu (PK) |
game_id |
numer identyfikacyjny gry, jaką stanowi produkt (FK) |
destination |
przeznaczenie (S - 'sales', R - 'rental', T - 'tournaments') |
price_id |
numer identyfikacyjny ceny (FK) |
active |
status posiadania gry na stanie |
purchase_payment_id |
numer identyfikacyjny płatności związanej z zakupem (FK) |
delivery_date |
data i godzina wprowadzenia nowego produktu do magazynu |
updated_at |
moment ostatniej zmiany w krotce |
Zależności funkcyjne to:
- {
inventory_id
} → {game_id
,destination
,price_id
,active
,purchase_payment_id
,delivery_date
,updated_at
}
Celowo nie wspominamy tu o zależności ceny od pary gry i jej przeznaczenia. Chcemy dopuścić możliwość, że nawet pośród tych samych gier i przeznaczenia (np. do sprzedaży), można nadawać w celach marketingowych przeceny tylko kilku sztukom (powiedzmy tym, które wystawione są na półkach podczas, gdy takie same produkty leżą z inną ceną w magazynie). Naturalnie, jeżeli produkt jest przeznaczony na turnieje, nie musi dostawać swojej ceny, ale nie są to jedyne przypadki pustego pola z price_id
. Jeżeli pracownik przyjmie dostawę, a nie zdąży wprowadzić ceny, pole pozostaje z wartością NULL
. Nie jest to groźne, gdyż w każdym momencie można cenę nadać według bieżącej polityki sklepu. Z drugiej strony wartość T
przeznaczenia nie zawsze wiąże się z brakiem ceny, gdyż produkt mógł z kategorii wypożyczanego być tymczasowo przeniesiony do kategorii turniejowego, bez likwidacji przypisanej ceny.
W niej przechowujemy informacje o wszystkich pracownikach, którzy kiedykolwiek pracowali w firmie. Część atrybutów jest analogiczna do występujących w customers
. Nie będziemy się nad tymi ponownie szczegółowo pochylać.
Atrybut | Opis |
---|---|
staff_id |
numer identyfikacyjny pracownika (PK) |
first_name |
imię |
last_name |
nazwisko |
phone |
numer telefonu kontaktowego |
email |
adres e-mail do kontaktu |
city_id |
numer identyfikacyjny miasta zamieszkania (FK) |
current_salary |
aktualna bazowa pensja miesięczna |
is_manager |
informacja o tym, czy pracownik jest managerem |
gender |
płeć pracownika (pole może mieć wartość M , F lub być puste) |
from_date |
data rozpoczęcia pracy w sklepie |
to_date |
data zwolnienia (jeśli osoba nadal pracuje, pole pozostaje puste) |
updated_at |
moment ostatniej zmiany w krotce |
Zależności funkcyjne to:
- {
staff_id
} → {first_name
,last_name
,phone
,matches
,email
,city_id
,current_salary
,is_manager
,gender
,from_date
,to_date
,updated_at
}
Znów teoretyczna (choć skrajnie mało prawdopodobna) możliwość istnienia kilku pracowników o tym samym nazwisku, pochodzeniu, zatrudnionych w tym samym czasie itd. decyduje o tym, że nie znajdą się inne zależności, które możemy wypisać.
Ciekawą (i dość osobliwą) praktyką firmy jest wtykanie nosa w życie miłosne pracowników. Mają oni raportować wszystkich swoich partnerów z okresu pracy w firmie wraz z liczbą randek (według uznania pracownika - stopień zbliżenia jest bowiem subiektywny). Co do danych personalnych partnerów, wystarczy podać ich imię i płeć (ale nie trzeba). W końcu RODO i tak dalej... Cała sytuacja ma służyć wyłącznie analizie produktywności i nie ma związku z dewiacjami właściciela. Przynajmniej taka jest oficjalna wersja.
Atrybut | Opis |
---|---|
relationship_id |
numer identyfikacyjny relacji (PK) |
staff_id |
numer identyfikacyjny pracownika (FK) |
partner_id |
numer identyfikacyjny partnera (FK) |
dates_number |
liczba randek w obrębie relacji |
updated_at |
moment ostatniej zmiany w krotce |
Zależności funkcyjne to:
- {
relationship_id
} → {staff_id
,partner_id
,dates_number
,updated_at
}
W związku z możliwymi odejściami i powrotami, dopuszczamy możliwość kilku relacji między danym pracownikiem a partnerem, z osobnym licznikiem spotkań. Przecież czasem trzeba sobie dać szanse na start od nowa, z czystą kartą... Ponadto, znając identyfikator partnera, nie możemy jednoznacznie ocenić, jakiego pracownika dotyczył związek miłosny. Teoretyczne romanse w pracy mogą skutkować odbijaniem sobie nawzajem partnerów.
Ta tabela jest "rozszerzeniem" tabeli relationships
, zawierającym już konkretne dane na temat partnerów.
Atrybut | Opis |
---|---|
partner_id |
numer identyfikacyjny partnera (PK) |
name |
imię bądź pseudonim danego partnera |
gender |
płeć partnera (pole może mieć wartość M , F lub być puste) |
updated_at |
moment ostatniej zmiany w krotce |
Zależności funkcyjne to:
- {
partner_id
} → {name
,gender
,updated_at
}
Widać wyraźnie, że nie da się budować innych zależności funkcyjnych z tak skąpego zestawu danych o partnerach. Samo imię też oczywiście nie wyznacza płci, gdyż Wrocław wcale nie jest mocno konserwatywnym miastem.
To rejestr wszystkich płatności związanych z działalnością sklepu. Jeżeli w bazie pojawia się jakikolwiek wydatek, swoim kluczem obcym odnosi się do rekordów tej tabeli. W zależności, czy pozycja w rejestrze dotyczy przychodu (np. ze sprzedaży), czy wydatku, kwoty mogą być dodatnie lub ujemne. Dodatkowo, uznajemy na ogół każdą wartość jako jedną konkretną pozycję, dotycząc jednego produktu, specyfiki itp. To, że kilka gier może być przykładowo kupione na jeden paragon notujemy numerem atrybutu invoice
. Jest identyfikator paragonu czy faktury związanej z daną grupą płatności lub jedną płatnością. Każda płatność ma przypisaną jakąś "fakturę".
Atrybut | Opis |
---|---|
payment_id |
numer identyfikacyjny opłaty (PK) |
amount |
wartość pojedynczej płatności (w walucie PLN) |
invoice_id |
numer paragonu/faktury |
updated_at |
moment ostatniej zmiany w krotce |
Zależności funkcyjne to:
- {
payment_id
} → {amount
,invoice_id
,updated_at
}
Wyraźnie widać, że kwota bądź numer "faktury" nie są w stanie wskazać konkretnej płatności nawet razem.
Jest to rejestr grup płatności - faktur i paragonów związanych z dowolnymi płatnościami w firmie. Nie rozpisujemy tu większych szczegółów oprócz daty rozliczenia dla takiej grupy płatności.
Warto zauważyć, że data jakiejś operacji finansowej nie jest tożsama z datą realizacji płatności! Po to właśnie mimo dat obecnych w innych tabelach, też tutaj jest osobna data. Miejmy na uwadze czas realizacji przelewów, blokady środków i inne podobne zdarzenia. Tak więc data płatności "faktury" będzie zazwyczaj taka sama jak daty operacji, ale tak nie musi być.
Atrybut | Opis |
---|---|
invoice_id |
numer identyfikacyjny grupy płatności (PK) |
date |
data i godzina rozliczenia |
updated_at |
moment ostatniej zmiany w krotce |
Zależności funkcyjne to:
- {
invoice_id
} → {date
,updated_at
}
Przedstawione zależności funkcyjne są raczej oczywiste.
Wszystkie historyczne wydatki związane z utrzymaniem sklepu, mediami, płacami dla pracowników (zanotowane przy pracownikach kwoty to tylko ich obecne stawki a nie cała historia) itp. Zauważmy, że nie obejmują one dostaw, gdyż te zanotowane są już przy produktach w magazynie.
Atrybut | Opis |
---|---|
spend_id |
numer identyfikacyjny wydatku (PK) |
title_id |
numer identyfikacyjny tytułu wydatku (FK) |
payment_id |
numer identyfikacyjny płatności (FK) |
date |
data i godzina związana z wydatkiem |
updated_at |
moment ostatniej zmiany w krotce |
Zależności funkcyjne to:
- {
spend_id
} → {title_id
,payment_id
,date
,updated_at
} - {
payment_id
} → {spend_id
,title_id
,date
,updated_at
}
Aby zapobiec istnieniu w tabeli tytułów implikujących konkretny typ wydatku, a mogących się powtarzać, mamy tu tylko referencje do tytułów. Niestety tytuł nie jest nadkluczem i dodatkowa struktura jest wymagana w żądanej postaci normalnej.
Konieczna byłaby mechanika w systemie sklepu, która dla każdego tytułu sprawdza czy ten już istniał. Brzmi to na niezbyt sensowną logikę, ale pewne wymagania należy formalnie spełnić.
Są to unikalne tytuły wydatków wraz z przypisanymi kategoriami.
Atrybut | Opis |
---|---|
title_id |
numer identyfikacyjny tytułu wydatku (PK) |
title |
opisowy tytuł wydatku |
expenses_type_id |
numer identyfikacyjny typu wydatków (FK) |
updated_at |
moment ostatniej zmiany w krotce |
Zależności funkcyjne to:
- {
title_id
} → {title
,expenses_type_id
,updated_at
} - {
title
} → {title_id
,expenses_type_id
,updated_at
}
Tytuły z założenia unikalne.
Zawarte są w niej typy (czyli kategorie) wydatków.
Atrybut | Opis |
---|---|
expenses_type_id |
numer identyfikacyjny typu wydatków (PK) |
expenses_type |
nazwa typu (kategorii) wydatków |
updated_at |
moment ostatniej zmiany w krotce |
Zależności funkcyjne to:
- {
expenses_type_id
} → {expenses_type
,updated_at
} - {
expenses_type
} → {expenses_type_id
,updated_at
}
Typy wydatków utrzymaniowych są też z założenia unikalne.
Jest to zestawienie wszystkich operacji zakupowych dokonanych przez klientów - zakupów oraz ewentualnych zwrotów produktów. Te drugie są oznaczane jako zwyczajne rekordy, ale z flagą return_oper
. Jeżeli dochodzi do zwrotu, rekord z produktem identyfikowany jest po płatności i nowo wprowadzany rekord - już oflagowany, z nowym kluczem głównym - dotyczy oddanych klientowi środków na tym danym produkcie. Ma też oczywiście nową datę, a proces może być przeprowadzany z innym pracownikiem.
Atrybut | Opis |
---|---|
sale_id |
numer identyfikacyjny zakupu/zwrotu (PK) |
inventory_id |
numer identyfikacyjny produktu (FK) |
staff_id |
numer identyfikacyjny pracownika obsługującego sprzedaż (FK) |
payment_id |
numer identyfikacyjny płatności (FK) |
date |
data i godzina związana z zakupem |
return_oper |
flaga oznaczająca, że dana operacja jest zwrotem produktu |
updated_at |
moment ostatniej zmiany w krotce |
Zależności funkcyjne to:
- {
sale_id
} → {inventory_id
,staff_id
,payment_id
,date
,return_oper
,updated_at
} - {
payment_id
} → {sale_id
,inventory_id
,staff_id
,date
,return_oper
,updated_at
}
Identyfikator płatności czy zakupu jednoznacznie wyznacza operację. W związku z tym, iż może dojść do zwrotów (legalnych w świetle prawa), produkt nie rozpoczyna żadnej zależności funkcyjnej. O dacie czy pracowniku chyba nie trzeba wspominać.
Jest to zestawienie gier zarejestrowanych przez sklep wraz ze szczegółami na temat ich rodzaju. Nie są to pozycje w magazynie, a zwyczajnie unikalne propozycje dostępne na rynku.
Atrybut | Opis |
---|---|
game_id |
numer identyfikacyjny gry (PK) |
title |
tytuł gry |
description |
opis gry |
category_id |
numer identyfikacyjny kategorii tematycznej (FK) |
type_id |
numer identyfikacyjny typu gry (FK) |
competitivity |
możliwość przeprowadzania zawodów z grą |
updated_at |
moment ostatniej zmiany w krotce |
Zależności funkcyjne to:
- {
game_id
} → {title
,description
,category_id
,type_id
,competitivity
,updated_at
} - {
title
} → {game_id
,description
,category_id
,type_id
,competitivity
,updated_at
}
Tytuł gry jest także unikalnym identyfikatorem. Uznajemy, że sklep nie posiada odmiennych gier o tym samym tytule. Jeżeli by się tak zdarzyło zbiegiem okoliczności, zakładamy zapis tych tytułów np. z jakimś deskryptywnym przyrostkiem. Kategorie, typy, czy możliwość grania turniejowego to niezależne informacje. Opis gry jest opcjonalny i też o niczym nie świadczy.
Są to kategorie tematyczne, opisujące różne gry.
Atrybut | Opis |
---|---|
category_id |
numer identyfikacyjny kategorii gry (PK) |
game_category |
nazwa kategorii tematycznej |
updated_at |
moment ostatniej zmiany w krotce |
Zależności funkcyjne to:
- {
category_id
} → {game_category
,updated_at
} - {
game_category
} → {category_id
,updated_at
}
Mamy tutaj prostą relację jednoznaczności numeru kategorii i jej nazwy. Nie przewidujemy różnych kategorii o tej samej nazwie, gdyż ten koncept nie ma sensu.
Są to typy gier nieelektronicznych, jakimi określane są poszczególne pozycje na liście gier.
Atrybut | Opis |
---|---|
type_id |
numer identyfikacyjny typu gry (PK) |
game_type |
nazwa typu gry |
updated_at |
moment ostatniej zmiany w krotce |
Zależności funkcyjne to:
- {
type_id
} → {game_type
,updated_at
} - {
game_type
} → {category_id
,updated_at
}
Znów, mówimy o prostej dwustronnej zależności typu i jego numeru. Wyjaśnienie takiej logiki jest analogiczne do przypadku kategorii.
Relacje tu zebrane to ceny za konkretne produkty w sklepie. Mogą być różne nawet dla jednego typu produktu, w zależności od jego stanu czy innych szczegółów polityki sklepu. Jeżeli produkty są przeznaczone do wypożyczenia, cena dotyczy wypożyczenia. Mechanizmy sklepu musiałyby przewidywać po prostu jednoczesną zmianę identyfikatora ceny przy zmianie przeznaczenia produktu.
Sens tej tabeli to możliwość grupowania cen dla podkategorii tego samego produktu w dowolny sposób.
Atrybut | Opis |
---|---|
price_id |
numer identyfikacyjny typu ceny (PK) |
current_price |
aktualna cena związana z danym zbiorem produktów |
updated_at |
moment ostatniej zmiany w krotce |
Zależności funkcyjne to:
- {
price_id
} → {current_price
,updated_at
}
Nie pozostawiają one wiele do tłmaczenia. Zdaje się, że takie wynikanie jest kolejny raz jasne.
Do tego momentu właściwie już wykazaliśmy że baza jest w standardzie EKNF. Formalnie wyjaśnimy to jeszcze niżej.
Wszystkie wartości w kolumnach są skalarne, a wiersze unikalne, dzięki dbającym o to, dodanym osobno (unikalnym) kluczom głównym (1NF).
Widzimy też, że nie ma częściowych zależności funkcyjnych atrybutów niegłównych od kluczy kandydujących (wystarczy to do 2NF). Są nieraz takie zależności od nadkuczy w ogóle, ale nigdy od kluczy kandydujących.
Wreszcie, wszystkie przedstawione przypadki zależności funkcyjnych (zarówno te wypisane, jak i przez nie implikowane) rozpoczynają się od nadklucza (daje nam to 3NF i EKNF).
Wiemy, że omawiane atrybuty rozpoczynające zależności są nadkluczami, ponieważ - jak widać - identyfikują one pełne krotki. Wypisane punkty obejmują szczególne przypadki, bo tylko klucze kandydujące, ale konstruując wg. zasad wnioskowania pozostałe zależności, mamy też takie, które rozpoczynają się od niekandydujących nadkluczy.
Nadmieńmy także, że zawarte w każdej tabeli kolumny updated_at
mogą być zasadniczo dowolną datą - nawet związaną z poprawką wcześniejszego błędu. Nie informują o niczym oprócz poglądowego terminu zmian, dlatego nie rozpoczynają zależności funkcyjnych.
Footnotes
-
Razem z wieloma innymi - założenie podjęte w ramach szczegółowej decyzji, o rodzaju działalności przedsiębiorstwa, będącego obiektem rozważań. ↩