Arquivo da tag: JSON

OCI NoSQL Tables

Geralmente temos servidores e storage, e então bancos de dados, e claro, tabelas. No entanto, com o avanço das arquiteturas cloud native existe uma outra abordagem: as tabelas sem bancos de dados e sem servidores (tabelas serverless).

Essas tabelas existem como um serviço gerenciado em diversos provedores de cloud, e somente nos provedores de cloud, pois elas são uma consequência das arquiteturas cloud native. Neste post vou escrever sobre o serviço NoSQL Tables, as tabelas Serverless da Oracle Cloud (OCI).

Vou explicar como elas funcionam, para que servem, casos de uso, e também vou mostrar um exemplo prático para ficar mais claro.

Principais Características

Apesar de você não enxergar os servidores, eles existem, claro! Mas o serviço expõe as tabelas com uma abordagem serverless. Do ponto de vista físico, as NoSQL Tables são tabelas que implementam o conceito de sharding, isto é, os dados são distribuídos de forma horizontal em servidores diferentes. Além disso, os dados de cada servidor também são replicados para outros servidores. Essa arquitetura distribuída, além de promover alta disponibilidade e durabilidade, garante que o tempo de resposta seja, sempre de forma estável e previsível, de poucos milisegundos.

Como os dados são distribuídos e replicados em servidores diferentes, você pode escolher se as leituras serão do tipo consistente ou eventual, e se vai utilizar ACID para as transações: para entender melhor este ponto (caso não conheça), veja este meu outro post onde abordo o Teorema CAP.

Há bastante flexibilidade para escolher o formato dos dados: existe suporte para dados em colunas (schema-full), tal como conhecemos no modelo relacional (ex: integer, string timestamp e Blob), mas adiciona tipos complexos, como Arrays, Records e Map. E claro, não menos importante, também suporta JSON nativamente (schema flexível).

Outras capacidades que também ajudam bastante em diversos casos é a possibilidade de configurar TTL (time-to-live, onde os dados são removidos automaticamente após o tempo que você especificar), criação de índices secundários (inclusive com JSON), e também a possibilidade de usar SQL ou API (com suporte a Spring Data).

Como não existe servidor, e se quer existe um banco de dados (pelo menos em termos de usabilidade), como será que esses serviços são dimensionados? Bom, aqui está a grande diferença: pelo workload! Isto é, você dimensiona uma NoSQL Table informando qual é a demanda de uso.

São três variáveis requeridas quando se cria uma NoSQL Table na OCI:

  • Quantidade máxima de leituras por segundo;
  • Quantidade máxima de escritas por segundo;
  • Espaço em Gbytes.

Essas variáveis podem ser alteradas de forma online enquanto a tabela existir (elástico, para cima ou para baixo, a qualquer tempo).

Nota: o billing deste serviço considera essas três variáveis: existe um preço por read unit, outro para write unit, e outro para gbytes utilizados. Em geral é um serviço considerado barato, e faz parte do free tier, mas é importante estimar o custo para o seu workload.

No final do dia NoSQL Tables é um serviço gerenciado, então operações como provisionamento de infraestrutura, backup, patching, configurações de alta disponibilidade e segurança, enfim, tudo aquilo que é necessário para deixá-lo no ar e disponível é gerenciado pela própria OCI.

Casos de Uso e Utilidade

Por ser simples, permitir um schema flexível, com baixa latência previsível, e ser completamente gerenciado, as tabelas serveless podem ser bastante úteis nos casos de uso abaixo e quaisquer outros casos similares a estes:

  • Perfis de usuário;
  • Eventos de qualquer tipo (usuários, clicks, etc) e IoT;
  • Carrinho de compras;
  • Catálogo de produtos;
  • Cache de dados para web servers;
  • Armazenamento de metadados e parâmetros;
  • Personalização de anúncios;
  • Detecção de fraude em tempo real.

Claro que existem também os casos onde elas não são úteis. Vou colocar abaixo os principais casos onde você deve fugir das tabelas serverless:

  • Migrar um banco de dados legado existente as-is (a não ser que você queira fazer um grande refactoring);
  • Necessita de um modelo de dados mais complexo, que demanda por um schema com muitas tabelas e relacionamentos. Se não for possível colocar tudo em poucas tabelas, ou se não for compatível com workloads do tipo JSON, não use;
  • No caso onde os limites do serviço gerenciado são insuficientes para o seu caso de uso. À propósito, isto vale para qualquer serviço gerenciado.

Na Prática

As NoSQL Tables podem ser usadas de diversas formas: pelo console gráfico da OCI, pela CLI (Command Line Interface), e também pelas SDKs nas diversas liguagens suportadas, como Java, Node.js, Python, .Net e Go.

Vou utilizar a CLI para criá-las, e para isso você precisa configurar a OCI CLI previamente.

Nota: no arquivo de configuração da OCI CLI você pode colocar alguns parâmetros que utiliza com frequência para evitar de repeti-los toda vez que faz uma chamada CLI. Nos exemplos a seguir eu fiz isso para o parâmetro compartment_id, que é obrigátorio e é responsável por agrupar logicamente os recursos que você cria na OCI).

Com a CLI configurada, veja abaixo como é simples criar uma tabela serverless na OCI. Primeiro vamos especificar os limites da tabela, criando este arquivo table_limits.json:

{
  "maxReadUnits": 2,
  "maxStorageInGBs": 1,
  "maxWriteUnits": 2
}

Com este arquivo como exemplo, nossa tabela permitirá no máximo 2 leituras (maxReadUnits) e 2 escritas (maxWriteUnits) concorrentes, e até 1 GByte de espaço (maxStorageInGBs).

Depois podemos utilizá-lo no comando abaixo:

oci nosql table create --ddl-statement "CREATE TABLE usuarios (id integer, nome string, PRIMARY KEY (SHARD(id)))" --name usuarios --table-limits file://table_limits.json

A tabela chama-se Usuarios, e coloquei apenas duas colunas: id (integer) e nome (string), onde id é a chave primária, e também é a coluna que distribui os dados horizontalmente (shard).

Se eu quisesse utilizar JSON para a coluna nome, bastava substituir string por json na criação da tabela, ou ainda adicionar mais colunas de outros tipos.

Para listar suas tabelas criadas, você pode executar o seguinte comando:

oci nosql table list

Este comando vai retornar um output JSON com a lista de tabelas e suas propriedades.

Apesar de eu não achar apropriado, podemos também manipular e consultar dados pelo OCI CLI. Este comando abaixo insere um registro:

oci nosql query execute --statement "INSERT INTO usuarios (id, nome) VALUES (1,\"Fernando\")"

É possível consultar a tabela com SQL, utilizando este comando:

oci nosql query execute --statement "SELECT * FROM usuarios"

Para remover a tabela, você pode fazer desta forma:

oci nosql table delete --table-name-or-id usuarios

Bom, utilizar a OCI CLI para criar e remover tabelas tudo bem, mas para manipular dados com certeza é melhor utilizar alguma SDK, pois é através dela que você utilizará as tabelas serverless no seu sistema.

Vou utilizar como exemplo a SDK em Python. Veja aqui os requerimentos para instalar os drivers (biblioteca borneo) no seu sistema.

Abaixo vou fazer uma consulta simples pela chave usando API, mas antes preciso importar a biblioteca com os objetos que usaremos, e também fazer a configuração de conexão e autenticação:

from borneo import NoSQLHandle, NoSQLHandleConfig, Regions, Consistency
from borneo.iam import SignatureProvider

handleConfig = NoSQLHandleConfig(Regions.US_ASHBURN_1)
config = handleConfig.set_authorization_provider(SignatureProvider())
config.set_default_compartment('dev')
config.set_consistency(Consistency.EVENTUAL) # ou Consistency.ABSOLUTE
conn = NoSQLHandle(config)

Importei da biblioteca borneo (que utilizamos para manipular as NoSQL Tables) todos os objetos que vou utilizar nos exemplos seguintes.

config é minha configuração de conexão e autenticação, que recebe a região da OCI que usei para criar a tabela (us_ashburn_1) e meus dados de acesso com SignatureProvider(): esse método retorna minhas credenciais que estão configuradas na minha OCI CLI — há várias outras formas de autentição também, veja aqui.

dev é o compartimento onde criei a tabela, e Consistency.EVENTUAL configuro as leituras para serem eventuais (Availability + Partition Tolerance, do Teorema CAP), mas poderia configurar como Consistency.Absolute (Strong Consistency + Partition Tolerance, do Teorema CAP).

O objeto conn (criado a partir de config) é utilizado para as demais operações sobre a tabela.

Bom, vamos então para a consulta simples pela chave usando API:


from borneo import GetRequest

request = GetRequest().set_table_name('usuarios')
request.set_key({'id': 1}) 
result = conn.get(request) 
if result.get_value() is not None:
    print(result)   

Primeiro informei no objeto request qual é o nome da minha tabela, e depois consigo consultá-la informando qual é a chave de busca pelo método set_key. O objeto result recebe o resultado da consulta.

O código abaixo faz a mesma coisa, porém utiliza SQL, e Prepared Statements:

from borneo import QueryRequest, PrepareRequest

sql = 'declare $myKey integer; select * from usuarios where id = $myKey'
request = PrepareRequest().set_statement(sql)
prepStmt = conn.prepare(request).get_prepared_statement()
prepStmt.set_variable('$myKey', 1)
queryRequest = QueryRequest().set_prepared_statement(prepStmt)
while True:
    result = conn.query(queryRequest)
    resultList = result.get_results()
    if queryRequest.is_done():
        break

print(resultList)

PreparedStatements servem para otimizar a execução das instruções SQL em um banco de dados, de forma que faça apenas um parse e múltiplas execuções (ao invés de fazer um parse para cada execução).

Inicialmente criei uma string SQL utilizando $mykey como variável. Depois preparei o statement, e depois fiz a operação de bind, que é colocar o valor na variável $mykey. Por fim a query é executada e o resultado é atribuído ao objeto resultList.

E quanto as transações? Certo! É possível inserir, atualizar e remover dados, normalmente.

Veja abaixo como faço para inserir mais um registro na minha tabela:

from borneo import PutRequest

request = PutRequest().set_table_name('usuarios')
request.set_value({'id': 2, 'nome': 'Paulo'})
result = handle.put(request)
if result.get_version() is not None:
   print('success')

Basta criar um request utilizando o objeto PutRequest, atribuir o valor, e executar com put. Também é possível fazer com SQL, e também é possível fazer várias mudanças com uma simples operação (WriteMultipleRequest).

Neste post eu fiz um overview bem resumido do que é possível fazer com as tabelas serverless. Em outros posts vou mostrar outras capacidades deste serviço. Stay tuned!

Conclusão

As NoSQL Tables são mais robustas do que parecem. Você pode criar tabelas serverless com múltiplos formatos, incluindo JSON e SQL (com ACID e opção de escolha entre consistência forte ou eventual), índices secundários e TTL. É muito fácil de começar, e não há muito o que aprender, pois o serviço é totalmente gerenciado.

Referências

Você pode me seguir neste blog:

Disclaimer: As opiniões expressas neste blog são minhas, e não são necessariamente as mesmas opiniões do meu empregador.


Publicidade

Arquitetura de Dados Convergente

Nos sistemas e nas aplicações que são criadas hoje em dia, os maiores problemas com Dados em geral são a quantidade de informações geradas, os múltiplos formatos que existem, e a velocidade em que eles se modificam.

Nesse post eu vou comentar um pouco sobre esse tema, e duas maneiras de lidar com isso.


Quando a gente desenvolve um sistema hoje em dia, normalmente nós usamos várias tecnologias que tem a ver com Dados, mas cada uma tem uma finalidade específica, por exemplo:

  • Quando você armazena dados com objetivo de alguém ler depois, você usa um banco de dados;
  • Se você quer lembrar o resultado de uma busca complexa, para acelerar leituras, você usa um mecanismo de cache;
  • Para permitir que os usuários façam buscas por palavras-chave específicas, textos-livres, ou consultas ad-hoc, você usa índices;
  • Para trocar mensagens entre processos, ou entre sistemas, você usa um broker de eventos;
  • E para periodicamente pegar uma grande quantidade de dados acumulados e processar, você faz um processamento batch.

Nós tipicamente pensamos que bancos de dados, caches, filas, são todas ferramentas de diferentes categorias. Por exemplo, um banco de dados e um broker de eventos têm uma certa similaridade, porque ambos armazenam dados por algum tempo. Mas eles são diferentes. Eles têm padrões de acesso diferentes, e características de performance e implementação diferentes.

Além disso, pra cada uma dessas categorias existem dezenas de opções com base em diferentes capacidades. Existem vários tipos de bancos de dados, várias formas e métodos diferentes de fazer cache, e vários tipos de índices para buscar informações. E isso ocorre basicamente porque os sistemas TÊM necessariamente requerimentos diferentes.

E quando a gente desenvolve um sistema, especialmente na camada de persistência, na camada de dados, a gente precisa saber quais ferramentas e quais métodos são os mais apropriados para cada caso, e é difícil combinar ferramentas diferentes quando você precisa fazer uma coisa que uma só ferramenta sozinha não consegue fazer, certo?

A verdade é que no final do dia você, além de você ser um desenvolvedor de software, acaba sendo também um arquiteto de dados.

Então basicamente você tem dois caminhos.

Você tem o caminho onde você vai utilizar várias tecnologias. Você vai aprender todas essas ferramentas. Você vai integrar todas elas. Vai suportar todas elas, e vai garantir que elas funcionem em conjunto.

E você tem um outro caminho. O caminho que eu chamo de “um sistema de dados convergente”. E nesse sistema você tem uma plataforma, e nessa plataforma você tem quase todas as ferramentas, quase todos os tipos de dados…  seja schema on write, seja schema on read, seja um cache, uma fila, não importa o formato dos dados… todos eles, dentro do mesmo sistema de dados. E quando esse sistema não suporta alguma coisa, ele virtualiza o acesso e expõe a mesma interface de acesso pra você consumir aquela informação da forma mais transparente possível.

Veja meu video sobre esse tema:

Document Stores sempre serão um Nicho?

Document Stores são uma espécie de subcelebridade dos bancos de dados. Eles apareceram de repente, têm supostamente alguma relevância, mas todos os outros bancos de dados Não-Document Store já fazem o que eles fazem: Oracle, MySQL, SQL Server, PostgreSQL, DB2, ElasticSearch, Redis, MariaDB…

Document
Stores
Não-Document Stores
(Multimodel DBs)
Suporte a DocumentosXX

Eles são amados principalmente por front-end devs, porque Documentos permitem a mais rápida prototipação. É inegável.

O grau de flexibilidade de schema é tão alto que causa piripaques nos administradores de dados mais tradicionais. Alguns até proíbem.

Mas só depois que você usa o Modelo de Documentos você percebe o quão rígido é o Modelo Relacional. É incrível como um é o oposto do outro. Há de verdade um perigo real e iminente de você se lambuzar e querer colocar Document Stores em todos os lugares.

Bom, devo lembrar que este artigo não é exatamente sobre Documentos JSON ou XML – é sobre Document Stores: bancos de dados que só armazenam Documentos. Não confunda.

Fiz um video em meu canal no YouTube, com conteúdo para database developers, explicando algumas diferenças de arquitetura entre esses dois modelos:

Variedade, Velocidade e Volume.

Document stores resolvem principalmente a Variedade nos 3 Vs que definem Big Data, porque eles são schema-less, e por isso suportam uma grande variedade de formatos de dados.

Essa variedade é também o que lhes impede de serem completamente aderentes ao ACID, porque a letra C significa Consistência de Escrita [Schema], e Document Stores são, por definição, sem schema. Mas isso não lhes impede de suportar transações [Atomicidade, Isolamento e Durabilidade].

Há os que consideram 5 Vs, tendo Veracidade e Valor como os dois Vs adicionais. Neste post vou considerar apenas aspectos técnicos, por isso são 3 Vs.

A Velocidade nesse tipo de persistência é entregue através de particionamento dos dados: os documentos são distribuídos em partes [servidores] diferentes pela chave que identifica um documento.

Atualmente apenas tecnologias que escalam horizontalmente, como as que particionam os dados, conseguem suportar os mais altos volumes de leitura e escrita. Ainda não existe um teorema que prove isso, mas meu feeling diz que nunca vai existir outra arquitetura que escale mais, pelo menos até a popularização da computação quântica.

É questionável um Document Store ter grande desempenho quando não se pesquisa pela chave, mesmo que o banco de dados suporte índices secundários.

Pesquisas por chave são resolvidas com Hash, e índices secundários em geral são resolvidos com Árvores Binárias. Em uma análise assintótica, Hash é O(1), e Árvore Binária é O(Log n), e O(Log n) é mais lento que O(1). E uma busca por um índice secundário particionado é na melhor das hipóteses O(Log N) + O(1).

A maioria dos bancos de dados Não-Document Store que citei no início, que suporta Documentos, também suporta particionamento de dados.

Document
Stores
Não-Document Stores
(Multimodel DBs)
Suporte a DocumentosXX
Suporte a ParticionamentoXX

E como Document Stores se diferenciam em relação ao Volume?

Qual é o tipo de persistência que você acredita que tem maiores dificuldades com grandes volumes? Relacional? Bom, já pensou que um banco relacional é normalizado justamente para reduzir grandes volumes?

Vou explicar “normalizar” explicando “desnormalizar“.

Desnormalizar tem um significado que é agregar dados, um benefício que é agregar dados, e um problema que é agregar dados.

Document Stores são do tipo do segundo ‘agregar dados’, o do benefício. Para eles, desnormalizar significa aumentar o desempenho das consultas no banco de dados, porque a final de contas, dados agregados evitam joins, e joins são lentos, segundo eles.

Bancos Relacionais são do tipo do terceiro ‘agregar dados’, o do problema. Para eles, desnormalizar significa aumentar problemas de integridade nas escritas no banco de dados, pois como os dados não-chave não dependeriam funcionalmente só da chave [terceira forma normal], poderia haver valores duplicados e inconsistentes.

Pense que um banco de dados relacional com o tamanho de 10 TBytes em 3NF, se desnormalizado, atingiria fácil os 100 TBytes.

Isso ocorre porque esses bancos relacionais normalizam os dados, e eles fazem isso substituindo as repetições por um código [chaves estrangeiras]. A maneira mais vulgar de explicar isso é dizer que eles ‘desduplicam’ os dados.

Desduplicar na área de algoritmos de compressão significa substituir um valor que se repete por um símbolo de tamanho pequeno [um tipo de compressão sem comprimir]. Então como os bancos relacionais fazem isso como parte da sua natureza, posso dizer que um banco de dados relacional é um Big Data desduplicado.

Pare por um momento e pense. É isso mesmo. Um banco relacional suporta volumes colossais e você não sacou. Eles só estão desduplicados!

E o que isso tem a ver com os Document Stores? Basicamente isso indica que ambos têm a mesma capacidade de armazenar grandes volumes, porém o fazem de forma diferente. Isso também indica que qualquer coisa, menos o Excel (risos), consegue armazenar grandes volumes de dados.

Document
Stores
Não-Document Stores
(Multimodel DBs)
Suporte a Documentos
(Variedade)
XX
Suporte a Particionamento
(Velocidade)
XX
Suporte a Grandes Volumes
(Volume)
XX

Um desnormaliza para ter performance de consulta, e o outro normaliza para ter integridade. E o benefício de um, é o trade-off do outro.

O fato dos Não-Document Stores que citei no início deste post também suportarem Documentos, indica que eles são híbridos no sentido de que o desenvolvedor poderá escolher em qual parte do trade-off ele vai querer estar, em partes independentes do código, e em partes diferentes das informações que estiver armazenando.

Em outras palavras, ele poderá escolher qualquer combinação do C do ACID [consistência de escrita], com o C do CAP [consistência de leitura], particionado ou não.

Document
Stores
Não-Document Stores
(Multimodel DBs)
Suporte a Documentos
(Variedade)
XX
Suporte a Particionamento
(Velocidade)
XX
Suporte a Grandes Volumes
(Volume)
XX
undefinedX

Status Quo

O grande problema dos Document Stores para serem os Panteões de Todas as Persistências e de Tudo o Mais é o fato de eles serem schema-less.

O benefício da Variedade afetou sua relevância.

A história mostra que antes e após o Modelo Relacional, as formas de persistência de dados têm sido schema-less, onde só a aplicação entende como os dados estão organizados. Todas elas falharam como status quo, sumiram, ou são apenas nichos tornando-se subcelebridades [importantes pontualmente].

Qualquer modelo de dados onde você tem que conhecer a aplicação para entender os dados vai falhar no caminho para o mainstream, e será sempre um nicho.

Winter is Coming: Como Sobreviver em um Mundo com Persistência Poliglota?

Eu costumo dizer que bancos de dados são como queijos e vinhos, pois eles melhoram enquanto envelhecem.


No início existia apenas uma arquitetura de dados one-size-fits-all: a solução para todo e qualquer problema.

O que vemos hoje é uma verdadeira miscelânea, centenas de combinações e permutações possíveis de formatos diferentes, tecnologias, e integrações de todas as latências. Você já não sabe se tem que ser streaming ou batch, síncrono ou assíncrono, schema-less ou schema-full, SQL ou NoSQL, se a consistência mais adequada é eventual ou forte, o relacionamento é entre nós de um grafo ou entre chaves de tabelas, se precisa ser scale-up ou scale-out, e se é necessário um Data Lake ou um Data Warehouse.

Se identificou? Continue lendo.

Este artigo é sobre uma das maiores tendências a respeito de como organizar toda essa bagunça: persistência poliglota multi-modelo. Esta coisa de nome complicado, longo, e difícil de falar [eu entendo], é a fusão de uma dezena de tecnologias já testadas em batalha, e que provavelmente será, eu estimo, o futuro da arquitetura de dados. Será como antes só que o oposto: de one-size-fits-all, para all-fits-one-size.

O Início de Tudo: a Jornada do Herói

Nos primeiros sistemas de banco de dados, se é que podemos chamá-los de “sistemas” diante da primitividade que pairava no início, eu me lembro de ter lido que para conseguir uma informação você tinha que [1] saber qual era o arquivo onde ela estava, e [2] qual era o registro nesse arquivo, e [3] então quais eram as suas posições de início e término no registro. Essas posições eram sempre fixas, a propósito. Nessa estrutura, que se chamava ISAM, basicamente, você tinha que ter um mapa que lhe explicava como os dados que eram gravados, para poder buscá-los depois.

Não vou detalhar com mais profundidade sobre a estrutura de dados ISAM, pois assim como eu, hoje, você não vai querer saber!

Um senhor chamado Edgar Codd, pesquisador da IBM [claro, todos os pesquisadores da época eram da IBM] disse que as pessoas não deveriam ter que se preocupar em como os dados estavam organizados para entendê-los. Isso se tornaria um caos num futuro reservado para os Megabytes [hoje, Terabytes].

Codd então criou o Modelo de Dados Relacional. Era um mecanismo engenhoso, porém simples, que desacoplava os dados da organização física deles. Uma simples frase como “selecione os dados do cliente, na tabela de clientes” foi capaz de substituir milhares de linhas de código, e acelerar em ordens de magnitude o tempo para buscar as informações.

Eis que surge Larry Ellison, com mais alguns amigos. Enquanto uns apenas viam o que seus olhos enxergavam, Larry enxergava o que seus olhos viam: nada inventado antes era mais completo e eficiente do que o Modelo de Dados Relacional. Naqueles anos 70, era o futuro. E foi mesmo. E esse futuro ainda não acabou.

Surgiu o Oracle Database como o primeiro sistema gerenciador de banco de dados baseado no Modelo de Dados Relacional. Uma locomotiva de inovação a toda velocidade passou entre os anos 80, 90 e 2000. Foi incrível a quantidade de recursos criados, e até hoje alguns desses recursos ainda são exclusivos.

Muitas outras tecnologias surgiram: ora morriam pelo caminho, ora apenas sobreviviam com uma vida de ócio e defasagem diante da constante evolução do Oracle.

CAP, e os 300

O ano 2000 entra, a internet começa, e o suposto declínio do Modelo Relacional é ensaiado. A internet é um colosso perto dos sistemas empresariais. Por um minuto você tem 10 usuários, e no momento seguinte, instantaneamente, você tem 1 milhão. Projetado para funcionar em servidores que escalam verticalmente, os Bancos de Dados Relacionais realmente não funcionam nesse novo paradigma.

Eric Brewer formula uma conjectura que alguns anos mais tarde se tornara um teorema, o teorema CAP. Esse teorema basicamente forma a base para a criação de estruturas de dados que seriam mais adequados para a internet do que o Banco de Dados Relacional. Em essência, são estruturas de dados que escalam horizontalmente.

O teorema CAP é realmente uma ideia estranha, mas foi provada matematicamente. A letra A do CAP significa “sempre disponível para leitura e escrita“, o que implica em uma disponibilidade de 100%. Ora, nada nesse mundo tem 100% de disponibilidade. Nem o mundo tem 100% de disponibilidade. Bom, essa é uma premissa que não concordo, mas em função dela o CAP é um teorema, e com teoremas não se discute.

A ideia colou, mais de 300 bancos de dados Não-Relacionais foram criados, e a internet explode. Todo hype é então construído baseado em estruturas Não-Relacionais.

Dá-se então início a vários formatos de dados diferentes.

Timidamente, primeiro foram as estruturas Objeto-Relacionais. Essas morreram rápido. Prometeram de mais, fizeram de menos. Cometeram o mesmo pecado do ISAM: era necessário compreender os Objetos para entender os dados. Retrocesso. Curiosamente, os Bancos Relacionais absorveram o modelo lógico Objeto-Relacional, junto do Relacional, para quem quisesse usar, inclusive, no mesmo comando SQL.

É o início dos Bancos de Dados Multi-Modelo. Relacional com Objeto.

Depois surgiram as estruturas de armazenamento XML. Menos complexas do que os Objeto-Relacionais, pois elas tinham schema, e XPath para percorrer os dados. XML ficou bons anos em evidência. Mas também falhou. E novamente, os bancos relacionais absorveram o modelo lógico XML, junto do Relacional, para quem quisesse usar, no mesmo comando SQL.

Relacional, Objeto e Documento XML.

Em paralelo surgiu a necessidade de bancos de dados com capacidades de geo-processamento, os Bancos de Dados Espaciais. Nenhum fez sucesso como um produto a parte, porém, essa camada de persistência se juntou aos Relacionais, como uma outra camada lógica, e ainda é usada hoje, desta forma, como parte dos bancos relacionais.

Relacional, Objeto, Documento XML e Geo-Espacial.

Mais próximo dos tempos atuais, despontou como uma forma de armazenamento bastante comum o formato JSON, com grande flexibilidade e agilidade: os desenvolvedores adoram. No entanto, apesar de alguns bancos de dados puramente baseados nesse formato terem se tornado relevantes, os bancos de dados relacionais que outrora haviam absorvido vários outros modelos lógicos, também absorveram o formato JSON.

A partir de agora é muito mais fácil um Banco de Dados Multi-Modelo acrescentar uma nova estrutura lógica, do que um banco de dados especializado implementar as capacidades mais básicas, como Consistência Forte, ou mesmo SQL, por exemplo.

Relacional, Objeto, Documento XML e JSON, e Geo-Espacial.

Outras estruturas de dados com usos mais específicos também fizeram parte do big bang causado pelo teorema CAP: Grafos, Chave/Valor, Colunares e Motores de Busca. Adivinha? Os bancos de dados relacionais adicionaram mais camadas lógicas para suportar essas estruturas.

Destaco que de todos esses, o Modelo de Grafos é o mais diferente. É o mais engenhoso. E provavelmente é o que eu gosto mais. É também assunto para outro artigo.

Enquanto que do ponto de vista de estrutura de dados os Bancos Relacionais fizeram grandes intervenções para suportar tudo o que o mercado demandava, por outro lado, num passe metamorfósico, esses bancos também passaram a escalar horizontalmente, para os lados e não somente para cima, como todos os outros 300 bancos de dados Não-Relacionais já faziam. Dá-se o nome de NewSQL, uma alusão aos bancos Não-Relacionais [NoSQL] com a consistência forte dos Relacionais e o suporte completo ao SQL [basicamente, SQL é um sonho de toda a vida de um NoSQL, Apache Hive que o diga].

Esses super Bancos de Dados Relacionais, que suportam vários modelos de dados, e escalam tanto para cima como para os lados, são chamados finalmente de Bancos de Dados Poliglotas Multi-Modelo. Eles possuem toda a herança de inovação dos fundamentos essenciais para gestão de dados, o suporte aos principais formatos de dados dos últimos 40 anos, e a capacidade de escala que é um dos requerimentos para aplicações modernas de internet e Big Data.

Um outro fator relevante se soma a esses super bancos de dados: cloud automation. Poucos cliques ou chamadas de API permitem que toda essa estrutura seja criada, e mantida automaticamente.

De Codd ao Uber

As arquiteturas de dados são como um trem em alta velocidade, com curvas e hypes pelo caminho. Sempre surgem aquelas tecnologias que só elas fazem aquilo, que são mais típicas de “aplicações de ponta”, mas depois elas acabam convergindo para o mainstream porque as aplicações de ponta hoje são as aplicações mainstream de amanhã.

É por isso que tenho uma preferência particular por Bancos de Dados Multi-Modelo. Você absorve todas as inovações que surgem ao longo dos anos, e herda todas as capacidades fundamentais que as aplicações de ponta ainda não têm.

Meu Uber chegou! A propósito, ele me localizou com geo-processamento, o pagamento da minha corrida vai persistir com a integridade de um modelo relacional, e todas as outras coisas devem estar agregadas em documentos JSON em uma estrutura scale-out, pois afinal é mobile e precisa ser escalável.

Como poderia dizer Codd: as pessoas não deveriam ter que se preocupar em como os dados estão organizados para entendê-los. Isso pode se tornar um caos num futuro reservado para os Pegabytes…

Document Stores?

Eles são bancos de dados Relacionais ao contrário, porque ao invés de criar as tabelas e escrever os dados, você cria os dados e escreve as tabelas. Eles vieram, provavelmente, da mente de algum desenvolvedor de front-end que se cansou do pragmatismo dos Relacionais, e se revoltou. Não farão nada com a gente: os bancos Multi-Model já o absorveram! 


Documento é uma forma alternativa de persistência que se popularizou com o formato XML há alguns anos, e hoje está mais presente com o formato JSON, que tem melhor usabilidade.

Opinião sincera e desnecessária sobre a comparação entre XML e JSON para a narrativa de quando usar o que, como, e onde: esqueça XML, use JSON. Não é o objetivo deste post comparar os dois, mas vamos lá: além de ridículo, antigo, e ocupar muito espaço, XML me lembra muito HTML, e eu detesto HTML! Quando pessoas fazem o trabalho sujo de desenvolver ferramentas como o DreamWeaver, que nos permitem NÃO VER códigos HTML, eu definitvamente me sinto no topo da cadeia alimentar…

Normalmente utilizado para troca de dados entre sistemas, um documento JSON também pode ser utilizado como um formato de armazenamento [sempre surge alguém com uma ideia brilhante]. Então vou compará-lo com o modelo Relacional, o mais popular.

No modelo Relacional, uma tabela é um conjunto de linhas, e cada linha é um conjunto de colunas. No modelo de Documentos, uma coleção é um conjunto de documentos, e cada documento possui atributos. Então uma coleção é como se fosse uma tabela, um documento é como se fosse uma linha, e seus atributos é como se fossem colunas.

A diferença é que cada documento pode ser diferente em uma coleção, e em cada documento, seus atributos também podem ser diferentes, e ainda cada um desses atributos pode conter outros documentos, que pode conter outros documentos, que pode conter outros documentos, que pode conter outros documentos, que pode conter outros documentos, que pode conter outros documentos [não estou repetindo só pro meu post ter mais palavras. É que, de fato, rola um lance recursivo aqui, e incrivelmente eles não entram em loop].

Leia o parágrafo anterior 3 vezes. Eu sei que ficou confuso, e você não entendeu. Na terceira re-leitura você vai entender, e também vai notar que fiz o melhor que pude. A confusão faz parte desse tipo de banco de dados. Eu mesmo li três vezes pra entender o que escrevi.

No modelo Relacional, a estrutura deve ser definida antes de escrever os dados, enquanto que no modelo de Documentos, você pode escrever sem ter uma estrutura. A aplicação que escreve é que define a estrutura dos dados.

Se você conhece os bancos de dados Orientados a Objeto [faz parte do passado, e ninguém sente saudade], um banco de dados de Documentos vai lhe parecer bastante familiar, pois ambos permitem estruturas aninhadas [um atributo dentro de outro]. Porém, as semelhanças param por aí. O banco de dados de Documentos tem característica schemaless, onde você não especifica a estrutura antes de escrever os dados.

Reforçando os conceitos para você não esquecer nunca mais: 

Schema-full, ou Schema on Write

Quando a estrutura da tabela e as regras são definidas no banco de dados, e o código da aplicação deve respeita-las. Típico do banco de dados Relacional.

Schemaless, ou Schema ou Read

Quando a estrutura da tabela e as regras são definidas na aplicação, e no banco de dados se define apenas o nome da tabela, ou coleção. Típico do banco de dados de Documentos.

Neste post vou explorar as principais características de um banco de dados de Documentos para você utilizar quando for necessário.

Ele é necessário, e você precisa conhecer. É sério.

Vamos Olhar por Dentro: Coloque as Crianças pra Dormir!

O formato Java Script Object Notation [JSON] surgiu por volta do ano 2000, e logo se tornou popular principalmente pela sua simplicidade. Ele é mais compacto do que XML e consegue representar estruturas complexas, que não são possíveis na forma tabular comum nos bancos relacionais. 

{
 id: 100, 
 nome: “Suco”, 
 dept: “Bebidas”, 
 qtd: 10, 
 vl: 4.50
}

O documento JSON acima é um dos mais simples, e nota-se que ele se parece muito com uma estrutura chave-valor. O documento é tudo que está entre as chaves {}, e os atributos estão separados por vírgulas.

Ao buscar um atributo, obtem-se o valor correspondente: nome, por exemplo, retorna Suco.

Vamos ver um exemplo no Banco de Dados Oracle.

No Oracle 21c, criamos uma tabela comum, e os documentos são armazenados em uma coluna desta tabela, que representa a coleção, quando especificamos o datatype JSON.

Abaixo, eu crio uma tabela produtos que armazena uma coleção na coluna prodDocument, que é o documento JSON acima. Depois consulto o atributo nome:

CREATE TABLE produtos
(id INT, prodDocument JSON);

INSERT INTO produtos VALUES (1,
'{id: 100, nome: "Suco", dept: "Bebidas", qtd: 10, vl: 4.50}');

SELECT p.prodDocument.nome FROM produtos p;

É possível também colocar documentos dentro de documentos:

{"Produtos":
[
{id: 100, nome: “Suco”, dept: “Bebidas”, qtd: 10, vl: 4.50},
{id: 200, nome: “Chá”, dept: “Bebidas”, qtd: 31, vl: 6.75},
{id: 300, nome: “Água”, dept: “Bebidas”, qtd: 17, vl: 1.70}
]
}

Então basta abrir colchetes [observação desnecessária: eu definitivamente substitui os parenteses por eles nos meus textos, você já deve ter percebido] na parte do valor de um atributo para aninhar outros documentos JSON.

Na mesma tabela produtos vou incluir outra linha, desta vez com 3 documentos:

INSERT INTO produtos VALUES (2,
'{"Produtos":[
{id: 100, nome: "Suco", dept: "Bebidas", qtd: 10, vl: 4.50},
{id: 200, nome: "Chá",  dept: "Bebidas", qtd: 31, vl: 6.75},
{id: 300, nome: "Água", dept: "Bebidas", qtd: 17, vl: 1.70}
]}');

SELECT p.prodDocument.Produtos.nome
FROM produtos p
WHERE id = 2;

Perceba que a primeira linha que inseri [id = 1] tem um documento JSON com 5 atributos. E a segunda linha [id = 2] tem 3 documentos JSON com 5 atributos. Isto é, a segunda linha é aninhada, pois tem vários documentos. Agora veja este outro exemplo mais interessante:   

{OrdemVenda: 
[
 {id: 1,
  loja: "SP-A",
  data: "01-10-2018",
  items: [
    {id: 100, nome: "Suco", dept: "Bebidas", qtd: 10, vl: 4.50},
    {id: 200, nome: "Chá",  dept: "Bebidas", qtd: 31, vl: 6.75},
    {id: 300, nome: "Água", dept: "Bebidas", qtd: 17, vl: 1.70}
   ]},
 {id: 2,
  loja: "SP-B",
  data: "01-10-2018",
  items: [
    {id: 300, nome: "Água", dept: "Bebidas", qtd: 1, vl: 1.70},
    {id: 110, nome: "Café", dept: "Bebidas", qtd: 2, vl: 3.20}
   ]}
]}

No exemplo acima temos um documento com Ordens de Venda. Precisamente temos duas Ordens, sendo que a primeira tem três itens vendidos, e a segunda tem dois itens.

Basicamente modelamos em um documento o que seria equivalente a 5 tabelas em um modelo Relacional Normalizado em 3NF [Ordem, ItemOrdem, Loja, Produto e Departamento].

Agora quero retornar a quantidade total de itens vendidos em todas as Ordens.

Mas antes veja que este documento se lê assim: ele é composto por Ordens de Venda com os atributos [id, loja, data e items], que por sua vez, items é composto por [id, nome, dept, qtd e vl]. Então para somar a quantidade, eu tenho que percorrer as ordens, depois os items, e então buscar [qtd].

Vou usar a função JSON_TABLE para converter o JSON com Arrays para Relacional para poder somar.

Para cada array [OrdemVenda e Items] precisamos incluir a cláusula NESTED com o PATH do atributo que queremos retornar. $ indica a raíz do documento, ou o ponto onde eu parei no caso da cláusula NESTED. O asterisco entre colchetes é para retornar todos os itens do array – poderia passar 0 para retornar o primeiro item, 1 para o segundo, e assim por diante:

INSERT INTO produtos VALUES (3,
'{OrdemVenda: [
{id: 1,
loja: "SP-A",
data: "01-10-2018",
items: [
{id: 100, nome: "Suco", dept: "Bebidas", qtd: 10, vl: 4.50},
{id: 200, nome: "Chá",  dept: "Bebidas", qtd: 31, vl: 6.75},
{id: 300, nome: "Água", dept: "Bebidas", qtd: 17, vl: 1.70}
]},
{id: 2,
loja: "SP-B",
data: "01-10-2018",
items: [
{id: 300, nome: "Água", dept: "Bebidas", qtd: 1, vl: 1.70},
{id: 110, nome: "Café", dept: "Bebidas", qtd: 2, vl: 3.20}
]}
]}');

SELECT SUM(qtd)
FROM produtos p,
     JSON_TABLE(p.prodDocument, '$'
                 COLUMNS (
                          NESTED PATH '$.OrdemVenda[*]'
                          COLUMNS (
                                   NESTED PATH '$.items[*]'
                                   COLUMNS (qtd NUMBER PATH '$.qtd')
                                   )
                         )
                ) as prodDocument
WHERE id = 3

Em breve vou fazer um artigo somente sobre a manipulação de documentos JSON no banco de dados Oracle. Há muitas funcionalidades que podemos explorar.

Fiz um post há um tempo sobre algumas funções poderosas no Oracle para fazer transformação de dados. Algumas delas são para transformar documentos JSON. Veja aqui.

Já Passa da Meia-Noite, Vamos Falar dos Benefícios, Comparando com o Modelo Relacional

DOCUMENTOS SÃO SCHEMA ON READ. A principal característica de um Documento JSON no banco de dados é o fato de ele ser schema on read. Isto é, você não precisa criar uma estrutura rígida que uma tabela tem [colunas, datatypes, constraints] para depois colocar os dados: a aplicação que determina como os dados são escritos, sem a especificar previamente a estrutura – veja claramente nos exemplos que criei mais cedo neste artigo.

Essa característica permite um desenvolvimento mais flexível, por exemplo: em uma tabela “Clientes” você pode ter clientes com diferentes tipos e quantidades de contato, como telefone, celular, email, Facebook, Whatsapp, Linkedin, etc. Mas nem todos os clientes precisam ter todos os contatos.

Em um modelo Relacional, a tabela Clientes poderia ter muitas colunas com valores nulos [porque nem todos teriam todas essas formas de contato], ou teríamos que normalizar as tabelas, criando outras, e usando joins nas consultas, podendo eventualmente deixa-las mais lentas.

{"nome": "João",  "telefone" : "911112222"}
{"nome": "Mario", "telefone" : "922223333", "twitter": "@mario"}

Com a tabela [coleção] armazenando os clientes desta forma, uma query que busca “twitter” para um cliente que não tenha esta informação receberia um null.

COM DOCUMENTOS AS MUDANÇAS NO BANCO DE DADOS SÃO MAIS ÁGEIS. Não é necessário fazer DDL [ALTER TABLE] para alterar a estrutura de uma coleção, porque a estrutura na prática está nos documentos [schema on read, lembra?]. Se a aplicação quiser incluir um “Complemento de Endereço” na coleção “Clientes” por exemplo, basta incluir a nova informação na aplicação, sem precisar alterar o banco de dados. De fato essa característica torna as mudanças mais ágeis. Entretanto, alguns bancos relacionais, como o Oracle por exemplo, permite alterar uma estrutura relacional sem downtime – ainda que, também, suporta o modelo de Documentos, conforme observamos nos exemplos.

OS DOCUMENTOS MINIMIZAM O USO DE JOINS, E POR ISSO AS CONSULTAS SÃO MAIS RÁPIDAS. Uma outra característica interessante do JSON é o fato de armazenar os “relacionamentos” no mesmo documento, sem precisar recorrer a joins como ocorre no modelo Relacional. No exemplo da Ordem de Venda mais acima, temos os pais [ordens] e os filhos [itens das ordens] juntos no mesmo documento. Isto indica que ao modelar você pode pensar na forma como a aplicação vai funcionar, e então especificar um Documento que terá todas as informações contidas nele.

OS DOCUMENTOS TRABALHAM MELHOR COM API REST, A SENSAÇÃO DO MOMENTO. JSON é o formato mais comum quando se utiliza as populares APIs REST [chamadas por http]. Consultar uma coleção retorna nativamente um JSON, sem necessitar de conversão.

Diga-me o que Você Fala só para seus Amigos em Particular

SCHEMA ON READ REQUER CUIDADO. A característica schema on read promove agilidade no desenvolvimento, mas impõe maior governança por parte do desenvolvedor. Veja o seguinte exemplo:

{cliente_id:100, desc_cep: "11111-100"}
{cliente_id:101, desc_cep: "22222-100"}
{cliente_id:102, desc_cep: "33333-100"}
{cliente_id:103, cep: "44444-100"}
{cliente_id:"texto", cep: "55555-100"}

A coleção utiliza como padrão desc_cep para indicar o CEP, mas em algum momento a informação foi escrita como cep. Como o atributo cep ou desc_cep não tem a integridade exercida no banco de dados, a informação é gravada errada. O mesmo ocorre com client_id: o último está como texto ao invés de número. Um find para buscar a informação certamente não traria o resultado correto.

LEITURA DE MUITOS DOCUMENTOS DE UMA SÓ VEZ. O fato de não fazer joins porque toda a informação está contida em um mesmo documento só é vantagem quando a maioria das buscas forem para um ou poucos documentos. Suponha que uma consulta retorne 10 mil documentos: um I/O vai trazer muito mais dados no formato Relacional do que no formato JSON, mesmo fazendo joins. Isso ocorre porque no modelo Relacional as linhas estão mais próximas fisicamente, pois as tabelas são normalizadas, e portanto faz menos I/O. No JSON não se normaliza, então existe muita redundância e isto implica em maior consumo de armazenamento [menos “dados” por I/O].

COMPRESSÃO MAIS OU MENOS. Há um trade-off entre compressão e performance, em especial para o formato de Documentos. Em geral os bancos de dados de Documentos não possuem compressões colunares ou deduplicações. Eles recorrem a algoritmos mais lentos como zlib que podem até gerar uma boa redução, mas ao custo de impactar no desempenho das leituras.

CONTROLE TRANSACIONAL PRECÁRIO. Os sistemas de banco de dados puramente baseados em Documentos JSON, em geral, não suportam ACID nativamente, ou suportam com alguma restrição. Isto significa que se você transacionar sobre documentos terá que gerenciar as transações manualmente na aplicação ou por meio de alguma API. Boa sorte. 

RELACIONAMENTOS SÃO UM PARTO. Os defensores deste formato argumentam que os Documentos não precisam de relacionamentos, pois eles estão todos auto-contidos no mesmo Documento. Mas o mundo é feito de relacionamentos, e eles são complexos em especial quando são do tipo muitos-para-muitos. Veja um exemplo: em um sistema nós temos papéis e usuários. Você pode ter uma coleção “Papéis” cujos documentos contém para cada papel todos os seus usuários, ou uma coleção “Usuários” cujos documentos contém para cada usuário todos os seus papéis. Qualquer atributo de Papéis ou Usuários, como “descrição do Papel” ou “Nome do Usuário”, vai se repetir nos documentos, criando redundância e margem para bugs… é possível, entretanto, utilizar IDs ao invés de “embeddar” um documento no outro, mas aí é como no Relacional, só que desta vez com menos performance, pois o Relacional já é otimizado para relacionamentos.

Então modelar relacionamentos em um banco de dados de Documentos exige maior skill do desenvolvedor para não implementar alguma prática que depois se torne um problema — e não há estrutura schemaless que salve o esforço de mudança depois.

Para te deixar Menos Relacional por Entender Melhor como os Bancos de Documentos Funcionam. Vamos Finalmente para as Considerações Finais!

Uma coleção schemaless no banco de dados é na verdade um “schema implícito” porque o “schema” sempre existe em algum lugar: neste caso, no código da aplicação.

É necessário um schema para determinar se o correto é “DataNasc” ou “Data_Nascimento”. A flexibilidade no banco de dados vem ao custo de uma maior governança na aplicação.

Ter todas as informações contidas em um Documento para evitar joins não necessariamente é um benefício de performance. Consultas SQL com muitos joins são em geral uma deficiência de design da aplicação do que propriamente do modelo Relacional. Documentos JSON muito aninhados [documentos dentro de documentos] também podem ser um problema, e uma deficiência da aplicação.

Há restrições importantes quando armazenamos JSON em um banco de dados de Documentos, como controle transacional, tipos de leituras realizadas e inclusive tamanho do banco de dados.

O banco de dados de Documentos é um modelo bastante interessante que endereça alguns casos de usos. No entanto, na minha visão ele é um formato COMPLEMENTAR ao Relacional.

Recomendo ARMAZENAR DOCUMENTOS no banco de dados RELACIONAL quando:

[1] Houver a necessidade da aplicação criar atributos de forma ad-hoc, em runtime, com formatos diversos.

[2] A aplicação exigir que se normalize uma informação tal que gere muitas colunas com Nulos em várias linhas.

Recomendo ARMAZENAR DOCUMENTOS em um banco de dados DE DOCUMENTOS quando:

[3] A origem [quem gerou] e o fim [quem vai consumir] também forem JSON. Neste caso não faria sentido o meio [o banco de dados] ter outro formato, e em especial se o dado tem perfil transitório, isto é, ele é consumido rapidamente e fica como histórico, sem pós-processamentos [ex: IOT, logs, configurações, etc].

Em geral, minha primeira opção é optar por schema on write [Relacional], e complementar com schema on read [JSON] se for necessário.

Tenho uma preferência particular por bancos de dados Multi-Model, que permitem vários formatos nativamente ao mesmo tempo, em detrimento dos bancos de dados especializados. E por isso, em uma estrutura multi-model, você pode usar os formatos mais adequados para os mais diversos casos de uso sem muita complexidade.

Hoje em dia as aplicações tem necessidades de Dados mais poliglotas, e ter várias tecnologias especialistas aumenta bastante a complexidade.

Pense no caso de uso, e não na plataforma.

Se você pensar em “plataforma” para endereçar um caso de uso, vai acabar colocando um banco de dados muito especializado que só faz aquilo: se os requisitos mudarem, você ficará sem saída. Um banco de dados multi-model é capaz de atender vários casos de uso, e você não precisa se preocupar com a plataforma.

Você pode filtrar por Document Store no site DB-Engines, para ver quais são os bancos de dados especializados em Documentos mais populares. MongoDB é o banco de Documentos mais popular, mas é importante notar que os bancos mais populares da lista [1. Oracle e 2. MySQL] são multi-model, e aceitam Documentos JSON nativamente.

3 Funções Super Poderosas para Transformar Dados no Oracle

Se você já se deparou alguma vez com a necessidade de desnormalizar um relacionamento entre duas ou mais tabelas para criar uma lista de strings, ou criar um mapeamento para popular uma estrutura de grafo ou documento JSON, ou ainda converter do Relacional para JSON, você precisa conhecer as funções LISTAGG, JSON_OBJECT e JSON_ARRAYAGG.

Como exemplo considere as seguintes tabelas:

ALUNO (id, nome)
DISCIPLINA (id, nome)
ALUNO_DISCIPLINA (id_aluno, id_disciplina)

A tabela ALUNO_DISCIPLINA é basicamente a tabela de ligação do muitos-para-muitos entre ALUNO e DISCIPLINA, já que um aluno pode cursar várias disciplinas, e cada disciplina pode ter muitos alunos.

Então a seguinte consulta retorna:

SELECT a.nome AS nome_aluno,
       b.nome AS nome_disciplinas
FROM aluno A,
     disciplina B,
     aluno_disciplina C
WHERE a.id = c.id_aluno
  AND b.id = c.id_disciplina
ORDER BY a.nome, b.nome

Transforma os Valores de Linhas Diferentes em uma String separada por Algum Delimitador

Para desnormalizar, basta agrupar pelas colunas que devemos manter como identificador [no meu exemplo nome do aluno] e depois na função LISTAGG coloque a coluna que queremos desnormalizar e o delimitador [neste caso, vírgula]. WITHIN GROUP permite ordenar o resultado:

SELECT a.nome AS nome_aluno,
       LISTAGG(b.nome, ',') WITHIN GROUP (ORDER BY b.nome) AS lista_disciplinas
FROM aluno A,
     disciplina B,
     aluno_disciplina C
WHERE a.id = c.id_aluno
  AND b.id = c.id_disciplina
GROUP BY a.nome ORDER BY a.nome

Mais simples do que parece, certo?! Se você quiser transformar dados no modelo Relacional para um modelo de Grafos, pode usar LISTAGG para ligar as arestas aos nós com apenas uma query. Há outras variantes do LISTAGG na documentação, neste link. No Oracle ela está disponível a partir da versão 11g, e há similares em outras tecnologias.

Na versão 19c foi incluída a opção DISTINCT no LISTAGG, para o caso de você querer eliminar os valores duplicados que são listados com LISTAGG. Veja este exemplo onde eu projeto o nome do departamento e listo ao lado, separado por vírgula, todos os cargos a partir da tabela de funcionários. Como vários funcionários podem ter o mesmo cargo, ele poderá se repetir na lista. Então com distinct nós eliminamos os valores duplicados:

Console do Oracle Live SQL

Transforma Relacional em JSON, usando Atributos Simples ou com Arrays

Voltando ao exemplo dos estudantes, ao invés de apenas desnormalizar, vamos também converter para JSON, e aninhar as disciplinas, pois quero montar um Documento com os alunos e seus respectivos cursos:

SELECT JSON_OBJECT('id'          VALUE a.id,
                   'nome'        VALUE a.nome,
                   'disciplinas' VALUE JSON_ARRAYAGG(b.nome order by b.nome)) JSON_DOCUMENT
FROM aluno A,
     disciplina B,
     aluno_disciplina C
WHERE a.id = c.id_aluno
  AND b.id = c.id_disciplina
GROUP BY a.id, a.nome ORDER BY a.nome

JSON_OBJECT transforma uma coluna em um atributo JSON em um documento, e JSON_ARRAYAGG cria um array para um atributo. Temos como resultado um documento JSON com os códigos dos alunos, nomes e um array com as disciplinas que cada um cursa. Há uma variedade de funções de conversão para JSON no Oracle. Especificamente a JSON_ARRAYAGG você pode verificar a referência neste link. JSON_ARRAYAGG está disponível a partir da versão 12c.

A partir da versão 19c do a função JSON_OBJECT foi simplificada. Basta listar as colunas separadas por vírgula, ou então colocar asterisco para retornar todas as colunas. Veja:

Console do Oracle Live SQL

Para criar um array com JSON_ARRAYAGG utilizando a nova forma do JSON_OBJECT podemos fazer como no exemplo abaixo. Eu retorno o departamento com o nome de todos os funcionários:

Console do Oracle Live SQL

Agora Faz o Contrário: Transforma do JSON para o Relacional

E para converter de JSON para Relacional, utilize NESTED sobre a coluna que representa o documento JSON e as colunas que você quer projetar [também somente na versão 19c]. Eu fiz uma sub-query utilizando o exemplo acima [transformando de Relacional para JSON] e a query superior faz o inverso, transformando o JSON no Relacional:

Console do Oracle Live SQL

Utilizei o Oracle XE 18c para fazer os testes, e o SQL Developer como front-end. Todos são gratuitos e estão disponíveis para download. Para os comandos que funcionam no 19c, utilizei Oracle SQL Live direto na internet [no momento que estou escrevendo este post, a versão 19c está disponível somente no SQL Live]. Update: você pode criar uma conta no free tier da Oracle Cloud (OCI) e utilizar gratuitamente o Oracle Autonomous Database.