Pourquoi les bases de données relationnelles ne permettent-elles pas de renvoyer des informations dans un format imbriqué?


46

Supposons que je construise un blog pour lequel je souhaite avoir des posts et des commentaires. Je crée donc deux tables, une table 'posts' avec une colonne 'id' entier auto-incrémentée et une table 'commentaires' avec une clé étrangère 'post_id'.

Ensuite, je veux exécuter ce qui sera probablement ma requête la plus courante, qui consiste à récupérer un message et tous ses commentaires. Étant assez nouveau dans les bases de données relationnelles, l’approche qui me semble la plus évidente consiste à écrire une requête qui ressemblerait à quelque chose comme:

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

Ce qui me donnerait l'identifiant et le contenu de la publication que je veux, ainsi que toutes les lignes de commentaires pertinentes, parfaitement regroupées dans un tableau (une représentation imbriquée comme celle que vous utiliseriez dans JSON). Bien sûr, SQL et les bases de données relationnelles ne fonctionnent pas ainsi, et le plus proche possible est de faire une jointure entre 'posts' et 'comments' qui renverra beaucoup de duplications inutiles de données (avec les mêmes informations postées répétées). dans chaque ligne), ce qui signifie que le temps de traitement est consacré à la fois à la base de données pour tout rassembler et à mon ORM pour analyser et annuler le tout.

Même si j'ordonne à mon ORM de charger avec empressement les commentaires de l'article, le mieux consiste à envoyer une requête pour l'article, puis une seconde requête pour extraire tous les commentaires, puis de les assembler côté client, qui est également inefficace.

Je comprends que les bases de données relationnelles sont une technologie éprouvée (diable, elles sont plus vieilles que moi), et qu’elles ont fait l’objet de nombreuses recherches au cours des décennies, et je suis sûr qu’il ya une très bonne raison pour laquelle elles (et le SQL) sont conçus pour fonctionner comme ils le font, mais je ne sais pas pourquoi la démarche que je viens de décrire est impossible. Cela me semble être le moyen le plus simple et le plus évident d'implémenter l'une des relations les plus fondamentales entre les enregistrements. Pourquoi les bases de données relationnelles n'offrent-elles pas quelque chose comme ça?

(Avertissement: j'écris principalement des applications Web à l'aide de bases de données Rails et NoSQL, mais j'ai récemment essayé Postgres et je l'aime beaucoup. Je ne veux pas attaquer les bases de données relationnelles, je suis perplexe.)

Je ne vous demande pas comment optimiser une application Rails ou comment résoudre ce problème dans une base de données particulière. Je demande pourquoi le standard SQL fonctionne de cette façon quand il me semble contre-intuitif et inutile. Il doit exister une raison historique pour laquelle les concepteurs originaux de SQL voulaient que leurs résultats ressemblent à ceci.


1
tous les orms ne fonctionnent pas de cette façon. hibernate / nhibernate permet de spécifier des jointures et peut charger des arborescences d’objets entières à partir d’une seule requête.
nathan gonzalez

1
De plus, bien que ce soit un point de discussion intéressant, je ne suis pas sûr que ce soit vraiment responsable sans une rencontre avec les gars de ansi sql
nathan gonzalez

@ Jonathan: Ouais, pas tous. J'ai utilisé Sequel qui vous permet de choisir l'approche que vous préférez pour une requête donnée ( docs ), mais ils continuent d'encourager l'approche des requêtes multiples (pour des raisons de performances, je suppose).

5
Parce qu'un SGBDR est conçu pour stocker et récupérer des ensembles, il n'est pas destiné à renvoyer des données à afficher. Pensez-y comme à MVC. Pourquoi voudrait-il implémenter la vue au prix de ralentir ou rendre plus difficile l'utilisation du modèle? Les SGBDR offrent des avantages que les bases de données NoSQL ne peuvent pas (et inversement) - si vous l'utilisez parce que c'est l'outil idéal pour résoudre votre problème, vous ne lui demanderez pas de renvoyer des données prêtes à être affichées.

1
Ils voient pour xml
Ian

Réponses:


42

CJ Date va plus en détail à ce sujet au chapitre 7 et à l’annexe B de SQL et de la théorie relationnelle . Vous avez raison, rien dans la théorie relationnelle n'interdit au type de données d'un attribut d'être une relation en tant que telle, à condition qu'il s'agisse du même type de relation sur chaque ligne. Votre exemple serait admissible.

Mais Date indique que de telles structures sont "généralement - mais pas invariablement - contre-indiquées" (c’est-à-dire une mauvaise idée) car les hiérarchies des relations sont asymétriques . Par exemple, une transformation d'une structure imbriquée en une structure "plate" familière ne peut pas toujours être inversée pour recréer l'imbrication.

Les requêtes, les contraintes et les mises à jour sont plus complexes, plus difficiles à écrire et à prendre en charge par le SGBDR si vous autorisez des attributs à valeur relationnelle (RVA).

Cela brouille également les principes de conception des bases de données, car la meilleure hiérarchie des relations n’est pas aussi claire. Devrions-nous concevoir une relation de fournisseurs avec une RVA imbriquée pour les pièces fournies par un fournisseur donné? Ou une relation de pièces avec une RVA imbriquée pour les fournisseurs qui fournissent une pièce donnée? Ou bien stocker les deux pour faciliter l’exécution de différents types de requêtes?

C'est le même dilemme qui découle des modèles de base de données hiérarchique et de base de données orientée document . Finalement, la complexité et le coût d’accès aux structures de données imbriquées incitent les concepteurs à stocker les données de manière redondante pour faciliter la recherche par différentes requêtes. Le modèle relationnel déconseillant la redondance, les RVA peuvent donc aller à l'encontre des objectifs de la modélisation relationnelle.

D'après ce que j'ai compris (je ne les ai pas utilisés), Rel et Dataphor sont des projets de SGBDR qui prennent en charge des attributs à valeur relationnelle.


Commentaire de @dportas:

Les types structurés font partie de SQL-99 et Oracle les prend en charge. Mais ils ne stockent pas plusieurs tuples dans la table imbriquée par ligne de la table de base. L'exemple courant est un attribut "adresse" qui semble être une colonne unique de la table de base, mais comporte d'autres sous-colonnes pour la rue, la ville, le code postal, etc.

Les tables imbriquées sont également prises en charge par Oracle. Celles-ci permettent plusieurs nuplets par ligne de la table de base. Mais je ne suis pas au courant que cela fait partie du SQL standard. Et gardez à l'esprit la conclusion d'un blog: "Je n'utiliserai jamais une table imbriquée dans une instruction CREATE TABLE. Vous passez tout votre temps à les NESTIVER pour les rendre utiles à nouveau!"


3
Je ne voudrais pas réellement stocker une relation dans une autre - elles seraient dans des tables séparées et dénormalisées comme d'habitude. Je demande simplement pourquoi ce type d'intégration de résultats n'est pas autorisé dans les requêtes, alors que cela me semble plus intuitif que le modèle de jointure.
PreciousBodilyFluids

Les ensembles de résultats et les tableaux sont d'un genre. Date les appelle respectivement relations et relvars (par analogie, 42 est un entier, alors qu'une variable xpeut avoir la valeur de 42). Les mêmes opérations s'appliquent aux relations et aux commentaires, de sorte que leur structure doit être compatible.
Bill Karwin

2
Le SQL standard prend en charge les tables imbriquées. Ils sont appelés "types structurés". Oracle est un SGBD doté de cette fonctionnalité.
nvogel

2
N’est-il pas un peu absurde de prétendre que pour éviter la duplication de données, vous devez rédiger votre requête de manière plate en dupliquant les données?
Eamon Nerbonne

1
@EamonNerbonne, symétrie des opérations relationnelles. Par exemple, projection. Si je sélectionne des sous-attributs dans une RVA, comment puis-je appliquer une opération inverse sur le jeu de résultats pour reproduire la hiérarchie d'origine? J'ai trouvé la page 293 du livre de Date sur Google Livres, vous pouvez donc voir ce qu'il a écrit: books.google.com/…
Bill Karwin

15

Certains des systèmes de base de données les plus anciens étaient basés sur le modèle de base de données hiérarchique . Cela représentait des données dans une arborescence ressemblant à une structure avec parent et enfants, un peu comme vous le suggérez ici. Les HDMS ont été largement remplacés par des bases de données construites sur le modèle relationnel. Les principales raisons en étaient que le SGBDR pouvait modéliser des relations "nombreuses à multiples" difficiles pour les bases de données hiérarchiques et qu'il pouvait facilement exécuter des requêtes ne faisant pas partie de la conception d'origine alors que HDBMS vous obligeait à interroger des chemins spécifiés au moment de la conception.

Il existe encore quelques exemples de systèmes de bases de données hiérarchiques, notamment le registre Windows et LDAP.

Une vaste couverture de ce sujet est disponible dans l' article suivant


10

Je suppose que votre question porte en réalité sur le fait que, même si les bases de données reposent sur une logique et des bases théoriques solides, elles stockent, manipulent et extraient les données dans des ensembles (à deux dimensions) tout en garantissant l’intégrité référentielle, la simultanéité et bien d’autres choses encore, ils ne fournissent pas une fonctionnalité (supplémentaire) d’envoi (et de réception) de données dans ce que l’on pourrait appeler un format orienté objet ou un format hiérarchique.

Ensuite, vous déclarez que "même si je demande à mon ORM de charger avec empressement les commentaires de l'article, le mieux qu'il puisse faire est d'envoyer une requête pour l'article, puis une deuxième requête pour extraire tous les commentaires, puis les assembler côté client, ce qui est également inefficace " .

Je ne vois rien d’inefficace dans l’envoi de 2 requêtes et la réception de 2 lots de résultats avec:

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


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

Je dirais que c'est (presque) le moyen le plus efficace (presque, car vous n'avez pas vraiment besoin de la posts.idcolonne et pas de toutes les colonnes comments.*)

Comme Todd l'a souligné dans son commentaire, vous ne devriez pas demander à la base de données de renvoyer des données prêtes à être affichées. C'est le travail de l'application de le faire. Vous pouvez écrire (une ou plusieurs) requêtes pour obtenir les résultats dont vous avez besoin pour chaque opération d'affichage afin d'éviter toute duplication inutile des données envoyées sur le réseau (ou le bus de mémoire) de la base de données vers l'application.

Je ne peux pas vraiment parler des ORM mais peut-être que certains d’entre eux peuvent faire partie de ce travail pour nous.

Des techniques similaires peuvent être utilisées pour la transmission de données entre un serveur Web et un client. D'autres techniques (comme la mise en cache) sont utilisées pour que la base de données (ou le serveur Web ou un autre serveur) ne soit pas surchargée de demandes en double.

Je suppose que les normes, comme SQL, sont préférables si elles restent spécialisées dans un domaine et n'essayent pas de couvrir tous les domaines d'un domaine.

D'autre part, le comité qui établit la norme SQL pourrait bien penser le contraire à l'avenir et fournir la normalisation d'une telle fonctionnalité supplémentaire. Mais ce n'est pas quelque chose qui peut être conçu en une nuit.


1
Je voulais dire inefficace en ce sens que mon application doit supporter le temps système et le délai de deux appels de base de données au lieu d'un seul. En dehors de cela, une jointure ne renvoie-t-elle pas simplement au retour de données dans un format prêt à être affiché? Ou en utilisant une vue de base de données? Vous pouvez également les résoudre en exécutant simplement plus de petites requêtes et en les assemblant dans votre application, si vous le souhaitez, mais ce sont toujours des outils utiles. Je ne pense pas que ce que je propose soit très différent d'une jointure, à part d'être plus facile à utiliser et plus performant.

2
@ Précieux: Il n'est pas nécessaire d'augmenter le temps système pour exécuter plusieurs requêtes. La plupart des bases de données vous permettent de soumettre plusieurs requêtes en un seul lot et de recevoir plusieurs ensembles de résultats à partir d'une seule requête.
Daniel Pryden

@PreciousBodilyFluids - L'extrait de code SQL dans la réponse de ypercube est une requête unique qui serait envoyée dans un seul appel à la base de données et renverrait deux ensembles de résultats dans une seule réponse.
Carson63000

5

Je ne suis pas en mesure de répondre avec une réponse correcte et argumentée, alors n'hésitez pas à me laisser tomber dans l'oubli si je me trompe (mais corrigez-moi afin que nous puissions apprendre quelque chose de nouveau). Je pense que la raison en est que les bases de données relationnelles sont centrées sur le modèle relationnel, qui à son tour est basé sur quelque chose que je ne connais pas, appelé "logique du premier ordre". Ce que vous demandez peut-être ne correspond probablement pas au cadre mathématique / logique sur lequel reposent les bases de données relationnelles. De plus, ce que vous demandez est généralement résolu facilement par les bases de données graphiques, ce qui donne plus d'indications sur le fait que c'est la conceptualisation sous-jacente de la base de données qui est en conflit avec ce que vous souhaitez réaliser.


5

Je sais au moins que SQLServer prend en charge les requêtes imbriquées lorsque vous utilisez 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')

Le problème ici n'est pas le manque de support du SGBDR, mais le manque de support des tables imbriquées dans les tables.

De plus, qu'est-ce qui vous empêche d'utiliser une jointure interne?

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

Vous pouvez voir la jointure interne comme une table imbriquée, seul le contenu des 2 premiers champs est répété une fois. Je ne m'inquiéterais pas beaucoup des performances de la jointure, le seul élément lent d'une requête comme celle-ci est l'io de la base de données au client. Cela ne posera problème que lorsque le contenu contient une grande quantité de données. Dans ce cas, je suggérerais deux requêtes, une avec select id, contentet une avec une jointure interne et select posts.id, comments.*. Cela évolue même avec plusieurs publications, car vous n'utiliseriez toujours que 2 requêtes.


Les questions abordent cela. Soit vous devez faire deux allers-retours (non optimal) ou vous devez renvoyer des données redondantes dans les deux premières colonnes (également non optimale). Il veut la solution optimale (pas irréaliste à mon avis).
Scott Whitlock

Je sais, mais il n'y a pas de problème de succion comme solution optimale. La seule chose que je peux dire, c’est que les frais généraux seraient minimes et que cela dépendait. Si vous voulez la solution optimale, comparez et essayez différentes approches. Même la solution XML peut être plus lente en fonction de la situation, et les bases de données NoSQL ne me sont pas familières. Je ne peux donc pas dire si elle a quelque chose de similaire for xml.
Dorus

5

En réalité, Oracle prend en charge ce que vous voulez, mais vous devez envelopper la sous-requête avec le mot clé "cursor". Les résultats sont récupérés via le curseur ouvert. En Java, par exemple, les commentaires apparaissent sous forme de jeux de résultats. Pour plus d'informations, consultez la documentation d'Oracle sur "CURSOR Expression".

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

1

Certains supportent l'imbrication (hiérarchique).

Si vous voulez une requête, vous pouvez avoir une table qui se référence elle-même. Certains RDMS supportent ce concept. Par exemple, avec SQL Server, vous pouvez utiliser des expressions de table communes (CTE) pour une requête hiérarchique.

Dans votre cas, les messages seraient au niveau 0 et tous les commentaires seraient au niveau 1.

Les autres options sont 2 requêtes ou une jointure avec des informations supplémentaires pour chaque enregistrement renvoyé (mentionné par d'autres).

Exemple de hiérarchie:

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

Dans le lien ci-dessus, EmpLevel indique le niveau d'imbrication (ou de hiérarchie).


Je ne trouve aucune documentation sur les sous-ensembles de résultats dans SQL Server. Même en utilisant un CTE. Par résultats, je veux dire des lignes de données avec juste assez de colonnes fortement typées. Pouvez-vous ajouter des références à votre réponse?
SandRock

@ SandRock - Une base de données renverra un seul résultat défini à partir d'une requête SQL. En identifiant les niveaux dans la requête elle-même, vous pouvez créer un jeu de résultats hiérarchique ou imbriqué qui doit être traité. Je pense qu’à l’heure actuelle, c’est au plus près de notre objectif de renvoyer des données imbriquées.
Jon Raynor

0

Je suis désolé, je ne suis pas sûr de bien comprendre votre problème.

Dans MSSQL, vous pouvez simplement exécuter 2 instructions SQL.

SELECT id, content
FROM posts
WHERE id = 7

SELECT * FROM comments WHERE post_id = 7

Et il retournera vos 2 jeux de résultats simultanément.


La personne qui pose la question dit que cela est moins efficace car cela entraîne deux allers-retours vers la base de données et nous essayons généralement de minimiser les allers-retours à cause des frais généraux. Il veut faire un aller-retour et récupérer les deux tables.
Scott Whitlock


0

Les RDBM sont basés sur la théorie et ils s'en tiennent à la théorie. Cela permet une certaine cohérence et une fiabilité mathématiquement prouvée.

Parce que le modèle est simple et basé à nouveau sur la théorie, il est facile pour les personnes d’optimiser et de nombreuses implémentations. Ceci est différent de NoSQL où tout le monde le fait légèrement différemment.

Il y a eu des tentatives dans le passé pour créer des bases de données hiérarchiques, mais l'IIRC (semble ne pas vouloir le chercher sur Google), mais des problèmes se sont posés (des cycles et l'égalité nous viennent à l'esprit).


0

Vous avez un besoin spécifique. Il serait préférable d'extraire les données d'une base de données dans le format de votre choix afin de pouvoir en faire ce que vous voulez.

Certaines choses que les bases de données ne font pas aussi bien, mais il n'est pas impossible de les construire pour le faire de toute façon. Laisser la formation à d’autres applications est la recommandation actuelle, mais ne justifie pas pourquoi cela ne peut pas être fait.

Le seul argument que j'ai contre votre suggestion est de pouvoir traiter ce résultat avec "sql". Ce serait une mauvaise idée de créer un résultat dans la base de données et de ne pas pouvoir l'utiliser ou la manipuler dans une certaine mesure. Supposons que j'ai créé une vue construite à la manière que vous suggérez, comment l'inclure dans une autre instruction select? Les bases de données aiment prendre des résultats et faire des choses avec eux. Comment pourrais-je le joindre à une autre table? Comment pourrais-je comparer votre jeu de résultats à un autre?

Alors, l'avantage de RDMS est la flexibilité de SQL. La syntaxe de sélection des données d'une table est assez proche de celle d'une liste d'utilisateurs ou d'autres objets du système (c'est du moins l'objectif). Pas sûr qu'il soit utile de faire quelque chose de complètement différent. Ils ne les ont même pas amenés au point de gérer très efficacement le code de procédure / les curseurs ou les BLOBS de données.


0

À mon avis, c'est principalement à cause de SQL et de la manière dont les requêtes d'agrégat sont effectuées - les fonctions d'agrégat et le regroupement sont exécutés sur de grands ensembles de lignes bidimensionnels pour renvoyer les résultats. C'est comme ça depuis le début et c'est très rapide (la plupart des solutions NoSQL sont assez lentes avec l'agrégation et reposent sur un schéma dénormalisé au lieu de requêtes complexes)

Bien sûr, PostgreSQL a quelques fonctionnalités de base de données orientée objet. Selon ce courrier ( message ), vous pouvez obtenir ce dont vous avez besoin en créant un agrégat personnalisé.

Personnellement, j'utilise des frameworks tels que Doctrine ORM (PHP) qui effectuent l'agrégation côté application et prennent en charge des fonctionnalités telles que le chargement différé pour augmenter les performances.


0

PostgreSQL prend en charge une variété de types de données structurés, notamment les tableaux et JSON . À l'aide de SQL ou de l'un des langages procéduraux intégrés, vous pouvez créer des valeurs avec une structure arbitrairement complexe et les renvoyer à votre application. Vous pouvez également créer des tableaux avec des colonnes de n'importe quel type structuré, bien que vous deviez vérifier si vous dénormalisez inutilement votre conception.


1
cela ne semble rien offrir de substantiel sur les points soulevés et expliqués dans les 13 réponses précédentes
gnat

La question mentionne spécifiquement JSON et cette réponse est la seule à indiquer que JSON peut être renvoyé dans les requêtes d'au moins un SGBDR. J'aurais plutôt commenté la question pour dire qu'elle repose sur une fausse prémisse et qu'elle ne peut donc pas attendre de réponse définitive. Cependant, StackExchange ne me laissera pas faire cela.
Jonathan Rogers
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.