Por que os bancos de dados relacionais não suportam o retorno de informações em um formato aninhado?


46

Suponha que eu esteja criando um blog que eu queira ter postagens e comentários. Portanto, crio duas tabelas, uma tabela 'posts' com uma coluna 'id' de número inteiro automático e uma tabela 'comments' que possui uma chave estrangeira 'post_id'.

Quero executar o que provavelmente será minha consulta mais comum, que é recuperar uma postagem e todos os seus comentários. Sendo uma novidade para os bancos de dados relacionais, a abordagem que me parece mais óbvia é escrever uma consulta que se pareça com:

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

O que me daria o ID e o conteúdo da postagem que eu quero, juntamente com todas as linhas de comentários relevantes empacotadas ordenadamente em uma matriz (uma representação aninhada como você usaria no JSON). É claro que os bancos de dados SQL e relacionais não funcionam assim, e o mais próximo que eles podem chegar é fazer uma junção entre 'postagens' e 'comentários' que retornarão muita duplicação desnecessária de dados (com as mesmas informações de postagem repetidas em cada linha), o que significa que o tempo de processamento é gasto no banco de dados para reunir tudo e no meu ORM para analisar e desfazer tudo.

Mesmo que eu instrua meu ORM a carregar avidamente os comentários da postagem, o melhor que ele fará é enviar uma consulta para a postagem e, em seguida, uma segunda consulta para recuperar todos os comentários e reuni-los no lado do cliente, o que também é ineficiente.

Entendo que os bancos de dados relacionais são uma tecnologia comprovada (inferno, eles são mais antigos do que eu), e que houve uma tonelada de pesquisas neles ao longo das décadas e tenho certeza de que há uma boa razão para eles (e os Padrão SQL) foram projetados para funcionar da maneira que funcionam, mas não sei por que a abordagem descrita acima não é possível. Parece-me a maneira mais simples e óbvia de implementar um dos relacionamentos mais básicos entre registros. Por que os bancos de dados relacionais não oferecem algo assim?

(Isenção de responsabilidade: geralmente escrevo aplicativos da Web usando datastores Rails e NoSQL, mas recentemente venho testando o Postgres e realmente gosto muito. Não pretendo atacar bancos de dados relacionais, estou perplexo.)

Não estou perguntando como otimizar um aplicativo Rails ou como solucionar esse problema em um banco de dados específico. Estou perguntando por que o padrão SQL funciona dessa maneira quando parece contra-intuitivo e inútil para mim. Deve haver alguma razão histórica pela qual os designers originais do SQL queriam que seus resultados fossem assim.


1
nem todas as empresas funcionam dessa maneira. O hibernate / nhibernate permite que as junções sejam especificadas e pode carregar ansiosamente árvores de objetos inteiras a partir de uma única consulta.
Nathan gonzalez

1
Além disso, enquanto um interessante ponto de discussão, eu não estou certo de que este é realmente responsável, sem ter uma reunião com os caras SQL ANSI
Nathan Gonzalez

@nathan: Sim, nem todos. Eu tenho usado o Sequel, que permite escolher qual abordagem você prefere para uma determinada consulta ( documentos ), mas eles ainda incentivam a abordagem de várias consultas (por razões de desempenho, suponho).

5
Como um RDBMS foi projetado para armazenar e recuperar conjuntos - ele não se destina a retornar dados para exibição. Pense nisso como o MVC - por que ele tentaria implementar a visão com o custo de tornar o modelo mais lento ou mais difícil de usar? O RDBMS oferece benefícios que os bancos de dados NoSQL não podem (e vice-versa) - se você o estiver usando porque é a ferramenta certa para resolver seu problema, não pediria que ele retornasse dados prontos para exibição.

1
Eles vêem para xml
Ian

Respostas:


42

CJ Date entra em detalhes sobre isso no capítulo 7 e no apêndice B do SQL e da teoria relacional . Você está certo, não há nada na teoria relacional que impeça o tipo de dados de um atributo de ser uma relação em si, desde que seja o mesmo tipo de relação em cada linha. Seu exemplo se qualificaria.

Mas Date diz que estruturas como essa são "geralmente - mas não invariavelmente - contra-indicadas" (ou seja, uma má idéia) porque hierarquias de relações são assimétricas . Por exemplo, uma transformação de estrutura aninhada em uma estrutura "plana" familiar nem sempre pode ser revertida para recriar o aninhamento.

Consultas, restrições e atualizações são mais complexas, mais difíceis de escrever e mais suportadas pelo RDBMS se você permitir atributos com valor de relação (RVAs).

Ele também confunde os princípios de design do banco de dados, porque a melhor hierarquia de relações não é tão clara. Devemos projetar uma relação de Fornecedores com um RVA aninhado para peças fornecidas por um determinado Fornecedor? Ou uma relação de peças com um RVA aninhado para fornecedores que fornecem uma determinada peça? Ou armazene os dois, para facilitar a execução de diferentes tipos de consultas?

Esse é o mesmo dilema que resulta do banco de dados hierárquico e dos modelos de banco de dados orientados a documentos . Eventualmente, a complexidade e o custo de acessar estruturas de dados aninhadas levam os designers a armazenar dados de forma redundante para facilitar a pesquisa por consultas diferentes. O modelo relacional desencoraja a redundância, para que os RVAs possam trabalhar contra os objetivos da modelagem relacional.

Pelo que entendi (não os usei), Rel e Dataphor são projetos RDBMS que suportam atributos com valor de relação.


Re comentário de @dportas:

Tipos estruturados fazem parte do SQL-99, e a Oracle os suporta. Mas eles não armazenam várias tuplas na tabela aninhada por linha da tabela base. O exemplo comum é um atributo "endereço" que parece ser uma única coluna da tabela base, mas possui sub-colunas adicionais para rua, cidade, código postal etc.

As tabelas aninhadas também são suportadas pelo Oracle e permitem várias tuplas por linha da tabela base. Mas não sei que isso faz parte do SQL padrão. E lembre-se da conclusão de um blog: "Eu nunca usarei uma tabela aninhada em uma instrução CREATE TABLE. Você gasta todo o seu tempo UN-NESTING-los para torná-los úteis novamente!"


3
Na verdade, eu não gostaria de armazenar uma relação dentro de outra - elas estariam em tabelas separadas e desnormalizadas como de costume. Estou apenas perguntando por que esse tipo de incorporação de resultados não é permitido em consultas, quando parece mais intuitivo para mim do que o modelo de junção.
PreciousBodilyFluids

Conjuntos de resultados e tabelas são de um tipo. Date os chama de relações e relvars respectivamente (por analogia, 42 é um número inteiro, enquanto uma variável xpode ter o valor do número inteiro 42). As mesmas operações se aplicam a relações e relvars, portanto, sua estrutura precisa ser compatível.
Bill Karwin

2
O SQL padrão suporta tabelas aninhadas. Eles são chamados de "tipos estruturados". O Oracle é um DBMS que possui esse recurso.
Nvogel

2
Não é um absurdo argumentar que, para evitar a duplicação de dados, você deve escrever sua consulta de maneira simples e duplicada?
Eamon Nerbonne

1
@EamonNerbonne, simetria das operações relacionais. Por exemplo, projeção. Se eu selecionar alguns subatributos de um RVA, como posso aplicar uma operação reversa no conjunto de resultados para reproduzir a hierarquia original? Encontrei a página 293 do livro de Date no Google Livros, para que você possa ver o que ele escreveu: books.google.com/…
Bill Karwin

15

Alguns dos primeiros sistemas de banco de dados foram baseados no modelo Hierarchical Database . Isso representou dados em uma árvore como estrutura com pai e filhos, como você está sugerindo aqui. O HDMS foi amplamente substituído pelos bancos de dados criados com base no modelo relacional. As principais razões para isso foram que o RDBMS podia modelar relacionamentos "muitos para muitos" que eram difíceis para bancos de dados hierárquicos e que o RDBMS podia facilmente executar consultas que não faziam parte do design original, enquanto o HDBMS o restringia a consultar os caminhos especificados no tempo de design.

Ainda existem alguns exemplos de sistemas hierárquicos de banco de dados em estado selvagem, particularmente o registro do Windows e o LDAP.

Cobertura extensiva deste assunto está disponível no seguinte artigo


10

Suponho que sua pergunta esteja realmente centrada no fato de que, embora os bancos de dados sejam baseados em uma lógica sólida e configurem bases teroréticas, eles executam um trabalho muito bom ao armazenar, manipular e recuperar dados em conjuntos (bidimensionais), garantindo integridade referencial, simultaneidade e muitas outras coisas, eles não fornecem um recurso (adicional) de enviar (e receber) dados no que se poderia chamar de formato orientado a objeto ou formato hierárquico.

Em seguida, você afirma que "mesmo que eu instrua meu ORM a carregar avidamente os comentários da postagem, o melhor a fazer é enviar uma consulta para a postagem e, em seguida, uma segunda consulta para recuperar todos os comentários e reuni-los. do lado do cliente, o que também é ineficiente " .

Não vejo nada de ineficiente no envio de 2 consultas e no recebimento de 2 lotes de resultados com:

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


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

Eu diria que essa é (quase) a maneira mais eficiente (quase, porque você realmente não precisa das posts.idcolunas e nem todas comments.*)

Como Todd apontou em seu comentário, você não deve pedir ao banco de dados para retornar dados prontos para exibição. É o trabalho do aplicativo para fazer isso. Você pode escrever (uma ou algumas) consultas para obter os resultados necessários para cada operação de exibição, para que não haja duplicação desnecessária nos dados enviados pelo cabo (ou pelo barramento de memória) do banco de dados para o aplicativo.

Na verdade, não posso falar sobre ORMs, mas talvez alguns deles possam fazer parte desse trabalho para nós.

Técnicas semelhantes podem ser usadas na entrega de dados entre um servidor web e um cliente. Outras técnicas (como cache) são usadas para que o banco de dados (ou a web ou outro servidor) não seja sobrecarregado com solicitações duplicadas.

Meu palpite é que os padrões, como o SQL, são melhores se permanecerem especializados em uma área e não tentarem cobrir todas as áreas de um campo.

Por outro lado, o comitê que define o padrão SQL pode pensar de outra maneira no futuro e fornecer padronização para esse recurso adicional. Mas não é algo que possa ser projetado em uma noite.


1
Eu quis dizer ineficiente no sentido de que meu aplicativo precisa suportar a sobrecarga e o atraso de duas chamadas ao banco de dados em vez de apenas uma. Além disso, a junção não está apenas retornando dados em um formato pronto para exibição? Ou usando uma exibição de banco de dados? Você também pode evitá-las simplesmente executando mais consultas pequenas e juntando-as no seu aplicativo, se quiser, mas elas ainda são ferramentas úteis. Não acho que o que estou propondo seja significativamente diferente de uma junção, além de ser mais fácil de usar e ter melhor desempenho.

2
@ Precioso: não é necessário aumentar a sobrecarga para executar várias consultas. A maioria dos bancos de dados permite enviar várias consultas em um único lote e receber vários conjuntos de resultados de uma única consulta.
Daniel Pryden

@PreciousBodilyFluids - o snippet SQL na resposta do ypercube é uma única consulta que seria enviada em uma única chamada de banco de dados e retornaria dois conjuntos de resultados em uma única resposta.
precisa saber é o seguinte

5

Não sou capaz de responder com uma resposta adequada e argumentada; portanto, sinta-se à vontade para me rebaixar ao esquecimento se estiver errado (mas, por favor, corrija-me para que possamos aprender algo novo). Penso que a razão é que os bancos de dados relacionais estão centrados no modelo relacional, que por sua vez se baseia em algo que não sei nada sobre chamado "lógica de primeira ordem". O que você pode perguntar provavelmente não se encaixa conceitualmente na estrutura matemática / lógica dos bancos de dados relacionais. Além disso, o que você pede geralmente é resolvido facilmente pelos bancos de dados de gráficos, dando mais dicas de que é a conceituação subjacente do banco de dados que entra em conflito com o que você deseja alcançar.


5

Eu sei que pelo menos o SQLServer oferece suporte a consultas aninhadas quando você usa 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')

O problema aqui não é a falta de suporte do RDBMS, mas a falta de suporte de tabelas aninhadas nas tabelas.

Além disso, o que impede você de usar uma junção interna?

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

Você pode olhar realmente para a junção interna como uma tabela aninhada, apenas o conteúdo dos 2 primeiros campos é repetido uma vez. Eu não me preocuparia muito com o desempenho da junção, a única parte lenta de uma consulta como essa é a io do banco de dados para o cliente. Isso só será um problema quando o conteúdo contiver uma grande quantidade de dados. Nesse caso, eu sugeriria duas consultas, uma com select id, contente outra com uma junção interna e select posts.id, comments.*. Isso é dimensionado mesmo com várias postagens, pois você ainda usaria apenas 2 consultas.


As perguntas abordam isso. Você precisa fazer duas viagens de ida e volta (não ideal) ou retornar dados redundantes nas duas primeiras colunas (também não ideal). Ele quer a solução ideal (não é realista na minha opinião).
Scott Whitlock 07/07

Eu sei, mas não há coisa ruim como uma solução ideal. A única coisa que posso argumentar é onde a sobrecarga seria mínima e de onde depende. Se você deseja a solução ideal, faça benchmark e tente diferentes abordagens. Mesmo a solução XML pode ser mais lenta, dependendo da situação específica, e eu não estou familiarizado com os datastores NoSQL, então não posso dizer se ele tem algo semelhante a for xml.
Dorus

5

Na verdade, o Oracle suporta o que você deseja, mas é necessário agrupar a subconsulta com a palavra-chave "cursor". Os resultados são buscados através do cursor aberto. Em Java, por exemplo, os comentários apareceriam como conjuntos de resultados. Mais sobre isso, consulte a documentação da Oracle sobre "Expressão CURSOR"

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

1

Alguns suportam aninhamento (hierárquico).

Se você quisesse uma consulta, poderia ter uma tabela que se auto-referencia. Alguns RDMS suportam esse conceito. Por exemplo, com o SQL Server, é possível usar CTEs (Common Table Expressions) para uma consulta hierárquica.

No seu caso, as postagens estariam no nível 0 e, em seguida, todos os comentários estariam no nível 1.

As outras opções são 2 consultas ou um ingresso com algumas informações extras para cada registro retornado (que outros mencionaram).

Exemplo de hierarquia:

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

No link acima, o EmpLevel mostra o nível do aninhamento (ou hierarquia).


Não consigo encontrar nenhuma documentação sobre subconjuntos de resultados no SQL Server. Mesmo ao usar um CTE. Por conjunto de resultados, quero dizer linhas de dados com apenas colunas fortemente tipadas. Você pode adicionar referências à sua resposta?
SandRock

@SandRock - Um banco de dados enviará de volta um único conjunto de resultados de uma Consulta SQL. Ao identificar níveis na própria consulta, você pode criar um conjunto de resultados hierárquico ou aninhado que deve ser processado. Eu acho que atualmente, o mais próximo, estamos prontos para retornar dados aninhados.
Jon Raynor

0

Sinto muito, não tenho certeza se entendi exatamente o seu problema.

No MSSQL, você pode apenas executar 2 instruções SQL.

SELECT id, content
FROM posts
WHERE id = 7

SELECT * FROM comments WHERE post_id = 7

E ele retornará seus 2 conjuntos de resultados simultaneamente.


A pessoa que está fazendo a pergunta está dizendo que isso é menos eficiente porque resulta em duas viagens de ida e volta ao banco de dados, e geralmente tentamos minimizar as viagens de ida e volta por causa da sobrecarga. Ele quer fazer uma viagem de ida e volta e recuperar as duas mesas.
Scott Whitlock

Mas será uma viagem de ida e volta. stackoverflow.com/questions/2336362/…
Biff MaGriff

0

RDBMs são baseados na teoria e se apegam à teoria. Isso permite uma boa consistência e confiabilidade comprovada matematicamente.

Como o modelo é simples e, novamente, baseado na teoria, facilita para as pessoas a otimização e muitas implementações. Isso é diferente do NoSQL, onde todo mundo faz um pouco diferente.

No passado, houve tentativas de criar bancos de dados hierárquicos, mas o IIRC (não é possível pesquisar no Google) houve problemas (ciclos e igualdade vêm à mente).


0

Você tem uma necessidade específica. Seria preferível extrair dados de um banco de dados no formato desejado, para que você possa fazer o que quiser.

Alguns bancos de dados não funcionam tão bem, mas não é impossível construí-los para fazê-lo de qualquer maneira. Deixar a formatação para outros aplicativos é a recomendação atual, mas não justifica por que isso não pode ser feito.

O único argumento que tenho contra a sua sugestão é ser capaz de lidar com esse conjunto de resultados de maneira "sql". Seria uma má idéia criar um resultado no banco de dados e não poder trabalhar com ele ou manipulá-lo até certo ponto. Digamos que eu criei uma exibição criada da maneira que você sugere, como incluí-la em outra instrução select? Os bancos de dados gostam de obter resultados e fazer coisas com eles. Como eu o juntaria a outra mesa? Como eu compararia seu conjunto de resultados com outro?

O benefício dos RDMSs é a flexibilidade do sql. A sintaxe para selecionar dados de uma tabela está bem próxima de uma lista de usuários ou outros objetos no sistema (pelo menos esse é o objetivo). Não tenho certeza se há razão para fazer algo completamente diferente. Eles nem chegaram ao ponto de manipular código / cursores procedimentais ou BLOBS de dados com muita eficiência.


0

Na minha opinião, é principalmente por causa do SQL e da maneira como as consultas agregadas são executadas - funções e agrupamentos agregados são executados em grandes conjuntos de linhas bidimensionais para retornar resultados. É assim que é desde o início e é muito rápido (a maioria das soluções NoSQL são muito lentas com agregação e dependem de esquema desnormalizado em vez de consultas complexas)

Obviamente, o PostgreSQL possui alguns recursos do banco de dados orientado a objetos. De acordo com esses e-mails ( mensagem ), você pode obter o que precisa criando agregados personalizados.

Pessoalmente, estou usando estruturas como o Doctrine ORM (PHP), que agregam o lado do aplicativo e suportam recursos como carregamento lento para aumentar o desempenho.


0

O PostgreSQL suporta uma variedade de tipos de dados estruturados, incluindo Arrays e JSON . Usando SQL ou uma das linguagens processuais incorporadas, você pode criar valores com uma estrutura arbitrariamente complexa e devolvê-los ao seu aplicativo. Você também pode criar tabelas com colunas de qualquer um dos tipos estruturados, embora considere cuidadosamente se está desnormalizando desnecessariamente seu design.


1
este não parece oferecer nada substancial sobre pontos feitos e explicado em anteriores 13 respostas
mosquito

A pergunta menciona especificamente o JSON e essa resposta é a única a apontar que o JSON pode ser retornado nas consultas de pelo menos um RDBMS. Eu preferiria ter comentado a questão para dizer que ela se baseia em uma premissa falsa e, portanto, não pode esperar nenhuma resposta definitiva. No entanto, o StackExchange não me permite fazer isso.
Jonathan Rogers
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.