EAV - czy to naprawdę źle we wszystkich scenariuszach?

65

Zastanawiam się nad użyciem modelu podmiot-wartość-wartość (EAV) do niektórych rzeczy w jednym z projektów, ale wszystkie pytania na ten temat w przepełnieniu stosu kończą się odpowiedziami nazywającymi EAV anty-wzorcem.

Ale zastanawiam się, czy we wszystkich przypadkach jest tak źle.

Powiedzmy, że produkt sklepowy ma wspólne cechy, takie jak nazwa, opis, obraz i cena, które biorą udział w logice wielu miejsc i ma (pół) unikalne cechy, takie jak zegarek i piłka plażowa, można by opisać zupełnie innymi aspektami. Myślę więc, że EAV nadawałby się do przechowywania tych (pół) unikalnych funkcji.

Wszystko to przy założeniu, że do wyświetlenia listy produktów wystarczy informacja w tabeli produktów (co oznacza, że ​​nie dotyczy EAV) i tylko przy pokazywaniu jednego produktu / porównywanie do 5 produktów / itp. wykorzystywane są dane zapisane przy użyciu EAV.

Widziałem takie podejście w handlu Magento i jest dość popularne, więc czy są przypadki, w których EAV jest uzasadniony?

Giedrius
źródło
2
@busy_wait Tabele „Entity-Attibute-Value” - patrz model encja – atrybut – wartość na Wikipedii .
Ross Patterson
Na przykład wzór EAV działający naprawdę dobrze, spójrz na bazę danych Datomic. Przechowuje wszystko we wzorze EAVT (T jest „znacznikiem czasu”, a właściwie bardziej jak identyfikator transakcji). Ich [dokumentacja indeksująca] (docs.datomic.com/indexes.html) wydaje się, że najlepiej to pokazuje. Przykład strasznego działania EAV można znaleźć w Wordpress .
Dan Ross

Odpowiedzi:

80

https://web.archive.org/web/20140831134758/http://www.dbforums.com/database-concepts-design/1619660-otlt-eav-design-why-do-people-hate.html

EAV zapewnia programistom elastyczność w definiowaniu schematu według potrzeb, co jest dobre w niektórych okolicznościach.

Z drugiej strony działa bardzo słabo w przypadku źle zdefiniowanego zapytania i może obsługiwać inne złe praktyki.

Innymi słowy, EAV daje ci wystarczająco dużo liny, aby się powiesić, a w tej branży rzeczy powinny być zaprojektowane na najniższym poziomie złożoności, ponieważ facet zastępujący cię w projekcie prawdopodobnie będzie idiotą.

wałek klonowy
źródło
32
Uwielbiam ostatnie zdanie.
Zohar Peled
2
Przegniły link. Czy jest gdzieś wersja buforowana?
Wildcard,
1
Nie podążaj za linkiem. Strona ładuje się powoli i nie jest pomocna. Również fora w starym stylu takie śmierdzą. Zamiast tego użyj przepełnienia stosu! Poproś o dobre / pomocne odpowiedzi i zrzuć kosz.
Jess
29

W skrócie, EAV jest przydatny, gdy twoja lista atrybutów często rośnie lub gdy jest tak duża, że ​​większość wierszy byłaby wypełniona głównie zerami, gdyby każdy atrybut był kolumną. Staje się anty-wzorcem, gdy jest używany poza tym kontekstem.

Karl Bielefeldt
źródło
16
Chciałbym zastąpić „często” przez „potrzebuje możliwości zmiany w czasie wykonywania”.
Dok. Brown
3
Możemy skrócić to jeszcze bardziej, używając Doca Browna, używając dość dobrze rozumianego słowa „dynamiczny” - EAV jest przydatny, gdy lista atrybutów może się zmieniać dynamicznie.
Alexander Mills,
Jeszcze bardziej „kiedy twoje atrybuty mogą się zmienić” - „dynamicznie” jest w tym kontekście nieco zbędny :)
Wranorn,
1
Czy jest to z konieczności bardziej przydatne niż, powiedzmy, posiadanie formularza zmiany atrybutu CREATE TABLEdla nowego atrybutu?
Damian Yerrick
@DamianYerrick ciekawe podejście. Czy używałeś tego w produkcji?
wykopywanie
21

Załóżmy, że produkt jest sklepowy, ma wspólne cechy, takie jak nazwa, opis, obraz, cena itp., Które biorą udział w logice wielu miejsc i ma (pół) unikalne cechy, takie jak zegarek i piłka plażowa byłyby opisywane zupełnie innymi aspektami . Więc myślę, że EAV byłby odpowiedni do przechowywania tych (pół) unikalnych funkcji?

Korzystanie ze struktury EAV dla ma kilka implikacji, które są kompromisami.

Wymieniasz „mniej miejsca dla wiersza, ponieważ nie masz 100 kolumn, które są null„ przeciw ”bardziej złożonym zapytaniom i modelom”.

Posiadanie EAV zazwyczaj oznacza, że ​​wartość jest łańcuchem, w który można włożyć dowolne dane. Ma to następnie wpływ na sprawdzanie poprawności i ograniczeń. Rozważ sytuację, w której umieściłeś liczbę używanych baterii jako coś w tabeli EAV. Chcesz znaleźć latarkę, która używa baterii w rozmiarze C, ale mniej niż 4 z nich.

select P.sku
from
  products P
  attrib Ab on (P.sku = Ab.sku and Ab.key = "batteries")
  attrib Ac on (P.sku = Ac.sku and Ac.key = "count")
where
  cast(Ac.value as int) < 4
  and Ab.value = 'C'
  ...

Należy tutaj zdać sobie sprawę, że nie można rozsądnie użyć indeksu wartości. Nie można również uniemożliwić komuś wstawienia czegoś, co nie jest liczbą całkowitą lub nieprawidłową liczbą całkowitą (używa baterii „-1”), ponieważ kolumna wartości jest używana wielokrotnie do różnych celów.

Ma to następnie wpływ na próbę napisania modelu dla produktu. Będziesz miał ładne, wpisane wartości ... ale będziesz też Map<String,String>siedział tam z różnego rodzaju rzeczami . Ma to dalsze implikacje podczas szeregowania go do formatu XML lub Json oraz złożoność próby sprawdzania poprawności lub zapytań przeciwko tym strukturom.

Niektóre alternatywy lub modyfikacje wzorca, które należy wziąć pod uwagę, to zamiast klucza swobodnego, mieć inną tabelę z prawidłowymi kluczami. Oznacza to, że zamiast porównywania ciągów w bazie danych, sprawdzasz równość identyfikatorów kluczy obcych. Zmiana samego klucza odbywa się w jednym miejscu. Masz znany zestaw kluczy, co oznacza, że ​​można je wykonać jako wyliczenie.

Możesz także mieć powiązane tabele zawierające atrybuty określonej klasy produktu. Dział spożywczy może mieć inny stół z kilkoma powiązanymi z nim atrybutami, których materiały budowlane nie potrzebują (i odwrotnie).

+----------+    +--------+    +---------+
|Grocery   |    |Product |    |BuildMat |
|id (fk)   +--->|id (pk) |<---+id (fk)  |
|expiration|    |desc    |    |material |
|...       |    |img     |    |...      |
+----------+    |price   |    +---------+
                |...     |               
                +--------+               

Są chwile, które szczególnie wymagają tabeli EAV.

Zastanów się nad sytuacją, w której piszesz nie tylko system inwentaryzacji dla swojej firmy, w którym znasz każdy produkt i każdy atrybut. Piszesz teraz system zapasów, aby sprzedawać je innym firmom. Nie możesz znać każdego atrybutu każdego produktu - będą musieli je zdefiniować.

Jednym z pomysłów, który się pojawia, jest „pozwolimy klientowi modyfikować tabelę”, a to jest po prostu złe (wchodzisz w meta-programowanie struktur tabel, ponieważ nie wiesz już, gdzie jest, mogą po królewsku zepsuć strukturę lub uszkodzić aplikacji, mają oni dostęp do robienia złych rzeczy, a implikacje tego dostępu stają się znaczące). W MVC4 jest więcej na temat tej ścieżki : jak stworzyć model w czasie wykonywania?

Zamiast tego tworzysz interfejs administracyjny do tabeli EAV i zezwalasz na jego użycie. Jeśli klient chce utworzyć wpis dla „polkadots”, wchodzi do tabeli EAV i już wiesz, jak sobie z tym poradzić.

Przykład tego można zobaczyć w modelu bazy danych dla Redmine , można zobaczyć tabelę custom_fields i tabelę custom_values ​​- są to części EAV, które pozwalają na rozbudowę systemu.


Zauważ, że jeśli znajdziesz całą strukturę tabeli, która wygląda bardziej jak EAV niż relacyjna, możesz spojrzeć na smak KV NoSQL (Cassandra, Redis, Mongo, ...). Zdaj sobie sprawę, że często mają one inne kompromisy w projekcie, które mogą, ale nie muszą być odpowiednie do tego, do czego go używasz. Są one jednak specjalnie zaprojektowane z myślą o strukturze EAV.

Możesz przeczytać SQL vs NoSQL dla systemu zarządzania zapasami

Postępując zgodnie z tym podejściem z zorientowaną na dokumenty bazą danych NoSQL (kanapa, mongo), możesz uznać każdy element ekwipunku za dokument na dysku ... wyciągnięcie wszystkiego z jednego dokumentu jest szybkie. Co więcej, dokument ma taką strukturę, że można szybko wyciągnąć jedną rzecz. Z drugiej strony wyszukiwanie we wszystkich dokumentach elementów pasujących do określonego atrybutu może mieć mniejszą wydajność (porównaj używając „grep” względem wszystkich plików) ... to wszystko jest kompromisem.

Innym podejściem byłby LDAP, w którym miałby się bazę ze wszystkimi powiązanymi z nim elementami, ale wówczas zastosowanoby również dodatkowe klasy obiektów dla innych typów elementów. (patrz Inwentaryzacja systemu za pomocą LDAP )

Po pójść tą drogą, to może znaleźć coś, co odpowiada dokładnie to, czego szukasz ... mimo wszystko pochodzi z pewnych kompromisów.

Społeczność
źródło
10

6 lat później

Teraz, gdy JSON w Postgres jest już dostępny, mamy inną opcję dla tych, którzy używają Postgres. Jeśli chcesz tylko dołączyć dodatkowe dane do produktu, Twoje potrzeby są dość proste. Przykład:

CREATE TABLE products (sku VARCHAR(30), shipping_weight REAL, detail JSON);
INSERT INTO products ('beachball', 1.0, '{"colors": ["red", "white"], "diameter": "50cm"}');

SELECT * FROM products;
    sku    | weight |               detail               
-----------+--------+------------------------------------
 beachball |      1 | {"colors": ["red", "white"], "diameter": "50cm"}

Oto płynniejsze wprowadzenie do JSON w Postgres: https://www.compose.com/articles/is-postgresql-your-next-json-database/ .

Zauważ, że Postgres faktycznie przechowuje JSONB, a nie zwykły tekst JSON, i obsługuje indeksy pól wewnątrz dokumentu / pola JSONB, na wypadek, gdybyś odkrył, że faktycznie chcesz zapytać o te dane.

Należy również pamiętać, że pól w polu JSONB nie można modyfikować indywidualnie za pomocą zapytania UPDATE; musisz zastąpić całą zawartość pola JSONB.

Ta odpowiedź może nie dotyczyć bezpośrednio pytania, ale stanowi alternatywę dla wzorca EAV, którą powinien rozważyć każdy, kto zastanawia się nad pierwotnym pytaniem.

Dan Ross
źródło
3
Myślę, że dobrym pomysłem jest opublikowanie alternatywnego rozwiązania. Aby utrzymać innych na drodze, MS SQL wspierał kolumny XML z możliwością indeksowania ich przez pewien czas, a od 2016 roku może zrobić to samo z JSON (chociaż JSON nie jest rodzimym typem kolumny w MS SQL, nadal można go indeksować ). Z drugiej strony - z tego, co przeczytałem, obsługa Postgres JSON jest lepsza, na przykład wygląda na to, że obsługuje indeksy danych we właściwościach tablicy JSON.
Giedrius
1
„... pól w polu JSONB nie można modyfikować indywidualnie za pomocą zapytania UPDATE; należy zastąpić całą zawartość pola JSONB.” To jest przestarzałe, prawda? W jsonb_set()Postgresie 9.5 i nowszych istnieje funkcja, która właśnie do tego służy. (Artykuł, który podłączyłeś do linków, z kolei do nowego artykułu omawiającego dodatki funkcji 9.5 ).
Wildcard
7

Zazwyczaj ludzie patrzą w drugą stronę, jeśli używasz go do tabel przeglądowych lub w innych sytuacjach, w których korzyścią jest powstrzymanie się od tworzenia tabel dla jednej lub dwóch przechowywanych wartości. Opisywana sytuacja, w której zasadniczo przechowujesz właściwości przedmiotu, brzmi zupełnie normalnie (i znormalizowana). Poszerzenie tabeli do przechowywania zmiennej liczby atrybutów przedmiotu to zły pomysł.

Ogólny przypadek przechowywania różnych danych w długim cienkim stole ... Nie powinieneś bać się tworzyć nowych tabel, jeśli zajdzie taka potrzeba, a posiadanie tylko jednego lub dwóch długich wąskich stołów nie jest dużo lepsze niż posiadanie tylko jednego lub dwa krótkie tabele tłuszczu.

Biorąc to pod uwagę, jestem znany z używania tabel EAV do logowania. Mają dobrą użyteczność.

Satanicpuppy
źródło
Proszę zdefiniować „chudy stolik” i „gruby stolik”.
Tulains Córdova
@ TulainsCórdova: „Chuda” tabela byłaby tabelą z kilkoma wierszami i wieloma kolumnami, a grubą tabelą byłaby tabela z wieloma kolumnami i kilkoma wierszami. Przykładem może być zbudowanie tabeli odnośników, w której masz właściwości powiedzmy książek. Tabela gruba miałaby jeden rekord na książkę, z wieloma kolumnami na określone fragmenty danych, a cienka tabela miałaby może cztery kolumny id, książka, nazwa_pola, dane_pola. Zaletą pierwszego jest to, że rekordów jest mniej, ale negatywne jest to, że niektóre pola mogą być puste, a całość jest trudniejsza do rozszerzenia.
Satanicpuppy
@Satanicpuppy Myślę, że twoje definicje chude / tłuszczowe są pomieszane - są takie same. Czy masz na myśli, że chudy stół ma kilka kolumn i wiele wierszy?
Charles Wood
1

EAV zmienia problem wyraźnej struktury na implikowaną percepcję. Zamiast powiedzieć, że X jest tabelą z kolumnami A i B. Sugerujesz, że kolumny A i B tworzą tabelę X. Jest odwrotnie w pewnym sensie, ale niekoniecznie istnieje mapowanie jeden do jednego. Można powiedzieć, że zarówno A, jak i B odwzorowują na tabele (lub typ) X i Y. Może to być ważne w bardziej zaangażowanej dziedzinie, w której kontekst ma znaczenie.

Studiowałem Datomic, dla tego rodzaju podejścia i myślę, że jest to bardzo przydatny i potężny system z ograniczeniami co do tego, co powinieneś z nim zrobić (nie, że nie możesz).

To, że EAV byłby powolny, lub „dać ci wystarczająco liny, aby się powiesić”, nie jest stwierdzeniem, z którym się zgodzę. Zamiast tego położyłbym większy nacisk na mocne strony EAV i jeśli pasuje to do twojej problematycznej przestrzeni, powinieneś to rozważyć.

Z mojego doświadczenia wynika, że ​​jest to cudowne, niemal nieograniczone podejście do modelowania. W szczególności w przypadku Datomic narzucają one zestaw semantyczny na wszystko. Każda decyzja modelowania, która modeluje relację, może swobodnie przejść od jednej do wielu bez konieczności przeprojektowywania kolumn / tabel. Możesz także wrócić, o ile ograniczenie nie narusza niezmiennika. Pod maską jest tak samo.

Moim zdaniem problem z EAV polegał na braku implementacji takiej jak Datomic. Ponieważ jest to pytanie dotyczące EAV, nie chcę pochwalać się Datomic, ale jest to jedna z tych rzeczy, w których myślę, że wszystko ułożyło się poprawnie w odniesieniu do EAV.

John Leidegren
źródło