Czy używanie wielu kluczy obcych oddzielonych przecinkami jest nieprawidłowe, a jeśli tak, to dlaczego?

31

Istnieją dwie tabele: Deali DealCategories. Jedna umowa może mieć wiele kategorii transakcji.

Zatem właściwym sposobem powinno być utworzenie tabeli DealCategorieso następującej strukturze:

DealCategoryId (PK)
DealId (FK)
DealCategoryId (FK)

Jednak nasz zespół outsourcingowy zapisał w Dealtabeli wiele kategorii w ten sposób:

DealId (PK)
DealCategory -- In here they store multiple deal ids separated by commas like this: 18,25,32.

Uważam, że to, co zrobili, jest złe, ale nie wiem, jak jasno wyjaśnić, dlaczego tak nie jest.

Jak mam im wyjaśnić, że to źle? A może to ja się mylę, a to jest do przyjęcia?

Sarawut Positwinyu
źródło
20
Masz rację. Czy przechowywanie listy oddzielonej przecinkami w kolumnie bazy danych jest tak złe? . Krótka odpowiedź: Tak, to takie złe.
ypercubeᵀᴹ
7
zwolnij zespół outsourcingu od razu, zanim wyrządzą więcej szkody ... (-_-)
Rafa

Odpowiedzi:

49

Tak, to okropny pomysł.

Zamiast iść:

SELECT Deal.Name, DealCategory.Name
FROM Deal
  INNER JOIN
     DealCategories ON Deal.DealID = DealCategories.DealID
  INNER JOIN
     DealCategory ON DealCategories.DealCategoryID = DealCategory.DealCategoryID
WHERE Deal.DealID = 1234

Teraz musisz iść:

SELECT Deal.ID, Deal.Name, DealCategories
FROM Deal
WHERE Deal.DealID = 1234

Następnie musisz wykonać czynności w kodzie aplikacji, aby podzielić listę przecinków na poszczególne liczby, a następnie osobno przeszukać bazę danych:

SELECT DealCategory.Name
FROM DealCategory
WHERE DealCategory.DealCategoryID IN (<<that list from before>>)

Ten projekt antipattern wynika albo z całkowitego niezrozumienia modelowania relacyjnego (nie musisz bać się tabel. Tabele to twoi znajomi. Używaj ich), albo dziwnie błędnego przekonania, że ​​szybciej jest wziąć listę oddzieloną przecinkami i podzielić ją w kodzie aplikacji niż po to, aby dodać tabelę linków ( nigdy nie jest). Trzecią opcją jest to, że nie są wystarczająco pewni / kompetentni w SQL, aby móc konfigurować klucze obce, ale w takim przypadku nie powinni mieć nic wspólnego z projektowaniem modelu relacyjnego.

SQL Antipatterns (Karwin, 2010) poświęca cały rozdział temu antypatternowi (który nazywa „Jaywalking”), strony 15-23. Ponadto autor opublikował podobne pytanie w SO . Kluczowe punkty, które zauważa (w odniesieniu do tego przykładu) to:

  • Zapytanie o wszystkie oferty w określonej kategorii jest dość skomplikowane (najprostszym sposobem rozwiązania tego problemu jest wyrażenie regularne, ale wyrażenie regularne samo w sobie jest problemem).
  • Nie można egzekwować integralności referencyjnej bez relacji klucza obcego. Jeśli usuniesz DealCategory nr. 26, następnie w kodzie aplikacji musisz przejrzeć każdą ofertę w poszukiwaniu odniesień do kategorii 26 i usunąć je. Jest to coś, co powinno być obsługiwane w warstwie danych, a konieczność obsługi tego w aplikacji jest bardzo zła .
  • Zbiorczy zapytań ( COUNT, SUMetc), ponownie, różnią się od „skomplikowane” do „prawie niemożliwe”. Zapytaj programistów, jak uzyskają listę wszystkich kategorii wraz z liczbą ofert w tej kategorii. Przy odpowiednim projekcie są to cztery linie SQL.
  • Aktualizacje stają się znacznie trudniejsze (tzn. Masz umowę w pięciu kategoriach, ale chcesz usunąć dwie i dodać trzy inne). To trzy linie SQL z odpowiednim projektem.
  • W końcu napotkasz VARCHARograniczenia długości listy. Chociaż jeśli masz listę oddzieloną przecinkami, która zawiera ponad 4000 znaków, istnieje szansa, że ​​potwór i tak będzie powolny jak diabli.
  • Wyciąganie listy z bazy danych, dzielenie jej, a następnie powrót do bazy danych w celu wykonania innego zapytania jest z natury wolniejsze niż jedno zapytanie.

TLDR: Jest to zasadniczo wadliwy projekt, nie skaluje się dobrze, wprowadza dodatkową złożoność nawet najprostszych zapytań, a zaraz po wyjęciu z pudełka spowalnia działanie aplikacji.

Simon Righarts
źródło
1
Simon, ktoś zadał to samo pytanie ( dba.stackexchange.com/questions/17824/... ), ale nie wiem, dlaczego te same FK i PK są w tej samej tabeli, co hamuje 3FN.
jcho360
2
Nie byłem do końca pewien, czy chcą mieć relację wiele do wielu między ofertami i kategoriami, czy jakąś dziedziczną kategorię. Tak czy inaczej, była to linia boczna do głównego punktu, że bycie rozdzielanymi przecinkami polami zamiast tabeli łączy to zły pomysł.
Simon Righarts
4

Jednak nasz zespół outsourcingowy zapisał wiele kategorii w tabeli transakcji w następujący sposób:

DealId (PK) DealCategory - tutaj przechowują wiele identyfikatorów transakcji oddzielonych przecinkami: 18,25,32.

To naprawdę dobry projekt, jeśli potrzebujesz tylko zapytać o kategorie dla danej oferty.

Ale to okropne, jeśli chcesz poznać wszystkie oferty w danej kategorii.

Utrudnia to także wykonywanie innych czynności, takich jak aktualizacje, zliczanie, dołączanie itp.

Denormalizacja ma swoje miejsce, ale należy pamiętać, że optymalizuje ona jeden typ zapytania, kosztem wszystkich innych, które możesz wykonać w stosunku do tych samych danych. Jeśli wiesz, że zawsze będziesz sprawdzać według jednego wzorca, może to dać przewagę w użyciu zdormalizowanego projektu. Ale jeśli istnieje jakakolwiek szansa, że ​​możesz potrzebować większej elastyczności w typach zapytań, trzymaj się znormalizowanego projektu.

Jak każda inna forma optymalizacji, musisz wiedzieć, jakie zapytania będziesz uruchamiać, zanim zdecydujesz, czy denormalizacja jest uzasadniona.

Bill Karwin
źródło
1
Czy naprawdę uważasz, że ciąg znaków z identyfikatorami podrzędnymi oddzielonymi przecinkami jest pomocny? Aplikacja musiała najpierw przeczytać, a następnie przeanalizować identyfikatory i wysłać zapytanie do wszystkich dzieci select * from DealCategories where DealId in (1,2,3,4,...). Masz więcej doświadczenia, jeśli chodzi o projektowanie baz danych, niż ja, więc może masz uzasadniony powód, by tak „ekstremalnie dostroić” w bardzo szczególnych przypadkach. Moim jedynym pomysłem, aby to uzasadnić, jest bardzo duże selectobciążenie Deal / DealCategory. To mi przypomina zespół outsourcingu bez wiedzy na temat projektowania baz danych, poza tworzeniem tabel, stworzeniem.
Erik Hart
1
@ErikHart, to jest denormalizacja i może być pomocna, ale chodzi mi o to, że zależy ona całkowicie od zapytań, które musisz uruchomić. Masz rację, że denormalizacja powoduje, że wszystkie zapytania działają gorzej, z wyjątkiem jednego, dla którego optymalizuje. Jeśli potrzebujesz uruchomić tylko jedno zapytanie, a nie przejmujesz się pozostałymi, to wygrywasz. Są to jednak rzadkie przypadki, ponieważ zazwyczaj chcemy elastyczności w wyszukiwaniu danych na różne sposoby.
Bill Karwin
1
@ErikHart, gdyby ten zespół outsourcingowy otrzymał specyfikacje projektu, które obejmowały tylko jedno zapytanie względem tych danych, mogliby zaprojektować optymalizację tylko dla tego konkretnego zapytania. Innymi słowy: „prosiłeś o to, rozumiesz”. Ale dostawca outsourcingu nie ma powodu, aby planować przyszłe wykorzystanie danych - wdrażają aplikację zgodnie z literą specyfikacji.
Bill Karwin
1

Wiele wartości w kolumnie jest przeciwnych pierwszej formie normalnej.

Nie ma też absolutnie żadnego wzrostu prędkości, ponieważ tabele mają być połączone w bazie danych. Najpierw musisz przeczytać i przeanalizować ciąg, a następnie wybrać wszystkie kategorie dla „Oferty”.

Poprawną implementacją byłaby tabela połączeń, taka jak „DealDealCategories”, z DealId i DealCategoryId.

Zła implementacja hierarchii?

Ponadto FK w DealCategory do innej DealCategory wygląda jak zła implementacja hierarchii / drzewa DealCategory. Praca z drzewami za pośrednictwem relacji nadrzędnego identyfikatora (tzw. Listy sąsiedztwa) jest uciążliwa!

Sprawdź zestawy zagnieżdżone (dobre do odczytania, ale trudne do modyfikacji) i tabele zamknięcia (najlepsza ogólna wydajność, ale możliwe wysokie zużycie pamięci - prawdopodobnie nie za dużo dla Twoich DealCategory) podczas wdrażania hierarchii!

Erik Hart
źródło