W jakim celu tworzy się relacje one-to-one?

0

jw. Poproszę o jakieś praktyczne przykłady gdzie ma to sens.

5

Aby "wizualnie" odseparować dane - np. mając sklep, na jednego użytkownika może przypadać jeden koszyk, ale mimo to logicznej może być stworzyć osobne tabele users oraz carts, aby wygodniej było po tych danych się poruszać.

W zależności od baz danych, złączenie one-to-one (nie relacja, bo relacja jest synonimem słowa tabela) można też wybrać dla celów optymalizacyjnych (np. aby jeden wiersz nie składał się z dużej liczby kolumn, co może wpływać na wydajność baz opartych o MVCC; w tym na wydajność migracji) albo pragmatycznych (osobne tabele pozwalają na osobne row-level / table-level blokady, zmniejszoną liczbę kolizji transakcji itd.).

One-to-one pozwala także na modelowanie sytuacji w stylu "użytkownik musi istnieć, ale jego koszyk już niekoniecznie" (co w przypadku wspólnej tabeli kinda-sorta można ogarnąć nullami + constraintami, ale jest to bardziej niezręczne).

2

Powodów może być wiele a moim zdaniem najważniejsze z nich to:

  1. Dla poprawy czytelności bazy danych (o czym napisł @Patryk27);
  2. Dla odseparowania danych np. z powodu konieczności dostosowania do poziomu uprawnień lub innych powodów organizacyjnych.
  3. Z powodów optymalizacyjnych - czasem szybciej szukać po kilku małych tabelach niż jednej wielkiej w szczególności gdy trzymamy w nich długie pola tekstowe lub binarne. W takiej sytuacji może opłacać się duże pola wywalić do osobnej tabeli po to by pozostałą część skutecznie wrzucić w 100% do RAM.
  4. W starych systemach, w których nie wiemy czy ktoś używa select * from żeby nie naruszać logiki istniejącej struktury a jednak móc rozszerzyć funkcjonalność.

Jak by się dłużej zastanowić to pewnie znalazłoby się więcej...

Przykład rozdzielenia danych bazy kontaktów na osobne tabele dla "firm oraz instytucji" oraz "ludzi":

Tabela `contact`:
* idContact int
- type enum('person','company','organisation')
- createdAt dateTime
- status enum('archive','active','temp')
- phoneNumbner
- email
etc...

Tabela `contactPerson` (w relacji 1:1 do `contact`):
* idContactPerson int
* idContact int -> contact.idContact)
- name string
- surname string
- birthDate date
- weight int
- PESEL string 
- eyesColor string
- penisLength int
etc..

Tabela `contactCompany` (w relacji 1:1 do `contact`)
* idContactCompany int
* idContact int -> contact.idContact)
- name string
- industrySector enum ('coal','food','it','other')
- employesCount int
- type ('small-business','stock-company','private-company')
etc...
2

Nie wiem jak to się ma do współczesnych silników, ale kiedyś bazy danych lepiej/szybciej sobie radziły z rekordami o stałej długości, mniej syfu i dziur po usuniętych rekordach, można łatwo wyliczyć offset z prostego wzoru itp więc łatwiej zoptymalizować. Przy zmiennym rozmiarze rekordów nawet update pola może spowodować fragmentację.
Dlatego mając np string o zmiennej długości miało sens wrzucić tylko jego ID i joinować do niego na ostatnim etapie zapytania.

Ja używałem kilka razy głównie ze względu na ograniczenia - na przykład mieliśmy bazę danych aplikacji 3rd party, musieliśmy rozszerzyć jedną tabelę, ale nie mogliśmy jej tykać bo aplikacja była osobno instalowana i mogła usunąć nasze dane przy aktualizacji, migracji, backupie itp.
Innym razem musieliśmy dodać i uzupełnić kilka pół do istniejącej tabeli z miliardami rekordów, testy na mniejszej testowej bazie wykazały że trwałoby to około 12 godzin przy czym do zmiany schematu tabeli i utworzenia indeksów potrzebny był pełny table lock i wyłączenie aplikacji co nie wchodziło w grę na tak długi czas. Stworzyliśmy więc tabelę obok z relacją 1:1, uzupełniliśmy dane w nowej tabeli w tle i zrobiliśmy release korzystający z nowych pól, a wszystko bez praktycznie żadnego downtime'u.

0

@obscurity: To nie kwestia silnika, tylko dysku. Domyślnie dane są zapisywane w układzie wierszowym, czyli coś jak np. CSV. Jeżeli tabela ma wiele pól, to ma też większy rozmiar, a zapytania typu select jakiespole from tabela muszą przelecieć przez cały "plik". Im większa tabela, tym więcej danych musi zostać przemielonych. Teoretycznie na SSD może być już mniejsza różnica, ale faktycznie silnik musiałby to obługiwać.

0
piotrpo napisał(a):

@obscurity: To nie kwestia silnika, tylko dysku. Domyślnie dane są zapisywane w układzie wierszowym, czyli coś jak np. CSV. Jeżeli tabela ma wiele pól, to ma też większy rozmiar, a zapytania typu select jakiespole from tabela muszą przelecieć przez cały "plik". Im większa tabela, tym więcej danych musi zostać przemielonych. Teoretycznie na SSD może być już mniejsza różnica, ale faktycznie silnik musiałby to obługiwać.

Nic nie szkodzi, żeby dane były trzymać na odwrót tj. kolumnami a nie wierszami https://en.wikipedia.org/wiki/Column-oriented_DBMS . Zgaduję, że przy typowych zastosowaniach jest to gorsza strategia jednak to na pewno nie problem dysku tylko pewnego rodzaju kompromis. Praktycznie każda możliwa pamięć (HDD, SSD, RAM, CPU cache) zyskuje, gdy potrzebne dane są blisko siebie a operacje na takich danych (np. suma całej kolumny) to nie rzadkość

1

@slsy: Tak, niektóre bazy danych pozwalają na układ kolumnowy. Szczególnie szybkie są wszelkiego typu agregaty, bo np. sum(kolumna) musi sekwencyjnie odczytać dokładnie tyle ile minimalnie potrzeba do wyliczenia tej wartośći. Gorzej, jak masz select * from... where ....

1 użytkowników online, w tym zalogowanych: 0, gości: 1