Perché i database relazionali non supportano la restituzione di informazioni in un formato nidificato?


46

Supponiamo che stia costruendo un blog in cui voglio avere post e commenti. Quindi creo due tabelle, una tabella "post" con una colonna "id" integer autoincrementante e una tabella "commenti" con una chiave esterna "post_id".

Quindi voglio eseguire quella che sarà probabilmente la mia query più comune, che è quella di recuperare un post e tutti i suoi commenti. Essendo piuttosto nuovo nei database relazionali, l'approccio che mi sembra più ovvio è quello di scrivere una query che assomiglierebbe a:

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

Il che mi darebbe l'id e il contenuto del post che desidero, insieme a tutte le righe di commento pertinenti impacchettate ordinatamente in un array (una rappresentazione nidificata come quella che useresti in JSON). Naturalmente, i database SQL e relazionali non funzionano in questo modo, e il più vicino che possono ottenere è unire tra "post" e "commenti" che restituiranno molti inutili duplicati di dati (con le stesse informazioni sui post ripetute in ogni riga), il che significa che il tempo di elaborazione viene speso sia nel database per mettere tutto insieme sia nel mio ORM per analizzare e annullare tutto.

Anche se chiedo al mio ORM di caricare con entusiasmo i commenti del post, il meglio che farà è inviare una query per il post, quindi una seconda query per recuperare tutti i commenti e quindi metterli insieme sul lato client, che è anche inefficiente.

Capisco che i database relazionali sono una tecnologia collaudata (l'inferno, sono più vecchi di me) e che ci sono state molte ricerche condotte nel corso dei decenni e sono sicuro che ci sia davvero una buona ragione per cui loro (e il SQL standard) sono progettati per funzionare nel modo in cui funzionano, ma non sono sicuro del perché l'approccio che ho descritto sopra non sia possibile. Mi sembra il modo più semplice ed ovvio per implementare una delle relazioni più elementari tra i record. Perché i database relazionali non offrono qualcosa del genere?

(Dichiarazione di non responsabilità: scrivo principalmente webapps usando Rails e NoSQL datastore, ma recentemente ho provato Postgres e in realtà mi piace molto. Non intendo attaccare i database relazionali, sono solo perplesso.)

Non sto chiedendo come ottimizzare un'app Rails o come aggirare il problema in un determinato database. Mi chiedo perché lo standard SQL funzioni in questo modo quando mi sembra controintuitivo e dispendioso. Ci deve essere una ragione storica per cui i progettisti originali di SQL volevano che i loro risultati fossero così.


1
non tutti gli orm funzionano in questo modo. hibernate / nhibernate consente di specificare i join e può desiderare di caricare interi alberi di oggetti da una singola query.
Nathan Gonzalez,

1
inoltre, sebbene sia un interessante punto di discussione, non sono sicuro che questo sia veramente responsabile senza avere un incontro con i ragazzi di ansi sql
nathan gonzalez,

@nathan: Sì, non tutti. Ho usato Sequel che ti consente di scegliere quale approccio preferisci per una determinata query ( documenti ), ma incoraggiano comunque l'approccio a più query (per motivi di prestazioni, suppongo).

5
Poiché un RDBMS è progettato per archiviare e recuperare set - non è destinato a restituire dati per la visualizzazione. Pensalo come MVC: perché dovrebbe provare a implementare la vista a costo di rendere il modello più lento o più difficile da usare? RDBMS offre vantaggi che i database NoSQL non possono (e viceversa) - se lo stai usando perché è lo strumento giusto per risolvere il tuo problema, non gli chiederesti di restituire i dati pronti per la visualizzazione.

1
Fanno vedere per XML
Ian

Risposte:


42

CJ Date approfondisce questo aspetto nel capitolo 7 e nell'appendice B di SQL e teoria relazionale . Hai ragione, non c'è nulla nella teoria relazionale che proibisce al tipo di dati di un attributo di essere una relazione stessa, purché sia ​​lo stesso tipo di relazione su ogni riga. Il tuo esempio sarebbe valido.

Ma Date afferma che strutture come questa sono "di solito - ma non invariabilmente - controindicate" (cioè una cattiva idea) perché le gerarchie di relazioni sono asimmetriche . Ad esempio, una trasformazione da struttura nidificata a struttura familiare "piatta" non può sempre essere invertita per ricreare l'annidamento.

Le query, i vincoli e gli aggiornamenti sono più complessi, più difficili da scrivere e più difficili da supportare per RDBMS se si consentono gli attributi a valore di relazione (RVA).

Confonde anche i principi di progettazione del database, perché la migliore gerarchia delle relazioni non è così chiara. Dovremmo progettare una relazione di fornitori con un RVA nidificato per le parti fornite da un determinato fornitore? O una relazione di parti con un RVA nidificato per i fornitori che forniscono una determinata parte? O archiviare entrambi, per semplificare l'esecuzione di diversi tipi di query?

Questo è lo stesso dilemma che risulta dal database gerarchico e dai modelli di database orientati al documento . Alla fine, la complessità e il costo dell'accesso alle strutture di dati nidificati spingono i progettisti a archiviare i dati in modo ridondante per una ricerca più semplice da parte di query diverse. Il modello relazionale scoraggia la ridondanza, quindi gli RVA possono lavorare contro gli obiettivi della modellazione relazionale.

Da quello che ho capito (non li ho usati), Rel e Dataphor sono progetti RDBMS che supportano attributi con valore di relazione.


Per commentare da @dportas:

I tipi strutturati fanno parte di SQL-99 e Oracle li supporta. Ma non memorizzano più tuple nella tabella nidificata per riga della tabella di base. L'esempio comune è un attributo "indirizzo" che sembra essere una singola colonna della tabella di base, ma ha ulteriori sotto-colonne per via, città, codice postale, ecc.

Le tabelle nidificate sono supportate anche da Oracle e queste consentono più tuple per riga della tabella di base. Ma non sono consapevole che questo fa parte dell'SQL standard. E tieni presente la conclusione di un blog: "Non userò mai una tabella nidificata in un'istruzione CREATE TABLE. Trascorri tutto il tuo tempo a NON-NIDERCI per renderli di nuovo utili!"


3
In realtà non vorrei archiviare una relazione all'interno di un'altra: sarebbero in tabelle separate e denormalizzate come al solito. Sto solo chiedendo perché questo tipo di incorporamento dei risultati non è consentito nelle query, quando mi sembra più intuitivo rispetto al modello di join.
PreciousBodilyFluids,

I set di risultati e le tabelle sono di un tipo. Date li chiama rispettivamente relazioni e relvars (per analogia, 42 è un numero intero, mentre una variabile xpuò avere il valore dell'intero 42). Le stesse operazioni si applicano a relazioni e relazioni, quindi la loro struttura deve essere compatibile.
Bill Karwin,

2
SQL standard supporta tabelle nidificate. Sono chiamati "tipi strutturati". Oracle è un DBMS con questa funzionalità.
nvogel,

2
Non è affatto assurdo sostenere che per evitare la duplicazione dei dati, è necessario scrivere la query in modo semplice e duplicando i dati?
Eamon Nerbonne,

1
@EamonNerbonne, simmetria delle operazioni relazionali. Ad esempio, proiezione. Se seleziono alcuni attributi secondari da un RVA, come posso applicare un'operazione inversa rispetto al set di risultati per riprodurre la gerarchia originale? Ho trovato la pagina 293 del libro di Date su Google Libri, quindi puoi vedere cosa ha scritto: books.google.com/…
Bill Karwin,

15

Alcuni dei primi sistemi di database erano basati sul modello di database gerarchico . Ciò rappresentava i dati in un albero come una struttura con genitori e figli, proprio come stai suggerendo qui. Gli HDMS sono stati in gran parte sostituiti da database basati sul modello relazionale. Le ragioni principali di ciò erano che RDBMS poteva modellare relazioni "molte a molte" che erano difficili per i database gerarchici e che RDBMS poteva facilmente eseguire query che non facevano parte del progetto originale mentre HDBMS ti costringeva a eseguire query attraverso percorsi specificati in fase di progettazione.

Esistono ancora alcuni esempi di sistemi di database gerarchici in natura, in particolare il registro di Windows e LDAP.

L'ampia copertura di questo argomento è disponibile nel seguente articolo


10

Suppongo che la tua domanda sia realmente centrata sul fatto che mentre i database si basano su una solida logica e stabiliscono basi teoriche e fanno un ottimo lavoro archiviando, manipolando e recuperando i dati in insiemi (bidimensionali) garantendo integrità referenziale, concorrenza e molte altre cose, non forniscono una funzione (aggiuntiva) di invio (e ricezione) di dati in quello che si potrebbe chiamare formato orientato agli oggetti o formato gerarchico.

Quindi affermi che "anche se chiedo al mio ORM di caricare con entusiasmo i commenti del post, il meglio che farà è inviare una query per il post, quindi una seconda query per recuperare tutti i commenti e poi metterli insieme lato client, che è anche inefficiente " .

Non vedo nulla di inefficiente nell'invio di 2 query e nella ricezione di 2 lotti di risultati con:

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


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

Direi che è (quasi) il modo più efficiente (quasi, poiché non hai davvero bisogno delle posts.idcolonne e non tutte comments.*)

Come ha sottolineato Todd nel suo commento, non si dovrebbe chiedere al database di restituire i dati pronti per la visualizzazione. È compito dell'applicazione farlo. È possibile scrivere (una o alcune) query per ottenere i risultati necessari per ogni operazione di visualizzazione in modo che non vi sia alcuna duplicazione non necessaria nei dati inviati tramite il filo (o il bus di memoria) dal db all'applicazione.

Non posso davvero parlare di ORM ma forse alcuni di loro possono fare parte di questo lavoro per noi.

Tecniche simili possono essere utilizzate nella consegna di dati tra un server Web e un client. Altre tecniche (come la memorizzazione nella cache) vengono utilizzate in modo che il database (o il Web o altri server) non sia sovraccaricato da richieste duplicate.

La mia ipotesi è che gli standard, come SQL, siano i migliori se rimangono specializzati in un'area e non cercano di coprire tutte le aree di un campo.

D'altra parte, il comitato che imposta lo standard SQL potrebbe benissimo pensare diversamente in futuro e fornire standardizzazione per tale funzionalità aggiuntiva. Ma non è qualcosa che può essere progettato in una notte.


1
Intendevo inefficiente, nel senso che la mia applicazione deve sostenere il sovraccarico e il ritardo di due chiamate al database invece di una sola. A parte questo, non sta facendo un join anche solo la restituzione dei dati in un formato pronto per la visualizzazione? O usando una vista del database? Puoi anche eliminarli semplicemente eseguendo più piccole query e ricucendole insieme nella tua app, se lo desideri, ma sono comunque strumenti utili. Non credo che ciò che sto proponendo sia significativamente diverso da un join, oltre ad essere più facile da usare e più performante.

2
@Precious: non è necessario aumentare le spese generali per l'esecuzione di più query. La maggior parte dei database consente di inviare più query in un singolo batch e di ricevere più set di risultati da una singola query.
Daniel Pryden,

@PreciousBodilyFluids: lo snippet SQL nella risposta di ypercube è una singola query che verrebbe inviata in una singola chiamata al database e restituire due set di risultati in un'unica risposta.
Carson63000,

5

Non sono in grado di rispondere con una risposta corretta e argomentata, quindi sentiti libero di ridimensionarmi nell'oblio se sbaglio (ma per favore correggimi in modo da poter imparare qualcosa di nuovo). Penso che il motivo sia che i database relazionali sono centrati sul modello relazionale, che a sua volta si basa su qualcosa di cui non so nulla di ciò che si chiama "logica del primo ordine". Ciò che potresti chiedere probabilmente non rientra concettualmente nei database relazionali del framework matematico / logico. Inoltre, ciò che chiedi viene generalmente risolto facilmente dai database dei grafi, dando più suggerimenti sul fatto che è la concettualizzazione sottostante del database che è in conflitto con ciò che vuoi ottenere.


5

So che almeno SQL Server supporta le query nidificate quando si utilizza 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')

Il problema qui non è la mancanza di supporto da parte di RDBMS, ma la mancanza di supporto delle tabelle nidificate nelle tabelle.

Inoltre, cosa ti impedisce di utilizzare un join interno?

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

È possibile esaminare il join interno come una tabella nidificata, solo il contenuto dei primi 2 campi viene ripetuto una volta. Non mi preoccuperei molto delle prestazioni del join, l'unica parte lenta in una query come questa è l'io dal database al client. Questo sarà un problema solo quando il contenuto contiene una grande quantità di dati. In tal caso, suggerirei due query, una con select id, contente una con un join interno e select posts.id, comments.*. Questo si ridimensiona anche con più post, poiché utilizzeresti comunque solo 2 query.


Le domande si rivolgono a questo. O devi fare due round trip (non ottimali) o devi restituire dati ridondanti nelle prime due colonne (anche non ottimali). Vuole la soluzione ottimale (non irrealistica secondo me).
Scott Whitlock,

Lo so, ma non c'è niente di buono come soluzione ottimale. L'unica cosa di cui posso discutere è dove il sovraccarico sarebbe minimo e da dove dipende. Se vuoi la soluzione ottimale, confronta e prova approcci diversi. Anche la soluzione XML potrebbe essere più lenta a seconda della situazione specifica, e non ho familiarità con i datastore NoSQL, quindi non posso dire se ha qualcosa di simile for xml.
Dorus,

5

In realtà Oracle supporta ciò che desideri ma devi racchiudere la sottoquery con la parola chiave "cursore". I risultati vengono recuperati tramite il cursore aperto. In Java, ad esempio, i commenti verrebbero visualizzati come set di risultati. Ulteriori informazioni al riguardo consultare la documentazione di Oracle su "CURSOR Expression"

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

1

Alcuni supportano l'annidamento (gerarchico).

Se si desidera una query, è possibile disporre di una tabella che si auto-referenzia. Alcuni RDMS supportano questo concetto. Ad esempio, con SQL Server è possibile utilizzare Common Table Expressions (CTE) per una query gerarchica.

Nel tuo caso i messaggi sarebbero al livello 0 e quindi tutti i commenti sarebbero al livello 1.

Le altre opzioni sono 2 query o un Join con alcune informazioni extra per ogni record restituito (che altri hanno menzionato).

Esempio di gerarchico:

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

Nel link sopra, EmpLevel mostra il livello di annidamento (o gerarchia).


Non riesco a trovare alcuna documentazione sui sotto-risultati in SQL Server. Anche quando si utilizza un CTE. Per gruppo di risultati intendo file di dati con colonne abbastanza fortemente tipizzate. Puoi aggiungere riferimenti alla tua risposta?
SandRock

@SandRock: un database restituirà un singolo set di risultati da una query SQL. Identificando i livelli nella query stessa, è possibile creare un set di risultati gerarchico o nidificato che dovrebbe essere elaborato. Penso che attualmente sia il più vicino che stiamo per ottenere per restituire i dati che sono nidificati.
Jon Raynor,

0

Mi dispiace, non sono sicuro di aver capito esattamente il tuo problema.

In MSSQL puoi semplicemente eseguire 2 istruzioni SQL.

SELECT id, content
FROM posts
WHERE id = 7

SELECT * FROM comments WHERE post_id = 7

E restituirà i tuoi 2 set di risultati contemporaneamente.


La persona che sta ponendo la domanda afferma che ciò è meno efficiente perché comporta due viaggi di andata e ritorno nel database e di solito proviamo a minimizzare i viaggi di andata e ritorno a causa del sovraccarico. Vuole fare un viaggio di andata e ritorno e riavere entrambi i tavoli.
Scott Whitlock,

Ma sarà un viaggio di andata e ritorno. stackoverflow.com/questions/2336362/...
Biff MaGriff

0

Le RDBM sono basate sulla teoria e si attengono alla teoria. Ciò consente una buona consistenza e affidabilità comprovata matematicamente.

Poiché il modello è semplice e di nuovo basato sulla teoria, è facile per le persone effettuare l'ottimizzazione e molte implementazioni. Questo è diverso da NoSQL in cui tutti lo fanno leggermente diverso.

Ci sono stati tentativi in ​​passato di creare database gerarchici ma IIRC (non riesco a cercarlo su Google) ci sono stati problemi (mi vengono in mente cicli e uguaglianza).


0

Hai un bisogno specifico. Sarebbe preferibile estrarre i dati da un database nel formato desiderato, in modo da poter fare quello che vuoi.

Alcune cose che i database non fanno altrettanto bene, ma non è impossibile costruirle per farlo comunque. Lasciare la formulazione ad altre applicazioni è la raccomandazione attuale, ma non giustifica il motivo per cui non può essere fatto.

L'unico argomento che ho contro il tuo suggerimento è di essere in grado di gestire questo set di risultati in modo "sql". Sarebbe una cattiva idea creare un risultato nel database e non poterlo lavorare o manipolarlo in una certa misura. Supponiamo che io abbia creato una vista costruita come suggerisci tu, come posso includerla in un'altra istruzione select? Ai database piace prendere risultati e fare cose con loro. Come potrei unirmi ad un altro tavolo? Come confronterei il tuo set di risultati con un altro?

Quindi il vantaggio di RDMS è la flessibilità di sql. La sintassi per selezionare i dati da una tabella è abbastanza vicina a un elenco di utenti o altri oggetti nel sistema (almeno questo è l'obiettivo.). Non sono sicuro che abbia senso fare qualcosa di completamente diverso. Non li hanno nemmeno portati al punto di gestire in modo molto efficiente codici procedurali / cursori o BLOBS di dati.


0

A mio avviso, è principalmente a causa di SQL e del modo in cui vengono eseguite le query aggregate: le funzioni aggregate e il raggruppamento vengono eseguiti su set di righe bidimensionali di grandi dimensioni per restituire risultati. È così com'è stato dall'inizio ed è molto veloce (la maggior parte delle soluzioni NoSQL sono piuttosto lente con l'aggregazione e si basano su schemi denormalizzati anziché su query complesse)

Ovviamente PostgreSQL ha alcune funzionalità dal database orientato agli oggetti. Secondo questa mail ( messaggio ) puoi ottenere ciò di cui hai bisogno creando un aggregato personalizzato.

Personalmente sto usando framework come Doctrine ORM (PHP) che eseguono l'aggregazione lato applicazione e supportano funzionalità come il caricamento lento per aumentare le prestazioni.


0

PostgreSQL supporta una varietà di tipi di dati strutturati, tra cui Array e JSON . Utilizzando SQL o uno dei linguaggi procedurali incorporati, è possibile creare valori con una struttura arbitrariamente complessa e restituirli all'applicazione. Puoi anche creare tabelle con colonne di qualsiasi tipo strutturato, anche se dovresti considerare attentamente se stai denormalizzando inutilmente il tuo design.


1
questo non sembra offrire nulla di sostanziale rispetto ai punti formulati e spiegati nelle precedenti 13 risposte
moscerino del

La domanda menziona specificamente JSON e questa risposta è l'unica a sottolineare che JSON può essere restituito nelle query da almeno un RDBMS. Avrei preferito commentare la domanda per dire che si basa su una premessa errata e quindi non posso aspettarmi una risposta definitiva. Tuttavia, StackExchange non mi consente di farlo.
Jonathan Rogers
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.