Dlaczego relacyjne bazy danych nie obsługują zwracania informacji w formacie zagnieżdżonym?

46

Załóżmy, że tworzę bloga, w którym chcę mieć posty i komentarze. Tworzę więc dwie tabele, tabelę „posty” z kolumną „id” z automatyczną inkrementacją liczb całkowitych i tabelę „komentarzy” z kluczem obcym „post_id”.

Następnie chcę uruchomić to, co prawdopodobnie będzie moim najczęstszym zapytaniem, czyli pobrać post i wszystkie jego komentarze. Będąc dość nowym w relacyjnych bazach danych, podejście, które wydaje mi się najbardziej oczywiste, polega na napisaniu zapytania, które wyglądałoby mniej więcej tak:

SELECT id, content, (SELECT * FROM comments WHERE post_id = 7) AS comments
FROM posts
WHERE id = 7

Który dałby mi identyfikator i treść posta, który chcę, wraz ze wszystkimi odpowiednimi wierszami komentarzy spakowanymi starannie w tablicy (zagnieżdżona reprezentacja, taka jak w JSON). Oczywiście SQL i relacyjne bazy danych nie działają w ten sposób, a najbliższe, co mogą uzyskać, to połączenie między „postami” i „komentarzami”, które zwrócą wiele niepotrzebnego powielania danych (z powtarzaniem tych samych informacji o postach w każdym wierszu), co oznacza, że ​​czas przetwarzania jest spędzany zarówno w bazie danych, aby zebrać wszystko razem, jak i na mojej ORM, aby przeanalizować i cofnąć wszystko.

Nawet jeśli poinstruuję mój ORM, aby chętnie ładował komentarze do posta, najlepiej będzie wysłać jedno zapytanie do posta, a następnie drugie zapytanie, aby pobrać wszystkie komentarze, a następnie połączyć je po stronie klienta, co jest również nieefektywny.

Rozumiem, że relacyjne bazy danych są sprawdzoną technologią (do diabła, są starsze ode mnie) i że przez dziesięciolecia przeprowadzono w nich mnóstwo badań i jestem pewien, że istnieje naprawdę dobry powód, dla którego oni (i Standard SQL) są zaprojektowane tak, aby działały tak, jak działają, ale nie jestem pewien, dlaczego opisane powyżej podejście nie jest możliwe. Wydaje mi się, że jest to najprostszy i najbardziej oczywisty sposób na wdrożenie jednej z najbardziej podstawowych relacji między rekordami. Dlaczego relacyjne bazy danych nie oferują czegoś takiego?

(Uwaga: głównie piszę aplikacje internetowe przy użyciu magazynów danych Rails i NoSQL, ale ostatnio wypróbowałem Postgres i bardzo mi się podoba. Nie chcę atakować relacyjnych baz danych, po prostu jestem zakłopotany.)

Nie pytam, jak zoptymalizować aplikację Rails ani jak zhakować ten problem w konkretnej bazie danych. Pytam, dlaczego standard SQL działa w ten sposób, gdy wydaje mi się sprzeczny z intuicją i marnotrawstwem. Musi być jakiś historyczny powód, dla którego oryginalni projektanci SQL chcieli, aby ich wyniki wyglądały tak.

PreciousBodilyFluids
źródło
1
nie wszystkie ormy działają w ten sposób. hibernacja / nhibernacja pozwala określić sprzężenia i może chętnie ładować całe drzewa obiektów z jednego zapytania.
nathan gonzalez
1
Poza tym, chociaż jest to interesujący punkt dyskusji, nie jestem pewien, czy to jest naprawdę odpowiedzialne bez spotkania z facetami z ansi sql
nathan gonzalez
@nathan: Tak, nie wszyscy. Korzystam z Sequel, który pozwala wybrać preferowane podejście do danego zapytania ( docs ), ale nadal zachęca do podejścia z wieloma zapytaniami (chyba ze względów wydajnościowych).
5
Ponieważ RDBMS jest przeznaczony do przechowywania i pobierania zestawów - nie jest przeznaczony do zwracania danych do wyświetlenia. Pomyśl o tym jak o MVC - dlaczego miałby próbować wdrożyć widok kosztem spowolnienia lub utrudnienia korzystania z modelu? RDBMS oferuje korzyści, których bazy danych NoSQL nie mogą (i na odwrót) - jeśli go używasz, ponieważ jest to odpowiednie narzędzie do rozwiązania problemu, nie poprosiłbyś go o zwrócenie danych gotowych do wyświetlenia.
1
Widzą xml
Ian

Odpowiedzi:

42

CJ Date szczegółowo omawia to w rozdziale 7 i załączniku B do SQL i teorii relacyjnej . Masz rację, w teorii relacji nie ma niczego, co zabraniałoby typowi danych atrybutu bycia relacją, o ile jest to ten sam typ relacji w każdym wierszu. Twój przykład się kwalifikuje.

Ale Date mówi, że takie struktury są „zwykle - ale nie zawsze - przeciwwskazane” (tzn. Zły pomysł), ponieważ hierarchie relacji są asymetryczne . Na przykład transformacja ze struktury zagnieżdżonej do znanej „płaskiej” struktury nie zawsze może zostać odwrócona w celu odtworzenia zagnieżdżenia.

Zapytania, ograniczenia i aktualizacje są bardziej złożone, trudniejsze do napisania i trudniejsze do obsługi RDBMS, jeśli zezwolisz na atrybuty o wartości relacyjnej (RVA).

Utrudnia również zasady projektowania baz danych, ponieważ najlepsza hierarchia relacji nie jest tak jasna. Czy powinniśmy zaprojektować relację Dostawców z zagnieżdżoną RVA dla części dostarczanych przez danego Dostawcę? Czy relacja Części z zagnieżdżoną RVA dla dostawców, którzy dostarczają daną Część? Lub przechowuj oba, aby ułatwić uruchamianie różnego rodzaju zapytań?

Jest to ten sam dylemat, który wynika z hierarchicznej bazy danych i modeli baz danych zorientowanych na dokumenty . W końcu złożoność i koszt dostępu do zagnieżdżonych struktur danych skłaniają projektantów do nadmiarowego przechowywania danych w celu łatwiejszego przeszukiwania różnych zapytań. Model relacyjny zniechęca do redundancji, więc RVA mogą działać wbrew celom modelowania relacyjnego.

Z tego, co rozumiem (nie korzystałem z nich), Rel i Dataphor to projekty RDBMS, które obsługują atrybuty cenione w relacjach.


Ponownie skomentuj @dportas:

Typy strukturalne są częścią SQL-99, a Oracle je obsługuje. Ale nie przechowują wielu krotek w zagnieżdżonej tabeli na wiersz tabeli podstawowej. Typowym przykładem jest atrybut „adres”, który wydaje się być pojedynczą kolumną tabeli podstawowej, ale zawiera dodatkowe podkolumny z ulicami, miastami, kodem pocztowym itp.

Tabele zagnieżdżone są również obsługiwane przez Oracle i pozwalają one na wiele krotek na wiersz tabeli podstawowej. Ale nie wiem, czy jest to część standardowego SQL. I pamiętaj o konkluzjach jednego z blogów: „Nigdy nie użyję tabeli zagnieżdżonej w instrukcji CREATE TABLE. Spędzasz cały swój czas ODCZESTUJĄC je, aby znów były użyteczne!”

Bill Karwin
źródło
3
Nie chciałbym właściwie przechowywać jednej relacji w drugiej - byłyby w osobnych tabelach i normalizowane jak zwykle. Pytam tylko, dlaczego tego rodzaju osadzanie wyników nie jest dozwolone w zapytaniach, kiedy wydaje mi się to bardziej intuicyjne niż model łączenia.
PreciousBodilyFluids
Zestawy wyników i tabele są w swoim rodzaju. Date nazywa je odpowiednio relacjami i relvarami (przez analogię 42 jest liczbą całkowitą, podczas gdy zmienna xmoże mieć wartość liczby całkowitej 42). Te same operacje dotyczą relacji i zmian, więc ich struktura musi być kompatybilna.
Bill Karwin
2
Standardowy SQL obsługuje zagnieżdżone tabele. Nazywa się je „typami strukturalnymi”. Oracle to jeden DBMS, który ma tę funkcję.
nvogel,
2
Czy to nie absurdalne argumentować, że aby uniknąć powielania danych, musisz napisać zapytanie w sposób płaski, powielający dane?
Eamon Nerbonne
1
@EamonNerbonne, symetria operacji relacyjnych. Na przykład projekcja. Jeśli wybiorę niektóre atrybuty podrzędne z RVA, w jaki sposób mogę zastosować operację odwrotną względem zestawu wyników w celu odtworzenia oryginalnej hierarchii? Znalazłem stronę 293 książki Daty, która znajduje się w Google Books, więc możesz zobaczyć, co napisał: books.google.com/…
Bill Karwin
15

Niektóre z najwcześniejszych systemów baz danych były oparte na modelu hierarchicznej bazy danych . To reprezentowało dane w strukturze drzewa z rodzicem i dziećmi, podobnie jak sugerujesz tutaj. HDMS zostały w dużej mierze zastąpione przez bazy danych zbudowane na modelu relacyjnym. Głównymi powodami tego było to, że RDBMS mógł modelować relacje „wiele do wielu”, które były trudne dla hierarchicznych baz danych, i że RDBMS mógł łatwo wykonywać zapytania, które nie były częścią pierwotnego projektu, podczas gdy HDBMS ograniczał cię do zapytania ścieżkami określonymi w czasie projektowania.

Wciąż istnieje kilka przykładów hierarchicznych systemów baz danych, w szczególności rejestr systemu Windows i LDAP.

Obszerne omówienie tego tematu jest dostępne w następnym artykule

Steve Weet
źródło
10

Przypuszczam, że twoje pytanie naprawdę koncentruje się na fakcie, że chociaż bazy danych opierają się na solidnej logice i ustawiają podstawy termoretyczne i wykonują bardzo dobrą pracę, przechowując, przetwarzając i wyszukując dane w (2-wymiarowych) zestawach, zapewniając jednocześnie integralność referencyjną, współbieżność i wiele innych rzeczy, nie zapewniają one (dodatkowej) funkcji wysyłania (i odbierania) danych w tak zwanym formacie obiektowym lub hierarchicznym.

Następnie twierdzisz, że „nawet jeśli poinstruuję mój ORM, aby chętnie ładował komentarze do posta, najlepiej będzie wysłać jedno zapytanie do posta, a następnie drugie zapytanie, aby pobrać wszystkie komentarze, a następnie złożyć je razem po stronie klienta, co również jest nieefektywne .

Nie widzę nic nieefektywnego w wysyłaniu 2 zapytań i otrzymywaniu 2 partii wyników z:

--- Query-1-posts
SELECT id, content 
FROM posts
WHERE id = 7


--- Query-2-comments
SELECT * 
FROM comments 
WHERE post_id = 7

Twierdzę, że jest to (prawie) najbardziej wydajny sposób (prawie, ponieważ tak naprawdę nie potrzebujesz posts.idi nie wszystkie kolumny z comments.*)

Jak zauważył Todd w swoim komentarzu, nie należy prosić bazy danych o zwrócenie danych gotowych do wyświetlenia. Jest to zadanie aplikacji. Możesz napisać (jedno lub kilka) zapytań, aby uzyskać wyniki potrzebne dla każdej operacji wyświetlania, aby nie było niepotrzebnego powielania danych przesyłanych przewodem (lub magistralą pamięci) z bazy danych do aplikacji.

Nie mogę tak naprawdę mówić o ORM, ale być może niektórzy z nich mogą wykonać dla nas część tej pracy.

Podobne techniki mogą być stosowane w dostarczaniu danych między serwerem internetowym a klientem. Stosowane są inne techniki (takie jak buforowanie), aby baza danych (lub serwer internetowy lub inny) nie była przeciążona zduplikowanymi żądaniami.

Domyślam się, że standardy, takie jak SQL, są najlepsze, jeśli pozostają wyspecjalizowane w jednym obszarze i nie próbują objąć wszystkich obszarów pola.

Z drugiej strony komitet, który ustala standard SQL, może w przyszłości pomyśleć inaczej i zapewnić standaryzację dla takiej dodatkowej funkcji. Ale nie jest to coś, co można zaprojektować w ciągu jednej nocy.

ypercubeᵀᴹ
źródło
1
Miałem na myśli nieefektywność w tym sensie, że moja aplikacja musiała ponieść obciążenie i opóźnienie dwóch wywołań bazy danych zamiast jednego. Poza tym, czy łączenie nie zwraca tylko danych w formacie gotowym do wyświetlenia? Lub za pomocą widoku bazy danych? Możesz je również ominąć, po prostu uruchamiając więcej małych zapytań i łącząc je ze sobą w aplikacji, jeśli chcesz, ale nadal są przydatnymi narzędziami. Nie sądzę, że to, co proponuję, różni się znacznie od łączenia, poza tym, że jest łatwiejsze w użyciu i bardziej wydajne.
2
@Precious: Nie trzeba zwiększać obciążenia, aby uruchamiać wiele zapytań. Większość baz danych umożliwia przesyłanie wielu zapytań w jednej partii i odbieranie wielu zestawów wyników z jednego zapytania.
Daniel Pryden
@PreciousBodilyFluids - fragment kodu SQL w odpowiedzi ypercube to pojedyncze zapytanie, które zostanie wysłane w pojedynczym wywołaniu bazy danych i zwróci dwa zestawy wyników w jednej odpowiedzi.
Carson63000,
5

Nie jestem w stanie odpowiedzieć prawidłową, argumentowaną odpowiedzią, więc proszę, głosujcie w zapomnienie, jeśli się mylę (ale proszę mnie poprawić, abyśmy mogli nauczyć się czegoś nowego). Myślę, że powodem jest to, że relacyjne bazy danych są skoncentrowane na modelu relacyjnym, który z kolei opiera się na czymś, o czym nic nie wiem, zwanym „logiką pierwszego rzędu”. To, o co możesz zapytać, prawdopodobnie nie pasuje koncepcyjnie do matematyczno-logicznej struktury, na której zbudowane są relacyjne bazy danych. Co więcej, to, o co pytasz, jest zazwyczaj łatwe do rozwiązania za pomocą baz danych graficznych, co daje więcej wskazówek, że to podstawowa konceptualizacja bazy danych jest sprzeczna z tym, co chcesz osiągnąć.

Stefano Borini
źródło
5

Wiem, że przynajmniej SQLServer obsługuje zagnieżdżone zapytania, gdy używasz FOR XML.

SELECT id, content, (SELECT * FROM comments WHERE post_id = posts.id FOR XML PATH('comments'), TYPE) AS comments
FROM posts
WHERE id = 7
FOR XML PATH('posts')

Problemem tutaj nie jest brak obsługi RDBMS, ale brak obsługi zagnieżdżonych tabel w tabelach.

Poza tym, co powstrzymuje cię przed użyciem połączenia wewnętrznego?

SELECT id, content, comments.*
FROM posts inner join comments on comments.post_id = posts.id
WHERE id = 7

Możesz faktycznie spojrzeć na łączenie wewnętrzne jako tabelę zagnieżdżoną, tylko zawartość pierwszych 2 pól jest powtarzana w określonym czasie. Nie martwiłbym się zbytnio wydajnością łączenia, jedyną wolną częścią takiego zapytania jest io od bazy danych do klienta. Będzie to stanowić problem tylko wtedy, gdy zawartość zawiera dużą ilość danych. W takim przypadku sugerowałbym dwa zapytania, jedno z wewnętrznym select id, contenti jedno z wewnętrznym złączeniem i select posts.id, comments.*. Skaluje się nawet w przypadku wielu postów, ponieważ nadal używasz tylko 2 zapytań.

Dorus
źródło
Pytania rozwiązują ten problem. Albo musisz wykonać dwie podróże w obie strony (nieoptymalne), albo musisz zwrócić zbędne dane w pierwszych dwóch kolumnach (również nieoptymalne). Chce optymalnego rozwiązania (moim zdaniem nierealistycznego).
Scott Whitlock,
Wiem, ale nie ma nic do zasysania jako optymalnego rozwiązania. Jedyne, co mogę argumentować, to to, gdzie narzut byłby minimalny i gdzie to zależy. Jeśli chcesz optymalnego rozwiązania, sprawdź i wypróbuj różne podejścia. Nawet rozwiązanie XML może być wolniejsze w zależności od konkretnej sytuacji, a nie znam magazynów danych NoSQL, więc nie mogę powiedzieć, czy ma coś podobnego do for xml.
Dorus,
5

W rzeczywistości Oracle obsługuje to, co chcesz, ale musisz zawrzeć zapytanie podrzędne słowem kluczowym „kursor”. Wyniki są pobierane za pomocą otwartego kursora. Na przykład w Javie komentarze pojawiałyby się jako zestawy wyników. Więcej informacji na ten temat można znaleźć w dokumentacji Oracle na temat „CURSOR Expression”

SELECT id, content, cursor(SELECT * FROM comments WHERE post_id = 7) AS comments
FROM posts
WHERE id = 7
Dilshod Tadjibaev
źródło
1

Niektóre obsługują zagnieżdżanie (hierarchiczne).

Jeśli chcesz jedno zapytanie, możesz mieć jedną tabelę, która sama się odwołuje. Niektóre RDMS obsługują tę koncepcję. Na przykład za pomocą programu SQL Server można użyć typowych wyrażeń tabelowych (CTE) dla zapytania hierarchicznego.

W twoim przypadku posty będą na poziomie 0, a następnie wszystkie komentarze będą na poziomie 1.

Inne opcje to 2 zapytania lub Łączenie z dodatkowymi informacjami dla każdego zwracanego rekordu (o którym wspominali inni).

Przykład hierarchicznej:

https://stackoverflow.com/questions/14274942/sql-server-cte-and-recursion-example

W powyższym linku EmpLevel pokazuje poziom zagnieżdżenia (lub hierarchii).

Jon Raynor
źródło
Nie mogę znaleźć żadnej dokumentacji dotyczącej zestawów wyników podrzędnych w programie SQL Server. Nawet podczas korzystania z CTE. Przez zestaw wyników rozumiem rzędy danych z wystarczająco silnie wpisanymi kolumnami. Czy możesz dodać odniesienia do swojej odpowiedzi?
SandRock
@ SandRock - baza danych odeśle pojedynczy zestaw wyników z zapytania SQL. Identyfikując poziomy w samym zapytaniu, możesz utworzyć hierarchiczny lub zagnieżdżony zestaw wyników, który musiałby zostać przetworzony. Myślę, że obecnie jest to najbliższe, abyśmy mogli uzyskać zwrot zagnieżdżonych danych.
Jon Raynor
0

Przepraszam, nie jestem pewien, czy dokładnie rozumiem twój problem.

W MSSQL możesz po prostu wykonać 2 instrukcje SQL.

SELECT id, content
FROM posts
WHERE id = 7

SELECT * FROM comments WHERE post_id = 7

I zwróci jednocześnie 2 zestawy wyników.

Biff MaGriff
źródło
Osoba zadająca pytanie mówi, że jest to mniej wydajne, ponieważ powoduje dwie podróże w obie strony do bazy danych, a zwykle staramy się minimalizować podróże w obie strony z powodu kosztów ogólnych. Chce odbyć jedną podróż w obie strony i odzyskać oba stoliki.
Scott Whitlock,
Ale będzie to jedna podróż w obie strony. stackoverflow.com/questions/2336362/…
Biff MaGriff
0

RDBM są oparte na teorii i trzymają się teorii. Pozwala to na pewną spójność i sprawdzoną matematycznie niezawodność.

Ponieważ model jest prosty i ponownie oparty na teorii, ułatwia ludziom optymalizację i wiele implementacji. W przeciwieństwie do NoSQL, gdzie każdy robi to nieco inaczej.

W przeszłości podejmowano próby stworzenia hierarchicznych baz danych, ale IIRC (nie wydaje się google go mieć) pojawiły się problemy (przychodzą na myśl cykle i równość).

Adam Gent
źródło
0

Masz konkretną potrzebę. Najlepiej byłoby wyodrębnić dane z bazy danych w wybranym formacie, abyś mógł zrobić z tym, co chcesz.

Niektóre bazy danych nie radzą sobie tak dobrze, ale i tak nie jest niemożliwością ich zbudowanie. Pozostawienie formatowania innym aplikacjom jest obecnym zaleceniem, ale nie uzasadnia, dlaczego nie można tego zrobić.

Jedynym argumentem, który mam przeciwko twojej sugestii, jest możliwość obsługi tego zestawu wyników w sposób „sql”. Byłoby złym pomysłem stworzyć wynik w bazie danych i nie być w stanie z nią pracować ani do pewnego stopnia manipulować. Powiedzmy, że utworzyłem widok zbudowany zgodnie z twoimi sugestiami, jak zawrzeć go w innej instrukcji select? Bazy danych lubią brać wyniki i robić z nimi różne rzeczy. Jak mógłbym dołączyć do innego stołu? Jak porównałbym twój zestaw wyników do innego?

Zatem zaletą RDMS jest elastyczność SQL. Składnia wybierania danych z tabeli jest bardzo zbliżona do listy użytkowników lub innych obiektów w systemie (przynajmniej taki jest cel). Nie jestem pewien, czy warto robić coś zupełnie innego. Nie doprowadzili ich nawet do bardzo skutecznego przetwarzania kodu / kursorów lub BLOBS danych.

JeffO
źródło
0

Moim zdaniem wynika to głównie z SQL i sposobu wykonywania zapytań agregujących - funkcje agregujące i grupowanie są wykonywane na dużych dwuwymiarowych zestawach wierszy w celu zwrócenia wyników. Tak było od samego początku i jest bardzo szybki (większość rozwiązań NoSQL jest dość powolna w agregacji i opiera się na zdenormalizowanym schemacie zamiast złożonych zapytań)

Oczywiście PostgreSQL ma pewne funkcje z obiektowej bazy danych. Zgodnie z tą wiadomością ( wiadomością ) możesz osiągnąć to, czego potrzebujesz, tworząc niestandardową agregację.

Osobiście używam frameworków takich jak Doctrine ORM (PHP), które wykonują agregację po stronie aplikacji i obsługują takie funkcje, jak leniwe ładowanie w celu zwiększenia wydajności.

Daimon
źródło
0

PostgreSQL obsługuje wiele strukturalnych typów danych, w tym tablice i JSON . Za pomocą SQL lub jednego z wbudowanych języków proceduralnych można budować wartości o dowolnej strukturze i zwracać je do aplikacji. Możesz także tworzyć tabele z kolumnami dowolnego typu strukturalnego, ale powinieneś dokładnie rozważyć, czy niepotrzebnie denormalizujesz swój projekt.

Jonathan Rogers
źródło
1
wydaje się, że nie oferuje to nic istotnego w porównaniu z punktami podanymi i wyjaśnionymi w poprzednich 13 odpowiedziach
gnat
Pytanie wyraźnie wymienia JSON, a ta odpowiedź jest jedyną, która wskazuje, że JSON może być zwracany w zapytaniach z co najmniej jednego RDBMS. Wolałbym skomentować to pytanie, twierdząc, że opiera się ono na fałszywej przesłance i dlatego nie mogę oczekiwać żadnej ostatecznej odpowiedzi. Jednak StackExchange nie pozwala mi tego zrobić.
Jonathan Rogers