Dlaczego miałbyś przechowywać wyliczenie w DB?

69

Widziałem wiele pytań, takich jak to , proszących o porady, jak przechowywać wyliczenia w DB. Ale zastanawiam się, dlaczego miałbyś to zrobić. Powiedzmy, że mam byt Personz genderpolem i Genderwyliczeniem. Następnie moja tabela osób ma płeć kolumny.

Poza oczywistym powodem wymuszania poprawności, nie rozumiem, dlaczego miałbym utworzyć dodatkową tabelę, genderaby zmapować to, co już mam w swojej aplikacji. I tak naprawdę nie lubię tego powielania.

użytkownik3748908
źródło
1
Gdzie jeszcze przechowaliby Państwo dane, które mogą się regularnie zmieniać? Chociaż mogłeś pomyśleć o wszystkich opcjach, co zrobić, jeśli ktoś przyjdzie i chce dodać nową opcję. Czy jesteś gotowy na ulepszenie tej zakodowanej listy? Ktoś może chcieć podać swoją płeć jako coś innego niż mężczyzna lub kobieta, np. Interseksualny.
JB King
4
@JBKing ... wystarczy spojrzeć na listę płci na Facebooku.
3
Jeśli Twoi klienci są „oszukanymi Tumblrytami”, to cholernie dobrze stwórz schemat bazy danych, który pozwala ci stworzyć coś, co zaspokoi ich potrzeby, przynajmniej jeśli zamierzasz kontynuować działalność.
Steven Burnap,

Odpowiedzi:

74

Weźmy inny przykład, który jest mniej przepełniony koncepcjami i oczekiwaniami. Mam tutaj wyliczenie i jest to zestaw priorytetów dla błędu.

Jaką wartość przechowujesz w bazie danych?

Tak, mogę być przechowywanie 'C', 'H', 'M'oraz 'L'w bazie danych. Lub 'HIGH'tak dalej. Ma to problem z ciągami danych. Znany jest zestaw prawidłowych wartości, a jeśli nie przechowujesz tego zestawu w bazie danych, praca z nim może być trudna.

Dlaczego przechowujesz dane w kodzie?

Masz List<String> priorities = {'CRITICAL', 'HIGH', 'MEDIUM', 'LOW'};lub coś w tym rodzaju w kodzie. Oznacza to, że masz różne mapowania tych danych do właściwego formatu (wstawiasz wszystkie Caps do bazy danych, ale wyświetlasz je jako Critical). Twój kod jest teraz również trudny do zlokalizowania. Związano reprezentację pomysłu bazy danych z ciągiem przechowywanym w kodzie.

Gdziekolwiek musisz uzyskać dostęp do tej listy, musisz mieć duplikację kodu lub klasę z szeregiem stałych. Żaden z nich nie jest dobrym rozwiązaniem. Nie należy również zapominać, że istnieją inne aplikacje, które mogą korzystać z tych danych (które mogą być napisane w innych językach - aplikacja internetowa Java ma używany system raportowania Crystal Reports i dane wsadowe do zadania wsadowego Perl ). Aparat raportujący musiałby znać prawidłową listę danych (co się stanie, jeśli nie ma nic zaznaczonego w 'LOW'priorytecie i musisz wiedzieć, że jest to prawidłowy priorytet dla raportu?), A zadanie wsadowe będzie zawierało informacje o tym, co jest ważne wartości są.

Hipotetycznie, to może powiedzieć „jesteśmy sklep single-język - wszystko jest napisane w języku Java” i mieć jeden .jar, który zawiera tę informację - ale teraz oznacza to, że aplikacje są ściśle sprzężone ze sobą i że .jar zawierającego dane. Musisz wydać część raportującą i część dotyczącą aktualizacji wsadowej wraz z aplikacją internetową za każdym razem, gdy zachodzi zmiana - i mam nadzieję, że to wydanie będzie przebiegać płynnie dla wszystkich części.

Co się stanie, gdy szef chce innego priorytetu?

Twój szef przyszedł dzisiaj. Jest nowy priorytet - CEO. Teraz musisz przejść i zmienić cały kod oraz dokonać ponownej kompilacji i ponownego wdrożenia.

Dzięki metodzie „wyliczania w tabeli” aktualizujesz listę wyliczania, aby uzyskać nowy priorytet. Cały kod, który pobiera listę, pobiera ją z bazy danych.

Dane rzadko są samodzielne

W przypadku priorytetów klucze danych do innych tabel, które mogą zawierać informacje o przepływach pracy lub o tym, kto może ustawić ten priorytet lub co innego.

Wracając do płci, jak wspomniano w pytaniu: Płeć ma link do używanych zaimków: he/his/himi she/hers/her... i chcesz uniknąć twardego kodowania tego w samym kodzie. A potem przychodzi twój szef i musisz dodać, że masz 'OTHER'płeć (dla uproszczenia) i musisz powiązać tę płeć z they/their/them... i twój szef widzi, co ma Facebook i ... no tak.

Ograniczając się do łańcucha danych o ciągach ciągłych zamiast do tablicy wyliczeniowej, musisz teraz zreplikować ten ciąg w szeregu innych tabel, aby zachować ten związek między danymi a innymi bitami.

Co z innymi magazynami danych?

Niezależnie od tego, gdzie to przechowujesz, istnieje ta sama zasada.

  • Możesz mieć plik priorities.propz listą priorytetów. Czytasz tę listę z pliku właściwości.
  • Możesz mieć bazę danych magazynu dokumentów (taką jak CouchDB ), która zawiera wpis dla enums(a następnie napisać funkcję sprawdzania poprawności w JavaScript ):

    {
       "_id": "c18b0756c3c08d8fceb5bcddd60006f4",
       "_rev": "1-c89f76e36b740e9b899a4bffab44e1c2",
       "priorities": [ "critical", "high", "medium", "low" ],
       "severities": [ "blocker", "bad", "annoying", "cosmetic" ]
    }
    
  • Możesz mieć plik XML ze schematem:

    <xs:element name="priority" type="priorityType"/>
    
    <xs:simpleType name="priorityType">
      <xs:restriction base="xs:string">
        <xs:enumeration value="critical"/>
        <xs:enumeration value="high"/>
        <xs:enumeration value="medium"/>
        <xs:enumeration value="low"/>
      </xs:restriction>
    </xs:simpleType>
    

Podstawowa idea jest taka sama. Sam magazyn danych jest miejscem, w którym należy przechowywać i egzekwować listę prawidłowych wartości. Umieszczając go tutaj, łatwiej jest zrozumieć kod i dane. Nie trzeba się martwić o defensywnie sprawdzanie co masz za każdym razem (jest to górna przypadek? Czy niższa? Dlaczego istnieje chriticaltyp w tej kolumnie? Etc ...), bo wiesz, co otrzymujesz z powrotem magazyn danych jest dokładnie to, czego oczekuje magazyn danych, że w przeciwnym razie wyślesz - i możesz zapytać magazyn danych o listę prawidłowych wartości.

Na wynos

Zestaw prawidłowych wartości to dane , a nie kod. Ty nie musisz dążyć do DRY kodu - ale problem powielania jest, że jesteś powielanie danych w kodzie, a nie respektując jego miejsce jako danych i przechowywanie go w bazie danych.

Ułatwia pisanie wielu aplikacji w magazynie danych i pozwala uniknąć instancji, w których trzeba będzie wdrożyć wszystko, co jest ściśle związane z danymi - ponieważ nie połączono kodu z danymi.

Ułatwia to testowanie aplikacji, ponieważ nie trzeba ponownie testować całej aplikacji po CEOdodaniu priorytetu - ponieważ nie ma kodu, który dbałby o rzeczywistą wartość priorytetu.

Możliwość niezależnego rozumowania kodu i danych ułatwia znajdowanie i naprawianie błędów podczas konserwacji.

Peter Mortensen
źródło
6
Jeśli możesz dodać wartość wyliczenia do swojego kodu bez konieczności zmiany jakiejkolwiek logiki (i żeby nie było to zlokalizowane jej wyświetlanie), wątpię w to, czy w ogóle potrzebna jest dodatkowa wartość wyliczenia. I chociaż jestem wystarczająco dorosły, aby docenić możliwość łatwego tworzenia zapytań o kopie zapasowe bazy danych za pomocą prostych zapytań SQL w celu analizy problemu, obecnie dzięki ORM możesz bardzo dobrze sobie radzić bez konieczności patrzenia na bazę danych w ogóle. Nie rozumiem jednak tutaj sensu lokalizacji (zaimków) - z pewnością nie powinno to być w bazie danych, ale powiedziałbym, że jakieś pliki zasobów.
Voo,
1
@ Voo zaimki to przykład innych danych związanych z tą wartością enumeque. Gdyby dane nie znajdowały się w tabeli, wartości o ciągach ciągłych musiałyby tam istnieć bez odpowiednich ograniczeń FK. Jeśli masz zaimki (takie jak ten) w pliku zasobów, masz połączenie między bazą danych a plikiem (zaktualizuj bazę danych i ponownie wdróż plik). Zastanów się nad wyliczeniami redmine, które można modyfikować za pomocą interfejsu administratora w locie, bez konieczności ponownego wdrażania.
1
... pamiętaj również, że bazy danych to magazyn danych typu polyglot. Jeśli wymagana jest weryfikacja jako część ORM w jednym języku, konieczne jest zduplikowanie tej weryfikacji w dowolnym innym używanym języku (ostatnio pracowałem z interfejsem Java, w którym Python wypychał dane do bazy danych - ORM Java i systemy Python muszą się zgadzać - i ta umowa (prawidłowe typy) najłatwiej była wdrożyć, zmuszając bazę danych do egzekwowania jej za pomocą tabeli „wyliczania”).
2
@ Voo Wykorzystanie wyliczenia przez Redmine jest takie samo jak bugzilla „najważniejsza tabela zawiera wszystkie błędy systemu. Składa się z różnych właściwości błędów, w tym wszystkich wartości wyliczenia, takich jak dotkliwość i priorytet”. - To nie jest dowolne pole tekstowe, to wartość, która jest jednym z tego znanego i wymiennego zestawu. To nie jest wyliczanie czasu kompilacji , ale wciąż jest wyliczane. Zobacz także Mantis .
1
Więc, aby potwierdzić - masz na myśli, że ludzie nigdy nie powinni używać Enums? Nie było jasne.
niico
18

Który z nich, według Ciebie, może częściej powodować błędy podczas czytania zapytania?

select * 
from Person 
where Gender = 1

Lub

select * 
from Person join Gender on Person.Gender = Gender.GenderId
where Gender.Label = "Female" 

Ludzie tworzą tabele enum w SQL, ponieważ uważają, że te ostatnie są bardziej czytelne - co prowadzi do mniejszej liczby błędów podczas pisania i obsługi SQL.

Możesz uczynić płeć ciągiem bezpośrednio w Person, ale wtedy będziesz musiał spróbować wyegzekwować wielkość liter. Możesz także zwiększyć trafienie pamięci dla tabeli i czas zapytania ze względu na różnicę między ciągami i liczbami całkowitymi w zależności od tego, jak wspaniale jest twoja baza danych w optymalizacji rzeczy.

Telastyn
źródło
5
Ale potem dołączamy do stołów. Jeśli moja jednostka ma dwa wyliczenia, dołączę trzy tabele tylko dla prostego zapytania.
user3748908,
11
@ user3748908 - więc? Połączenia są w czym DB są dobre, a alternatywy są gorsze - przynajmniej w oczach ludzi, którzy wybrali tę trasę.
Telastyn
8
@ user3748908: Bazy danych są nie tylko bardzo dobre w wykonywaniu połączeń, ale także w egzekwowaniu spójności. Wymuszanie spójności działa naprawdę, bardzo dobrze, gdy możesz wskazać kolumnę w jednej tabeli na wiersz identyfikujący drugą i powiedzieć „wartość dla tej kolumny musi być jednym z identyfikatorów w tej tabeli”.
Blrfl,
2
To wszystko prawda, ale istnieje wiele przypadków, w których musisz poświęcić połączenia ze względu na wydajność. Nie zrozum mnie źle, jestem o tym rodzaju projektowania i dołączania, ale rzucam, że świat się nie skończy, jeśli okaże się, że czasami nie potrzebujesz złączeń ze względu na wydajność.
JonH
3
Jeśli musisz zrezygnować z dołączania do tabel referencyjnych ze względów wydajnościowych @JonH, musisz kupić większy serwer lub przestać próbować przepychać predykaty przez dużą liczbę podkwerend (zakładam, że wiesz, co robisz). Tabele odwołań to elementy, które powinny znajdować się w pamięci podręcznej w ciągu kilku sekund od uruchomienia bazy danych.
Ben,
10

Nie mogę uwierzyć, że ludzie jeszcze o tym nie wspominali.

Klucz obcy

Utrzymując enum w bazie danych, i dodanie klucza obcego w tabeli, która zawiera wartość enum Ci zapewnić , że żaden kod kiedykolwiek wejdzie niepoprawne wartości dla tej kolumny. Pomaga to w integralności danych i jest najbardziej oczywistym powodem, dla którego IMO powinna mieć tabele dla wyliczeń.

Benjamin Gruenbaum
źródło
Pytanie ma zaledwie 5 wierszy i wyraźnie stwierdza „Poza oczywistym powodem wymuszania poprawności”. Więc nikt o tym nie wspominał, ponieważ OP twierdzi, że to oczywiste i szuka innych uzasadnień - PS: Zgadzam się z tobą, to wystarczający powód.
user1007074,
6

Jestem w obozie, który się z tobą zgadza. Jeśli zachowasz wyliczenie płci w kodzie i tblGender w bazie danych, możesz mieć problemy z czasem konserwacji. Musisz udokumentować, że te dwa podmioty powinny mieć te same wartości, a zatem wszelkie zmiany, które wprowadzisz w jednym, musisz wprowadzić również w drugim.

Następnie musisz przekazać wartości wyliczeniowe do procedur przechowywanych w następujący sposób:

create stored procedure InsertPerson @name varchar, @gender int
    insert into tblPeople (name, gender)
    values (@name, @gender)

Ale pomyśl, jak byś to zrobił, gdybyś trzymał te wartości w tabeli bazy danych:

create stored procedure InsertPerson @name varchar, @genderName varchar
    insert into tblPeople (name, gender)
    select @name, fkGender
    from tblGender
    where genderName = @genderName --I hope these are the same

Na pewno relacyjne bazy danych są tworzone z myślą o sprzężeniach, ale które zapytanie jest łatwiejsze do odczytania?


Oto kolejne przykładowe zapytanie:

create stored procedure SpGetGenderCounts
    select count(*) as count, gender
    from tblPeople
    group by gender

Porównaj to z tym:

create stored procedure SpGetGenderCounts
    select count(*) as count, genderName
    from tblPeople
    inner join tblGender on pkGender = fkGender
    group by genderName --assuming no two genders have the same name

Oto jeszcze jedno przykładowe zapytanie:

create stored procedure GetAllPeople
    select name, gender
    from tblPeople

Pamiętaj, że w tym przykładzie będziesz musiał przekonwertować komórkę płci w wynikach z int na wyliczenie. Te konwersje są jednak łatwe. Porównaj to z tym:

create stored procedure GetAllPeople
    select name, genderName
    from tblPeople
    inner join tblGender on pkGender = fkGender

Wszystkie te zapytania są mniejsze i łatwiejsze w utrzymaniu, gdy przechodzisz do pomysłu, aby trzymać definicje wyliczeń poza bazą danych.

user2023861
źródło
1
Co jeśli to nie była płeć. Myślę, że jesteśmy zbyt rozłączni, jeśli chodzi o płeć . Co by było, gdyby PO powiedział: „Powiedzmy, że mam błąd z polem Priorytet” - czy Twoja odpowiedź się zmieni?
4
@MichaelT Lista możliwych wartości „priorytet” jest częścią kodu przynajmniej w takim samym stopniu, w jakim jest częścią danych. Widzisz ikony graficzne dla różnych priorytetów? Nie spodziewasz się, że zostaną pobrani z bazy danych? Takie rzeczy mogą być tematyczne i stylizowane i nadal reprezentują ten sam zakres wartości przechowywany w DB. I tak nie można tak po prostu zmienić w bazie danych; masz kod prezentacji do synchronizacji.
Eugene Ryabtsev,
1

Chciałbym utworzyć tabelę Płeć z tego powodu, że można jej użyć do analizy danych. Mógłbym wyszukać wszystkie osoby płci męskiej lub żeńskiej w bazie danych, aby wygenerować raport. Im więcej sposobów przeglądania danych, tym łatwiej będzie znaleźć informacje o trendach. Oczywiście jest to bardzo proste wyliczenie, ale w przypadku skomplikowanych wyliczeń (takich jak kraje świata lub stany) ułatwia generowanie specjalistycznych raportów.

zackery.fix
źródło
1

Najpierw musisz zdecydować, czy baza danych będzie kiedykolwiek używana tylko przez jedną aplikację, czy może istnieć możliwość korzystania z niej przez wiele aplikacji. W niektórych przypadkach baza danych jest niczym innym jak formatem pliku dla aplikacji (bazy danych SQLite mogą być często używane w tym zakresie). W takim przypadku bit powielający definicję wyliczenia jako tabelę często może być w porządku i może mieć większy sens.

Jednak gdy tylko rozważysz możliwość posiadania dostępu do bazy danych przez wiele aplikacji, wówczas tabela dla wyliczenia ma sens (inne odpowiedzi zawierają bardziej szczegółowe informacje na temat tego, dlaczego). Inną rzeczą do rozważenia będzie Ty lub inny programista, którzy chcą przyjrzeć się surowym danym bazy danych. Jeśli tak, można to uznać za inne zastosowanie aplikacji (tylko takie, w którym miernikiem laboratoryjnym jest surowy SQL).

Jeśli masz wyliczenie zdefiniowane w kodzie (dla czystszego kodu i sprawdzania czasu kompilacji), a także tabelę w bazie danych, zaleciłbym dodanie testów jednostkowych w celu sprawdzenia, czy oba są zsynchronizowane.

Eric Johnson
źródło
1

Jeśli masz wyliczenie kodu, które jest używane do napędzania logiki biznesowej w kodzie, nadal powinieneś utworzyć tabelę reprezentującą dane w bazie danych z wielu powodów wyszczególnionych powyżej / poniżej. Oto kilka wskazówek, które pomogą zapewnić synchronizację wartości DB z wartościami kodu:

  1. Nie zmieniaj pola ID w tabeli w kolumnę Tożsamość. Uwzględnij identyfikator i opis jako pola.

  2. Zrób coś innego w tabeli, co pomoże programistom wiedzieć, że wartości są półstatyczne / powiązane z wyliczeniem kodu. We wszystkich innych tabelach przeglądowych (zwykle tam, gdzie użytkownicy mogą dodawać wartości) zazwyczaj mam LastChangedDateTime i LastChangedBy, ale brak ich w tabelach związanych z wyliczaniem pomaga mi pamiętać, że są one zmieniane tylko przez programistów. Dokumentuj to.

  3. Utwórz kod weryfikacyjny, który sprawdza, czy każda wartość w wyliczeniu znajduje się w odpowiedniej tabeli i czy tylko te wartości znajdują się w odpowiedniej tabeli. Jeśli masz zautomatyzowane „testy kondycji” aplikacji, które są uruchamiane po kompilacji, w tym miejscu. Jeśli nie, uruchom kod automatycznie podczas uruchamiania aplikacji, ilekroć aplikacja działa w środowisku IDE.

  4. Tworzenie produkcji dostarcza skrypty SQL, które robią to samo, ale z poziomu bazy danych. Prawidłowo utworzone pomogą również w migracji środowiska.

Paul Schirf
źródło
0

Zależy również od tego, kto uzyskuje dostęp do danych. Jeśli masz tylko jedną aplikację, może być w porządku. Jeśli dodasz w hurtowni danych lub systemie raportowania. Będą musieli wiedzieć, co oznacza ten kod, jaka jest ludzka wersja kodu, którą można zmodyfikować.

Zwykle tabela typów nie byłaby duplikowana w kodzie jako wyliczenie. Możesz załadować tabelę typów do listy, która jest buforowana.

Class GenderList

   Public Shared Property UnfilteredList
   Public Shared Property Male = GetItem("M")
   Public Shared Property Female = GetItem("F")

End Class

Często typ przychodzi i odchodzi. Potrzebna byłaby data dodania nowego typu. Dowiedz się, kiedy określony typ został usunięty. Wyświetlaj tylko w razie potrzeby. Co jeśli klient chce „transpłciowości” jako płci, ale inni klienci tego nie robią? Wszystkie te informacje najlepiej przechowywać w bazie danych.

the_lotus
źródło