Use o N1QL quando estiver em uma situação difícil com o JSON. - Confúcio
Para o modelo de dados JSON, a recomendação é pensar nas coleções como tabelas, no documento JSON como linhas desnormalizadas e nos nomes de campo como colunas - aproximadamente. Tudo isso é válido em bancos de dados como o Couchbase e o MongoDB quando as recomendações são seguidas à risca. Há muitos motivos pelos quais os usuários não seguem simplesmente esse modelo de par de valores-chave o tempo todo. Aqui estão os principais motivos.
-
- JSON é muito prolixo.
- Você deseja converter uma estrutura de dados de mapa/hashmap em que as chaves são dinâmicas.
- Dados de série temporal quando os nomes de campo são geralmente carimbos de data/hora codificados.
- Codificação baseada em dicionário
- Os formatos e padrões de documentos existentes não permitem o redesenho
Se o seu banco de dados e a linguagem de consulta não lidarem com a situação, você terá que passar por um redesenho elaborado. Além de simplesmente acessar as informações, Como você torna as consultas em JSON eficientes quando você não sabe nem mesmo o nome do campo que você precisa indexar? Felizmente, o Couchbase N1QL tem uma variedade de recursos de consulta e índice para lidar também com metadados flexíveis.
Vamos considerar esses casos de uso.
Caso de uso 1: transformação de valor.
Aqui está um exemplo de documento JSON.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
{ "cname": "Jane Smith", "dob" : "1990-01-30", "telefones" : [ "+1 510-523-3529", "+1 650-392-4923" ], "faturamento": [ { "tipo": "visto", "cardnum": "5827-2842-2847-3909", "expiração": "2019-03" }, { "tipo": "mestre", "cardnum": "6274-2542-5847-3949", "expiração": "2018-12" } ] } |
O modelo de dados JSON é descrito simplesmente como um conjunto de pares chave-valor. Cada chave é uma cadeia de caracteres, exclusiva naquele nível da hierarquia, e os valores podem ser escalares, objetos ou matrizes. Uma definição rigorosa pode ser lida aqui. O JSON também é autodescritivo, o que o torna flexível para um modelo de documento de banco de dados. Nem todo cliente precisa ter um número fixo de números de telefone, carros ou qualquer outro tipo de atributo.
As mesmas informações acima podem ser reorganizadas como o JSON abaixo sem perda de informações, mas alguns códigos implícitos
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ "Jane Smith": "1990-01-30", "casa": "+1 510-523-3529", "escritório": "+1 650-392-4923", "Faturamento": [ { "visto": "5827-2842-2847-3909", "expiração": "2019-03" }, { "mestre": "6274-2542-5847-3949", "expiração": "2018-12" } ] } |
Isso é muito bom se você estiver simplesmente colocando e definindo o documento. Não importa qual seja a estrutura do JSON. Basta arrastar o documento para frente e para trás.
Agora, vamos ver como isso afeta a consulta.
Q1: SELECIONAR * DE clientes ONDE cxname = "Jane Smith";
Com o novo modelo JSON, não há um nome de campo chamado cxname aqui.
|
1 2 3 4 |
Q2: SELECIONAR p DE pessoas p ONDE QUALQUER o IN object_names(p) SATISFAÇÕES o = "Jane Smith" FIM |
O que a magia do object_pairs() function? Ela transforma os pares JSON {"key": "value"} em uma matriz de pares nome-valor. Veja um exemplo.
|
1 2 3 4 5 6 7 8 9 |
SELECIONAR OBJECT_NAMES({"Jane Smith": "1990-01-30", "casa": "+1 510-523-3529"}) "$1": [ "Jane Smith", "casa" ] } |
A função OBJECT_NAMES() extrai a chave (aqui "Jane Smith") e retorna como um valor, que pode ser indexado. Como a função retorna não apenas um valor, mas uma matriz de "nomes de chaves" como valores, você precisa criar um índice de matriz. As consultas Q1 e Q2 fazem o mesmo trabalho para o respectivo modelo de dados. Porém, precisamos que cada uma dessas consultas seja executada em milissegundos.
Para o Q1, simplesmente criamos o índice em cxname.
CRIAR ÍNDICE ix_cxname ON clientes(cxname)
Para o Q2,
CRIAR ÍNDICE ix_people ON pessoas(DISTINTO OBJECT_NAMES(self))
Com esse índice, para o Q2, você obterá um plano que usa o índice.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
{ "#operator": "DistinctScan", "scan": { "#operator": "IndexScan3", "como": "p", "cardinalidade": 1, "custo": 0.273, "índice": "ix_people", "index_id": "4a2df8dd85543aa4", "index_projection": { "primary_key": verdadeiro }, "espaço-chave": "pessoas", "namespace": "default", "vãos": [ { "exato": verdadeiro, "intervalo": [ { "alto": "\"Jane Smith\"", "inclusão": 3, "baixo": "\"Jane Smith\"" } ] } ], |
Caso de uso 2: nomes de chaves dinâmicas.
Este caso de uso foi extraído do Couchbase post do fórum.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "id": "05a9b954-bdee-4d7f-9715-8e9e08f8cb75", "tipo": "artigo", "traduções": { "en": "Olá", "de": "Hallo", "fr": "Bonjour", "es": "Hola" } } |
Pergunta: Qual seria a melhor maneira de indexar os valores dentro de traduções dinamicamente? Ou seja, um índice genérico que indexa todas as chaves dentro do traduções objeto.
Se a necessidade for simplesmente consultar documentos em inglês o tempo todo, para consultar todos os documentos que tenham translations.en = "Hello" (Olá).
Se estiver sempre procurando por traduções para o inglês, basta criar o índice em transactions.en.
|
1 2 3 4 |
CRIAR ÍNDICE ix_tren ON info(translations.en); SELECIONAR * DE informações ONDE translation.en = "Hello"; |
Se as chaves forem dinâmicas, você não sabe qual linguagem específica estará nos dados e quais podem ser consultadas, então é preciso torná-las dinâmicas.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/* Consulta */ SELECIONAR * DE informações ONDE QUALQUER v IN OBJECT_PAIRS(traduções) SATISFAÇÕES [v.name,v.val] = ["en", "Olá"] FIM /* Índice */ CRIAR ÍNDICE ix_infoname ON informações ( DISTINTO ARRAY [v.name, v.val ] PARA v IN OBJECT_PAIRS(traduções) FIM ) |
Aqui está a explicação para verificar se o índice é de fato coletado e se os predicados são enviados para a varredura do índice.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
{ "#operator": "DistinctScan", "scan": { "#operator": "IndexScan3", "cardinalidade": 0.5, "custo": 0.1665, "índice": "ix_infoname", "index_id": "bebbfd22a022fb75", "index_projection": { "primary_key": verdadeiro }, "espaço-chave": "info", "namespace": "default", "vãos": [ { "exato": verdadeiro, "intervalo": [ { "alto": "[\"en\", \"Hello\"]", "inclusão": 3, "baixo": "[\"en\", \"Hello\"]" } ] } ], "usando": "gsi" } }, |
Não se preocupe se a definição do índice parecer um pouco mais complicada do que o normal. O Index Advisor o ajudará.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
CONSELHO SELECIONAR * DE informações ONDE QUALQUER v IN OBJECT_PAIRS(traduções) SATISFAÇÕES [v.name,v.val] = ["en", "Olá"] FIM { "index_statement" (declaração de índice): "CREATE INDEX adv_DISTINCT_object_pairs_translations_name_val ON `info`(DISTINCT ARRAY [`v`.`name`, `v`.`val`] FOR v in object_pairs((`translations`)) END)", "keyspace_alias": "info", "recommending_rule" (regra de recomendação): "As chaves de índice seguem a ordem dos tipos de predicado: 2. igualdade/nulo/ausência." } |
Você pode até mesmo adicionar expressões em cima de cada expressão que estiver avaliando.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
CONSELHO SELECIONAR * DE informações ONDE QUALQUER v IN OBJECT_PAIRS(traduções) SATISFAÇÕES [INFERIOR(v.name),INFERIOR(v.val)]. = ["en", "Olá"] FIM { "index_statement" (declaração de índice): "CREATE INDEX adv_DISTINCT_object_pairs_translations_lower_name_lower_val ON `info`(DISTINCT ARRAY [lower((`v`.`name`)), lower((`v`.`val`))] FOR v in object_pairs((`translations`)) END)", "keyspace_alias": "info", "recommending_rule" (regra de recomendação): "As chaves de índice seguem a ordem dos tipos de predicado: 2. igualdade/nulo/ausência." } |
Mais funções de objeto
O N1QL tem mais objeto e funções de dados aninhadas para ajudar com modelos de dados complexos. Confira o conjunto completo de funções de objeto e a seção funções de token.
Referências:
- Funções de objeto do Couchbae N1QL Documentação
- Couchbase Indexação de matrizes
- Couchbase blog de índice