Czy przechowywanie listy rozdzielanej w kolumnie bazy danych jest tak złe?


363

Wyobraź sobie formularz internetowy z zestawem pól wyboru (można wybrać dowolne lub wszystkie z nich). Zdecydowałem się zapisać je na oddzielonej przecinkami liście wartości przechowywanych w jednej kolumnie tabeli bazy danych.

Teraz wiem, że poprawnym rozwiązaniem byłoby utworzenie drugiej tabeli i poprawna normalizacja bazy danych. Łatwiej było wdrożyć łatwe rozwiązanie, a ja chciałem szybko przetestować tę aplikację bez konieczności poświęcania jej zbyt wiele czasu.

Myślałem, że zaoszczędzony czas i prostszy kod były tego warte w mojej sytuacji, czy to uzasadniony wybór projektu, czy też powinienem był go znormalizować od samego początku?

Więcej kontekstu, jest to mała wewnętrzna aplikacja, która zasadniczo zastępuje plik Excela, który był przechowywany w folderze współdzielonym. Pytam również, ponieważ myślę o oczyszczeniu programu i uczynieniu go łatwiejszym w utrzymaniu. Jest kilka rzeczy, z których nie jestem do końca zadowolony, jedna z nich jest tematem tego pytania.


21
w takim przypadku, po co zawracać sobie głowę bazą danych ?, wystarczy zapisanie w pliku.
thavan

6
Uzgodniony z @thavan. Po co zapisywać dane dla potwierdzenia koncepcji? Po zakończeniu sprawdzania poprawnie dodaj bazę danych. Twoja lekkość jest lekka jako dowód koncepcji, po prostu nie rób rzeczy, które musisz zrobić później.
Jeff Davis

1
W Postgresie kolumna tablicowa powinna być preferowana nad listą rozdzielaną przecinkami. Zapewnia to przynajmniej odpowiedni typ danych, nie ma problemów z odróżnieniem separatora od rzeczywistych danych i może być skutecznie indeksowane.
a_horse_w_no_name

Odpowiedzi:


567

Oprócz naruszenia pierwszej normalnej formy z powodu powtarzającej się grupy wartości przechowywanych w jednej kolumnie, listy oddzielone przecinkami mają wiele innych praktycznych problemów:

  • Nie mogę się upewnić, że każda wartość ma odpowiedni typ danych: nie ma sposobu, aby zapobiec 1,2,3, banan, 5
  • Nie można używać ograniczeń klucza obcego do łączenia wartości z tabelą odnośników; nie ma sposobu na wymuszenie integralności referencyjnej.
  • Nie można wymusić wyjątkowości: nie ma sposobu, aby zapobiec 1,2,3,3,3,5
  • Nie można usunąć wartości z listy bez pobrania całej listy.
  • Nie można przechowywać listy dłużej niż mieści się w kolumnie ciągu.
  • Trudno wyszukać wszystkie podmioty o podanej wartości na liście; musisz użyć nieefektywnego skanowania tabeli. Może być konieczne użycie wyrażeń regularnych, na przykład w MySQL:
    idlist REGEXP '[[:<:]]2[[:>:]]'*
  • Trudno policzyć elementy na liście lub wykonać inne zapytania agregujące.
  • Trudno połączyć wartości z tabelą odnośników, do której się odnoszą.
  • Trudno pobrać listę w posortowanej kolejności.

Aby rozwiązać te problemy, musisz napisać mnóstwo kodu aplikacji, wymyślając na nowo funkcjonalność, którą RDBMS zapewnia już znacznie wydajniej .

Listy rozdzielone przecinkami są na tyle niepoprawne, że uczyniłem to pierwszym rozdziałem mojej książki: SQL Antipatterns: Unikanie pułapek programowania baz danych .

Są chwile, kiedy trzeba zastosować denormalizację, ale jak wspomina @OMG Ponies , są to wyjątki. Wszelkie nierelacyjne „optymalizacje” przynoszą korzyści dla jednego rodzaju zapytań kosztem innych zastosowań danych, więc upewnij się, że wiesz, które z twoich zapytań należy traktować tak, aby zasługiwały na denormalizację.


* MySQL 8.0 nie obsługuje już tej składni wyrażenia granicznego słowa.


8
ARRAY (dowolnego typu danych) może naprawić wyjątek, wystarczy sprawdzić PostgreSQL: postgresql.org/docs/current/static/arrays.html (@Bill: Świetna książka, którą należy przeczytać dla każdego programisty lub dba)
Frank Heikens

4
+1 rachunek Karwin Świetna odpowiedź! Piękne zwięzłe punkty. To też wygląda na świetną książkę. Uwielbiam też okładkę +1 NullUserException. Jestem w trakcie projektowania schematu bazy danych MySQL, który ma zastąpić system oparty na tekstach plików płaskich. Do tej pory spotkałem kilka dylematów. Warto więc kupić tę książkę.
therobyouknow

2
Witryna pragprog.com również wygląda dobrze: ładny styl, układ, przyjazny dla użytkownika czysty. To musi być całkiem nowy, w przeszłości nie byłem w stanie kupić ich e-booków. PS. Nie pracuję dla nich, nie mam żadnego związku z autorami. Lubię świętować dobre produkty, usługi i pomoc, kiedy to widzę.
therobyouknow

2
Mówiąc poważnie, dodałbym do twojej listy: Trudne do przeszukania. Powiedz, że chcesz mieć wszystkie rekordy zawierające „2”. Oczywiście nie można po prostu wyszukać foobar = „2”, ponieważ pominęłoby to, gdyby istniały inne wartości. Nie możesz wyszukiwać foobar takich jak „% 2%”, ponieważ otrzymalibyśmy fałszywe trafienia dla 12 i 28 itd. Nie można wyszukiwać foobar, takich jak „%, 2,%”, ponieważ 2 może być pierwszym lub ostatnim elementem listy, a zatem może zawierać tylko jeden z tych przecinków.
Jay

2
Wiem, że nie jest to zalecane, ale granie w diabły jest rzecznikiem: większość z nich można usunąć, jeśli istnieje interfejs użytkownika, który obsługuje unikalność i typy danych (w przeciwnym razie spowodowałby błąd lub niewłaściwe zachowanie), interfejs użytkownika spada i tworzy go, istnieje tabela sterowników, w której wartości pochodzą z, aby je uczynić unikalnymi, można użyć pola typu „% P%”, wartości P, R, S, T, liczenie nie ma znaczenia, a sortowanie nie ma znaczenia. W zależności od interfejsu użytkownika wartości można podzielić [], np. Aby zaznaczyć pola wyboru na liście z tabeli sterowników w co najmniej typowym scenariuszu bez konieczności przechodzenia do innej tabeli w celu ich uzyskania.
jmcclure

44

„Jednym z powodów było lenistwo”.

To dzwoni dzwonkami alarmowymi. Jedynym powodem, dla którego powinieneś zrobić coś takiego, jest to, że wiesz, jak to zrobić „we właściwy sposób”, ale doszedłeś do wniosku, że istnieje namacalny powód, aby tego nie robić.

Powiedziawszy to: jeśli dane, które chcesz przechowywać w ten sposób, są danymi, o które nigdy nie będziesz musiał pytać, może być uzasadnione przechowywanie ich w sposób, który wybrałeś.

(Niektórzy użytkownicy zakwestionowaliby stwierdzenie z mojego poprzedniego akapitu, mówiąc, że „nigdy nie wiesz, jakie wymagania zostaną dodane w przyszłości”. Użytkownicy ci są wprowadzani w błąd lub wyrażają przekonanie religijne. Czasami korzystne jest zastosowanie wymagań mieć przed sobą.)


Zawsze słyszę, jak niektórzy mówią, że „mój projekt jest bardziej elastyczny niż twój”, kiedy konfrontuję je na temat takich rzeczy, jak brak ustawiania ograniczeń klucza obcego lub przechowywanie list w jednym polu. Dla mnie elastyczność (w takich przypadkach) == brak dyscypliny == lenistwo.
foresightyj

41

Istnieje wiele pytań dotyczących SO:

  • jak uzyskać liczbę określonych wartości z listy rozdzielanej przecinkami
  • jak uzyskać rekordy, które mają tylko tę samą określoną wartość 2/3 / etc z listy oddzielonej przecinkami

Innym problemem związanym z listą rozdzielaną przecinkami jest zapewnienie spójności wartości - przechowywanie tekstu oznacza możliwość literówek ...

Są to wszystkie objawy danych zdenormalizowanych i podkreślają, dlaczego zawsze należy modelować dane znormalizowane. Denormalizacja może być optymalizacją zapytania, którą można zastosować, gdy zajdzie taka potrzeba .


19

Zasadniczo wszystko można obronić, jeśli spełnia wymagania twojego projektu. To nie znaczy, że ludzie się zgodzą lub będą chcieli bronić twojej decyzji ...

Ogólnie rzecz biorąc, przechowywanie danych w ten sposób jest nieoptymalne (np. Trudniejsze do wykonania wydajne zapytania) i może powodować problemy z konserwacją, jeśli zmodyfikujesz elementy w formularzu. Być może mógłbyś znaleźć środek i użyć liczby całkowitej reprezentującej zestaw flag bitowych?


10

Tak, powiedziałbym, że to naprawdę takie złe. Jest to wybór, który można obronić, ale to nie czyni go poprawnym ani dobrym.

Łamie pierwszą normalną formę.

Druga krytyka polega na tym, że umieszczenie surowych danych wejściowych bezpośrednio w bazie danych, bez jakiejkolwiek weryfikacji lub wiązania, pozostawia cię otwartym na ataki typu SQL injection.

To, co nazywacie lenistwem i brakiem znajomości SQL, to rzeczy, z których zbudowani są neofici. Polecam poświęcić czas, aby zrobić to poprawnie i postrzegać to jako okazję do nauki.

Lub pozostaw to bez zmian i naucz się bolesnej lekcji ataku typu SQL injection.


19
W tym pytaniu nie widzę nic, co sugerowałoby, że jest podatny na wstrzykiwanie SQL. Wstrzykiwanie SQL i normalizacja bazy danych to tematy ortogonalne, a twoja dygresja przy wstrzykiwaniu nie ma znaczenia dla pytania.
Hammerite,

5
@Paul: I może to samo podejście spowoduje, że zostanie potrącony przez autobus, gdy nie spojrzy w obie strony przed przejściem przez ulicę, ale nie ostrzegłeś go przed tym. Edycja: Myślałem, że jesteś plakatem tej odpowiedzi, mój błąd.
Hammerite

1
@Hammerite - Twoja ekstrapolacja na autobusy jest absurdalna.
duffymo,

4
Tak, to miało być śmieszne. Jego niedorzeczność ilustruje moją rację, a mianowicie to, że nie ma sensu ostrzegać go przed czymś, o czym nie masz powodu sądzić, że należy go ostrzec.
Hammerite

1
Tak, widzę. Myślę, że miałam znacznie więcej powodów niż twoje ostrzeżenie o autobusach.
duffymo

7

Cóż, korzystam z listy rozdzielanych parami klucz / wartość w kolumnie NTEXT w SQL Server od ponad 4 lat i to działa. Tracisz elastyczność tworzenia zapytań, ale z drugiej strony, jeśli masz bibliotekę, która utrzymuje / wykracza poza parę kluczowych wartości, nie jest to zły pomysł.


13
Nie, to okropny pomysł. Udało ci się uciec, ale koszt twojego kilku minut opracowywania kosztował cię kiepską wydajność zapytań, elastyczność i łatwość konserwacji twojego kodu.
Paul Tomblin,

5
Paul, zgadzam się. Ale, jak powiedziałem, użyłem, jeśli do określonego celu, a to jest do operacji wprowadzania danych, w której masz wiele rodzajów formularzy. Przeglądam teraz projekt, kiedy nauczyłem się NHibernate, ale wtedy potrzebowałem elastyczności, aby zaprojektować formularz w ASP.NET i używać identyfikatorów pól tekstowych jako klucza w parze klucz / wartość.
Raj

28
+1 tylko w celu przeciwstawienia się negatywnym opiniom. Mówienie komuś, kto utrzymuje aplikację od 4 lat o problemach z konserwacją, jest nieco zarozumiałe. W rozwoju SW jest bardzo mało „okropnych” pomysłów - głównie są to pomysły o bardzo ograniczonym zastosowaniu. Rozsądne jest ostrzeganie ludzi przed ograniczeniami, ale karanie tych, którzy to zrobili i przeżyli, wydaje mi się bardziej świętą postawą niż ja.
Mark Brackett,

7

Potrzebowałem kolumny z wieloma wartościami, można ją zaimplementować jako pole xml

W razie potrzeby można go przekształcić w przecinek

odpytanie listy XML na serwerze SQL za pomocą Xquery .

Będąc polem XML, niektóre z problemów można rozwiązać.

Z CSV: Nie mogę upewnić się, że każda wartość ma odpowiedni typ danych: nie ma sposobu, aby zapobiec 1,2,3, banan, 5

Dzięki XML: wartości w znaczniku mogą być zmuszone do poprawnego typu


Z CSV: nie można używać ograniczeń klucza obcego do łączenia wartości z tabelą odnośników; nie ma sposobu na wymuszenie integralności referencyjnej.

Z XML: wciąż problem


Dzięki CSV: nie można egzekwować wyjątkowości: nie można zapobiec 1,2,3,3,3,5

Z XML: wciąż problem


Z CSV: nie można usunąć wartości z listy bez pobrania całej listy.

Dzięki XML: pojedyncze elementy można usunąć


Z CSV: Trudno wyszukać wszystkie podmioty o podanej wartości na liście; musisz użyć nieefektywnego skanowania tabeli.

Dzięki XML: pole xml może być indeksowane


Z CSV: Trudno policzyć elementy na liście lub wykonać inne agregowane zapytania. **

Z XML: niezbyt trudne


Z CSV: Trudno połączyć wartości z tabelą odnośników, do której się odnoszą. **

Z XML: niezbyt trudne


Z CSV: Trudno pobrać listę w posortowanej kolejności.

Z XML: niezbyt trudne


Z CSV: Przechowywanie liczb całkowitych jako ciągów zajmuje około dwa razy więcej miejsca niż przechowywanie liczb całkowitych binarnych.

Z XML: pamięć jest nawet gorsza niż csv


Z CSV: Plus wiele znaków przecinków.

W przypadku XML: zamiast przecinków używane są tagi


Krótko mówiąc, użycie XML pozwala obejść niektóre problemy z listą rozdzielaną ORAZ można ją w razie potrzeby przekonwertować na listę rozdzielaną


6

Tak, to jest aż tak źle. Moim zdaniem, jeśli nie lubisz korzystać z relacyjnych baz danych, poszukaj alternatywy, która bardziej Ci odpowiada, istnieje wiele interesujących projektów „NOSQL” z kilkoma naprawdę zaawansowanymi funkcjami.


0

Prawdopodobnie wziąłbym środek: uczynię każde pole w CSV oddzielną kolumną w bazie danych, ale nie martwię się zbytnio o normalizację (przynajmniej na razie). W pewnym momencie normalizacja może stać się interesująca, ale przy wszystkich danych zapisanych w jednej kolumnie praktycznie nie zyskujesz na korzystaniu z bazy danych. Musisz podzielić dane na pola logiczne / kolumny / cokolwiek chcesz je nazwać, zanim będziesz mógł w ogóle manipulować nimi w sposób znaczący.


Formularz zawiera jeszcze kilka pól, to tylko jedna część formularza (której nie wyjaśniłem dobrze w pytaniu).
Mad Scientist,

0

Jeśli masz stałą liczbę pól boolowskich, możesz użyć INT(1) NOT NULL(lub BIT NOT NULLjeśli istnieje) lub CHAR (0)(zerowalne) dla każdego z nich. Możesz także użyć SET(nie pamiętam dokładnej składni).


1
INT(1)zajmuje 4 bajty; to (1)jest bez znaczenia.
Rick James
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.