Nos aplicativos de negócios, os dados geralmente são modelados para um grande número de consultas simultâneas de baixa latência. Entretanto, se você quiser obter insights observando as tendências, acabará desejando ter um modelo de dados totalmente diferente. O meio tradicional de fazer a quadratura desse círculo era mover, transformar e carregar os dados em outro lugar, mas isso introduz sua própria série de problemas, incluindo latência inaceitável, várias fontes de verdade e muitas despesas.
Os clientes do Couchbase sabem que o serviço Analytics oferece uma maneira fácil de lidar com relatórios analíticos e de tendências em tempo real sobre os dados que eles têm em produção no momento. Um exemplo disso surgiu recentemente, quando trabalhamos com um cliente que buscava identificar a atividade de clientes de alto nível associada a um programa de fidelidade de parceiros corporativos. O modelo de documento subjacente foi claramente projetado tendo em mente o aplicativo interativo, e não a geração de relatórios. (Isso não é incomum, como você deve saber por experiência própria.) Vamos dar uma olhada rápida no problema e em como o resolvemos.
Exemplo de documento
No nosso caso, o modelo de documento (que dá suporte a um aplicativo de reservas on-line) é composto de quatro seções. A primeira seção inclui identificadores básicos de documentos e aplicativos. A segunda descreve as informações de reserva sobre uma excursão. A terceira contém detalhes sobre um ou mais itinerários associados à reserva, juntamente com os requisitos dos passageiros para um ou mais passageiros. A seção final descreve os programas de fidelidade corporativos aos quais cada um dos passageiros pode pertencer.
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
{ "_type": "reserva", "_header": { "criado": 1562888960, "fonte": "aplicativo", "versão": "v1.1" }, "reserva": { "status": "RESERVADO", "bookingType": "agência", "detalhes": { "agente": "FBL33", "contato": "Arlene", "assentos": 2, "excursão": { "embarcando": 1562958000, "equipamento": "123X", "linha": "SRF", "fromStation": { "código": "LAX", "facilityType": 1 }, "toStation": { "código": "SOL", "facilityType": 2 }, "bookingAgency": "PC", "agencyType": "3" } } }, "itinerário": [ { "daysOnboard" (dias a bordo): 1, "passageiros": [ { "passengerNumber" (número do passageiro): 1, "specialAccomodations": falso }, { "passengerNumber" (número do passageiro): 2, "specialAccomodations": falso } ], "itineraryType": "negócios" } ], "passengerDetails": [ { "loyaltyId": "aaaabbbbccccdddd", "passId": 1, "programType": { "corporatePartner": verdadeiro, "partnerId": 1 } }, { "loyaltyId": "eeeeffffgggghhhh", "passId": 2, "programType": { "corporatePartner": falso } } ] } |
Elementos de consulta
Para concluir a análise, meu cliente precisou extrair ou filtrar os seguintes campos:
- status, equipamento, embarque (convertido em formato legível por humanos), linha, _type, daysOnboard, passengerNumber, loyaltyId, partnerId
O problema, é claro, é que esses campos existem em níveis hierárquicos totalmente diferentes dentro do modelo de documento. Alguns são valores escalares, prontamente acessíveis a partir de uma simples consulta:
- status, equipamento, embarque, linha, _tipo
Outro é um elemento dentro de uma matriz (composta de itinerários de viagem), que deve ser aninhado:
- dias a bordo
Dentro dessa mesma matriz há uma segunda matriz (composta de detalhes do passageiro), cujo elemento deve ser usado como uma chave de junção:
- passengerNumber
Essa chave de junção é usada para acessar elementos de uma terceira matriz, que, por motivos de aplicativos comerciais, não está aninhada na segunda:
- loyaltyId, partnerId
Esses diferentes níveis equivalem a diferentes caminhos de acesso, o que aumenta a complexidade da análise. Felizmente, o N1QL for Analytics fornece as ferramentas sintáticas de que precisamos. Veja abaixo uma descrição passo a passo do processo que você pode usar para criar sua consulta.
Etapa 1 - seleção simples de um elemento escalar
Essa etapa deve ser bastante clara para pessoas com experiência em SQL. Usamos uma instrução select para recuperar um valor escalar do bucket de linhas. Qualificamos o campo de status como parte da seção de reserva e limitamos o número de registros a serem retornados.
1 2 3 |
selecionar reserva.status de linhas limite 1; |
Resultados da consulta:
1 2 3 4 5 |
[ { "status": "RESERVADO" } ] |
Etapa 2 - Unnest e adicionar elemento da primeira matriz
Em seguida, adicionamos dados da seção de itinerário do documento. No entanto, como esses elementos estão incorporados em uma matriz, primeiro precisamos aninhá-los.
1 2 3 4 5 |
selecionar l.reserva.status, i.dias a bordo de linhas l não registrado l.itinerário i limite 1; |
Resultados da consulta:
1 2 3 4 5 6 |
[ { "status": "RESERVADO", "daysOnboard" (dias a bordo): 1 } ] |
Etapa 3 - Unnest e adicionar elemento da segunda matriz (dentro da primeira)
Agora adicionamos elementos da matriz de passageiros incorporada. (Observe que aumentamos nosso limite para garantir que realmente estamos acessando mais de um elemento da matriz).
1 2 3 4 5 6 7 |
selecionar l.reserva.status, i.dias a bordo, p.passengerNumber de linhas l não registrado l.reserva.itinerário i não registrado i.passageiros p limite 2; |
Resultados da consulta:
1 2 3 4 5 6 7 8 9 10 11 12 |
[ { "status": "RESERVADO", "daysOnboard" (dias a bordo): 1, "passengerNumber" (número do passageiro): 1 }, { "status": "RESERVADO", "daysOnboard" (dias a bordo): 1, "passengerNumber" (número do passageiro): 2 } ] |
Etapa 4 - Unnest e adicionar elemento da terceira matriz, acessível por meio de junção
Os elementos do terceiro array (passengerDetails) devem ser aninhados e vinculados aos elementos do array passengers acima. Fazemos isso por meio da cláusula where.
1 2 3 4 5 6 7 8 9 10 |
selecionar l.reserva.status, i.dias a bordo, p.passengerNumber, pd.loyaltyId de linhas l não registrado l.itinerário i não registrado i.passageiros p não registrado l.passengerDetails pd onde p.passengerNumber = pd.passId limite 2; |
Resultados da consulta:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[ { "status": "RESERVADO", "daysOnboard" (dias a bordo): 1, "passengerNumber" (número do passageiro): 1, "loyaltyId": "aaaabbbbccccdddd" }, { "status": "RESERVADO", "daysOnboard" (dias a bordo): 1, "passengerNumber" (número do passageiro): 2, "loyaltyId": "eeeeffffgggghhhh" } ] |
Etapa 5 - Adicionar os elementos de consulta restantes
Outros campos são necessários para completar a consulta. Observe especialmente o campo _type adicionado à cláusula where. É muito provável que, em um sistema de produção, um bucket contenha documentos de vários tipos. Os resultados da consulta podem ser filtrados na própria consulta (como no exemplo abaixo) ou como parte da criação do conjunto de dados do Analytics.
1 2 3 4 5 6 7 8 9 10 11 12 |
selecionar l.reserva.status, l.reserva.detalhes.excursão.equipamentos, l.reserva.detalhes.excursão.linha, i.dias a bordo, p.passengerNumber, pd.loyaltyId, pd.programType.partnerId, millis_to_str(l.reserva.detalhes.excursão.embarque*1000) embarcando de linhas l não registrado l.itinerário i não registrado i.passageiros p não registrado l.passengerDetails pd onde p.passengerNumber = pd.passId e l._tipo = "reserva" e str_to_millis("2019-07-12T19:00:00Z") = l.reserva.detalhes.excursão.embarque*1000; |
Resultados da consulta:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
[ { "embarcando": "2019-07-12T19:00:00Z", "status": "RESERVADO", "equipamento": "123X", "linha": "SRF", "daysOnboard" (dias a bordo): 1, "passengerNumber" (número do passageiro): 1, "loyaltyId": "aaaabbbbccccdddd", "partnerId": 1 }, { "embarcando": "2019-07-12T19:00:00Z", "status": "RESERVADO", "equipamento": "123X", "linha": "SRF", "daysOnboard" (dias a bordo): 1, "passengerNumber" (número do passageiro): 2, "loyaltyId": "eeeeffffgggghhhh" } ] |
Leitura adicional
O Unnest é um recurso avançado do N1QL for Analytics, que fornece os meios para dominar vários elementos incorporados em seus dados. Mais informações sobre sua sintaxe podem ser encontradas aqui: https://docs.couchbase.com/server/6.0/analytics/3_query.html#Unnest_clauses
Um guia completo do N1QL for Analytics - tenho orgulho de possuir uma cópia assinada dele - pode ser encontrado aqui: https://www.amazon.com/SQL-Users-Tutorial-Don-Chamberlin/dp/0692184503/
Experimente você mesmo
Vá direto para https://docs.couchbase.com/server/6.0/analytics/quick-start.html#Using_docker e comece a usar imediatamente com um tutorial baseado no Docker. Ou, se preferir, faça o download do Couchbase Server 6 Enterprise nesta página: https://www.couchbase.com/downloads