Gerenciar dados sem esquema com o Spanner Graph

Esta página descreve como gerenciar dados sem esquema no Spanner Graph. Ele também detalha as práticas recomendadas e as dicas de solução de problemas. Recomendamos que você tenha familiaridade com o esquema e as consultas do Spanner Graph.

O gerenciamento de dados sem esquema permite criar uma definição flexível de um gráfico, em que as definições de tipo de nó e de aresta podem ser adicionadas, atualizadas ou excluídas sem mudanças no esquema. A abordagem oferece suporte ao desenvolvimento iterativo e menos sobrecarga de gerenciamento de esquema, além de preservar a experiência de consulta de gráfico conhecida.

O gerenciamento de dados sem esquema é particularmente útil para os seguintes cenários:

  • Você gerencia gráficos com mudanças frequentes, como atualizações e adições de rótulos e propriedades de elementos.
  • Seu gráfico tem muitos tipos de nó e aresta, o que dificulta a criação e o gerenciamento de tabelas de entrada.

Modelar dados sem esquema

O Spanner Graph permite criar um gráfico a partir de tabelas em que as linhas são mapeadas para nós e bordas. Em vez de usar tabelas separadas para cada tipo de elemento, a modelagem de dados sem esquema geralmente emprega uma única tabela de nó e uma única tabela de aresta com uma coluna STRING para o rótulo e uma coluna JSON para propriedades.

Criar tabelas de entrada

É possível criar uma única tabela GraphNode e uma única tabela GraphEdge para armazenar dados sem esquema, conforme mostrado no exemplo a seguir. Os nomes das tabelas são para fins ilustrativos, e você pode escolher os seus.

CREATE TABLE GraphNode (
  id INT64 NOT NULL,
  label STRING(MAX) NOT NULL,
  properties JSON,
) PRIMARY KEY (id);

CREATE TABLE GraphEdge (
  id INT64 NOT NULL,
  dest_id INT64 NOT NULL,
  edge_id INT64 NOT NULL,
  label STRING(MAX) NOT NULL,
  properties JSON,
) PRIMARY KEY (id, dest_id, edge_id),
  INTERLEAVE IN PARENT GraphNode;

Este exemplo faz o seguinte:

  • Armazena todos os nós em uma única tabela GraphNode, identificada exclusivamente pelo id.

  • Armazena todas as arestas em uma única tabela GraphEdge, identificada exclusivamente pela combinação de origem (id), destino (dest_id) e o próprio identificador (edge_id). Um edge_id é incluído como parte da chave primária para permitir mais de uma aresta de um par id a dest_id.

As tabelas de nó e de aresta têm as próprias colunas label e properties dos tipos STRING e JSON, respectivamente.

Criar um gráfico de propriedade

Com a instrução CREATE PROPERTY GRAPH, as tabelas de entrada na seção anterior são mapeadas como nós e arestas. É necessário usar as seguintes cláusulas para definir rótulos e propriedades para dados sem esquema:

  • DYNAMIC LABEL: cria o rótulo de um nó ou de uma aresta de uma coluna STRING da tabela de entrada.
  • DYNAMIC PROPERTIES: cria propriedades de um nó ou uma aresta de uma coluna JSON da tabela de entrada.

O exemplo a seguir mostra como criar um gráfico usando essas cláusulas:

CREATE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNode
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  )
  EDGE TABLES (
    GraphEdge
      SOURCE KEY (id) REFERENCES GraphNode(id)
      DESTINATION KEY (dest_id) REFERENCES GraphNode(id)
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  );

Rótulo dinâmico

A cláusula DYNAMIC LABEL designa uma coluna de tipo de dados STRING para armazenar os valores de rótulo.

Por exemplo, em uma linha GraphNode, se a coluna label tiver um valor person, ela será mapeada para um nó Person no gráfico. Da mesma forma, em uma linha GraphEdge, se a coluna de rótulo tiver um valor de owns, ela será mapeada para uma aresta Owns no gráfico.

Mapeamento de um rótulo "GraphNode" para um rótulo "GraphEdge"

Propriedades dinâmicas

A cláusula DYNAMIC PROPERTIES designa uma coluna de tipo de dados JSON para armazenar propriedades. As chaves JSON são os nomes das propriedades, e os valores JSON são os valores das propriedades.

Por exemplo, quando a coluna properties de uma linha GraphNode tem o valor JSON '{"name": "David", "age": 43}', ela é mapeada para um nó com as propriedades age e name, com 43 e "David" como valores de propriedade.

Quando não usar o gerenciamento de dados sem esquema

Talvez você não queira usar o gerenciamento de dados sem esquema nos seguintes cenários:

  • Os tipos de nó e aresta dos dados do gráfico estão bem definidos ou os rótulos e as propriedades não precisam de atualizações frequentes.
  • Seus dados já estão armazenados no Spanner, e você prefere criar gráficos com tabelas atuais em vez de introduzir novas tabelas de nó e de borda dedicadas.
  • As limitações de dados sem esquema impedem a adoção.

Além disso, se a carga de trabalho for altamente sensível ao desempenho de gravação, principalmente quando as propriedades forem atualizadas com frequência, usar propriedades definidas pelo esquema com tipos de dados primitivos, como STRING ou INT64, é mais eficaz do que usar propriedades dinâmicas com o tipo JSON.

Para mais informações sobre como definir o esquema de gráfico sem usar rótulos e propriedades de dados dinâmicos, consulte a Visão geral do esquema do Spanner Graph.

Consultar dados de gráfico sem esquema

É possível consultar dados de gráfico sem esquema usando a linguagem de consulta de gráfico (GQL). É possível usar as consultas de amostra na Visão geral da consulta de gráfico do Spanner e na Referência do GQL com modificações limitadas.

Corresponder nós e bordas usando rótulos

É possível fazer a correspondência de nós e arestas usando a expressão de rótulo no GQL.

A consulta a seguir corresponde a nós e arestas conectados que têm os valores account e transfers na coluna de rótulos.

GRAPH FinGraph
MATCH (a:Account {id: 1})-[t:Transfers]->(d:Account)
RETURN COUNT(*) AS result_count;

Acessar propriedades

As chaves e os valores de nível superior do tipo de dados JSON são modelados como propriedades, como age e name no exemplo a seguir.

JSON document Properties

   {
     "name": "Tom",
     "age": 43,
   }
"name": "Tom"
"age": 34

O exemplo a seguir mostra como acessar a propriedade name do nó Person.

GRAPH FinGraph
MATCH (person:Person {id: 1})
RETURN person.name;

Isso retorna resultados semelhantes a estes:

JSON"Tom"

Converter tipos de dados de propriedades

As propriedades são tratadas como valores do tipo de dados JSON. Em alguns casos, como para comparações com tipos SQL, elas precisam ser convertidas em um tipo SQL primeiro.

No exemplo abaixo, a consulta realiza as seguintes conversões de tipo de dados:

  • Converte a propriedade is_blocked em um tipo booleano para avaliar a expressão.
  • Converte a propriedade order_number_str em um tipo de string e a compara com o valor literal "302290001255747".
  • Usa a função LAX_INT64 para converter com segurança order_number_str em um número inteiro como o tipo de retorno.
GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->()
WHERE BOOL(a.is_blocked) AND STRING(t.order_number_str) = "302290001255747"
RETURN LAX_INT64(t.order_number_str) AS order_number_as_int64;

Isso retorna resultados semelhantes a estes:

+-----------------------+
| order_number_as_int64 |
+-----------------------+
| 302290001255747       |
+-----------------------+

Em cláusulas como GROUP BY e ORDER BY, também é necessário converter o tipo de dados JSON. O exemplo a seguir converte a propriedade city em um tipo de string, permitindo que ela seja usada para agrupamento.

GRAPH FinGraph
MATCH (person:Person {country: "South Korea"})
RETURN STRING(person.city) as person_city, COUNT(*) as cnt
LIMIT 10

Dicas para converter tipos de dados JSON em tipos de dados SQL:

  • Conversores rígidos, como INT64, realizam verificações rigorosas de tipo e valor. Isso é recomendado quando o tipo de dados JSON é conhecido e aplicado, por exemplo, usando restrições de esquema para aplicar o tipo de dados da propriedade.
  • Conversores flexíveis, como LAX_INT64, convertem o valor com segurança, quando possível, e retornam NULL quando a conversão não for viável. Isso é recomendado quando uma verificação rigorosa não é necessária ou os tipos são difíceis de aplicar.

Leia mais sobre a conversão de dados nas dicas de solução de problemas.

Filtrar por valores de propriedade

Nos filtros de propriedade, os parâmetros de filtro são tratados como valores do tipo de dados JSON. Por exemplo, na consulta a seguir, is_blocked é tratado como um boolean JSON e order_number_str como um string JSON.

GRAPH FinGraph
MATCH (a:Account {is_blocked: false})-[t:Transfers {order_number_str:"302290001255747"}]->()
RETURN a.id AS account_id;

Isso retorna resultados semelhantes a estes:

+-----------------------+
| account_id            |
+-----------------------+
| 7                     |
+-----------------------+

O parâmetro de filtro precisa corresponder ao tipo e ao valor da propriedade. Por exemplo, quando o parâmetro de filtro order_number_str é um número inteiro, nenhuma correspondência é encontrada, já que a propriedade é um string JSON.

GRAPH FinGraph
MATCH (a:Account {is_blocked: false})-[t:Transfers {order_number_str: 302290001255747}]->()
RETURN t.order_number_str;

Acessar propriedades JSON aninhadas

Chaves e valores JSON aninhados não são modelados como propriedades. No exemplo abaixo, as chaves JSON city, state e country não são modeladas como propriedades porque estão aninhadas em location. No entanto, é possível acessá-los com um operador de acesso a campo JSON ou um operador de subscrito JSON.

JSON document Properties

   {
     "name": "Tom",
     "age": 43,
     "location": {
       "city": "New York",
       "state": "NY",
       "country": "USA",
     }
   }
"name": "Tom"
"age": 34
"location": {
  "city": "New York",
  "state": "NY",
  "country": "USA",
}

O exemplo a seguir mostra como acessar propriedades aninhadas com o operador de acesso do campo JSON.

GRAPH FinGraph
MATCH (person:Person {id: 1})
RETURN STRING(person.location.city);

Isso retorna resultados semelhantes a estes:

"New York"

Modificar dados sem esquema

O Spanner Graph mapeia dados de tabelas para nós e arestas de gráficos. Quando você muda os dados da tabela de entrada, isso causa mutações diretas nos dados correspondentes do gráfico. Para mais informações sobre a mutação de dados do gráfico, consulte Inserir, atualizar ou excluir dados do Spanner Graph.

Exemplos

Esta seção oferece exemplos de como criar, atualizar e excluir dados de gráfico.

Inserir dados do gráfico

O exemplo a seguir insere um nó person. Os nomes de rótulos e propriedades precisam estar em letras minúsculas.

INSERT INTO GraphNode (id, label, properties)
VALUES (4, "person", JSON'{"name": "David", "age": 43}');

Atualizar dados do gráfico

O exemplo a seguir atualiza um nó Account e usa a função JSON_SET para definir a propriedade is_blocked.

UPDATE GraphNode
SET properties = JSON_SET(
  properties,
  '$.is_blocked', false
)
WHERE label = "account" AND id = 16;

O exemplo a seguir atualiza um nó person com um novo conjunto de propriedades.

UPDATE GraphNode
SET properties = JSON'{"name": "David", "age": 43}'
WHERE label = "person" AND id = 4;

O exemplo a seguir usa a função JSON_REMOVE para remover a propriedade is_blocked de um nó Account. Após a execução, todas as outras propriedades permanecem inalteradas.

UPDATE GraphNode
SET properties = JSON_REMOVE(
  properties,
  '$.is_blocked'
)
WHERE label = "account" AND id = 16;

Excluir dados de gráficos

O exemplo a seguir exclui a aresta Transfers nos nós Account que foram transferidos para contas bloqueadas.

DELETE FROM GraphEdge
WHERE label = "transfers" and id IN {
  GRAPH FinGraph
  MATCH (a:Account)-[:Transfers]->{1,2}(:Account {is_blocked: TRUE})
  RETURN a.id
}

Limitações

Esta seção lista as limitações do uso do gerenciamento de dados sem esquema.

Requisitos de tabela única para rótulo dinâmico

Só é possível ter uma tabela de nós se um rótulo dinâmico for usado na definição. Essa restrição também se aplica à tabela de borda. Não é permitido o seguinte:

  • Definir uma tabela de nós com um rótulo dinâmico junto a outras tabelas de nós.
  • Definir uma tabela de borda com um rótulo dinâmico junto a outras tabelas de borda.
  • Definir várias tabelas de nó ou de aresta que usam um rótulo dinâmico.

Por exemplo, o código a seguir falha quando tenta criar vários nós de gráfico com rótulos dinâmicos.

CREATE OR REPLACE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNodeOne
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties),
    GraphNodeTwo
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties),
    Account
      LABEL Account PROPERTIES(create_time)
  )
  EDGE TABLES (
    ...
  );

Os nomes dos rótulos precisam estar em letras minúsculas

Os valores de string de rótulo precisam ser armazenados em letras minúsculas para serem correspondidos. Recomendamos que você aplique essa regra no código do aplicativo ou usando restrições do esquema.

Embora os valores de string de rótulo precisem ser armazenados em letras minúsculas, eles não diferenciam maiúsculas de minúsculas quando são referenciados em uma consulta.

O exemplo a seguir mostra como inserir rótulos em valores minúsculos:

INSERT INTO GraphNode (id, label) VALUES (1, "account");
INSERT INTO GraphNode (id, label) VALUES (2, "account");

Você pode usar rótulos sem distinção entre maiúsculas e minúsculas para corresponder a GraphNode ou GraphEdge.

GRAPH FinGraph
MATCH (accnt:Account {id: 1})-[:Transfers]->(dest_accnt:Account)
RETURN dest_accnt.id;

Os nomes das propriedades precisam ser em letras minúsculas

Os nomes das propriedades precisam ser armazenados em letras minúsculas. Recomendamos que você aplique essa regra no código do aplicativo ou usando restrições de esquema.

Embora os nomes de propriedades precisem ser armazenados em letras minúsculas, eles não diferenciam maiúsculas de minúsculas quando você os referencia na consulta.

O exemplo a seguir insere as propriedades name e age usando letras minúsculas.

INSERT INTO GraphNode (id, label, properties)
VALUES (25, "person", JSON '{"name": "Kim", "age": 27}');

No texto da consulta, os nomes das propriedades não diferenciam maiúsculas de minúsculas. Por exemplo, é possível usar Age ou age para acessar a propriedade.

GRAPH FinGraph
MATCH (n:Person {Age: 27})
RETURN n.id;

Outras limitações

  • Apenas as chaves de nível superior do tipo de dados JSON são modeladas como propriedades.
  • Os tipos de dados de propriedade precisam estar em conformidade com as especificações do tipo JSON do Spanner.

Práticas recomendadas

Esta seção descreve as práticas recomendadas para modelar dados sem esquema.

Definições de chave primária para nós e arestas

A chave de um nó precisa ser exclusiva em todos os nós do gráfico. Por exemplo, como uma coluna INT64 ou de string UUID.

Se você tiver várias arestas entre dois nós, será necessário introduzir um identificador exclusivo para a aresta. O exemplo de esquema usa uma coluna INT64 edge_id de lógica de aplicativo.

Ao criar o esquema para tabelas de adjacência e de nó, você pode incluir a coluna label como uma coluna de chave primária, se o valor for imutável. Se você fizer isso, a chave composta formada por todas as colunas de chave precisa ser exclusiva em todos os nós ou arestas. Essa técnica melhora a performance das consultas que são filtradas apenas por rótulo.

Para mais informações sobre a escolha da chave primária, consulte Escolher uma chave primária.

Índice secundário para uma propriedade acessada com frequência

Para melhorar a performance da consulta de uma propriedade usada com frequência em filtros, é possível criar um índice secundário em uma coluna de propriedade gerada e usá-lo em esquemas e consultas de gráfico.

O exemplo a seguir adiciona uma coluna age gerada à tabela GraphNode para um nó person. O valor é NULL para nós sem o rótulo person.

ALTER TABLE GraphNode
ADD COLUMN person_age INT64 AS
(IF (label = "person", LAX_INT64(properties.age), NULL));

Em seguida, ele cria um NULL FILTERED INDEX para person_age e o intercala na tabela GraphNode para acesso local.

CREATE NULL_FILTERED INDEX IdxPersonAge
ON GraphNode(id, label, person_age), INTERLEAVE IN GraphNode;

A tabela GraphNode agora inclui novas colunas disponíveis como propriedades de nó de gráfico. Para refletir isso na definição do gráfico de propriedades, use a instrução CREATE OR REPLACE PROPERTY GRAPH. Isso recompila a definição e inclui a nova coluna person_age como uma propriedade.

A instrução a seguir recompila a definição e inclui a nova coluna person_age como uma propriedade.

CREATE OR REPLACE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNode
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  )
  EDGE TABLES (
    GraphEdge
      SOURCE KEY (id) REFERENCES GraphNode (id)
      DESTINATION KEY (dest_id) REFERENCES GraphNode (id)
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  );

O exemplo a seguir executa uma consulta com a propriedade indexada.

GRAPH FinGraph
MATCH (person:Person {person_age: 43})
RETURN person.id, person.name;

Opcionalmente, é possível executar o comando ANALYZE após a criação do índice para que o otimizador de consultas seja atualizado com as estatísticas mais recentes do banco de dados.

Verificar restrições de integridade de dados

O Spanner oferece suporte a objetos de esquema, como restrições de verificação, para aplicar a integridade dos dados de rótulo e propriedade. Esta seção lista recomendações para restrições de verificação que podem ser usadas com dados sem esquema.

Aplicar valores de rótulo válidos

Recomendamos o uso de NOT NULL na definição da coluna de rótulos para evitar valores indefinidos.

CREATE TABLE GraphNode (
  id INT64 NOT NULL,
  label STRING(MAX) NOT NULL,
  properties JSON,
) PRIMARY KEY (id);

Aplicar os valores de rótulos e nomes de propriedades em letras minúsculas

Como os nomes de rótulos e propriedades precisam ser armazenados como valores em minúsculas, recomendamos uma destas opções:

No momento da consulta, o rótulo e o nome da propriedade não diferenciam maiúsculas de minúsculas.

O exemplo a seguir adiciona uma restrição de rótulo de nó à tabela GraphNode para garantir que o rótulo esteja em letras minúsculas.

ALTER TABLE GraphNode ADD CONSTRAINT NodeLabelLowerCaseCheck
CHECK(LOWER(label) = label);

O exemplo a seguir adiciona uma restrição de verificação ao nome da propriedade de borda. A verificação usa JSON_KEYS para acessar as chaves de nível superior. COALESCE converte a saída em uma matriz vazia se JSON_KEYS retornar NULL e, em seguida, verifica se cada chave está em letras minúsculas.

ALTER TABLE GraphEdge ADD CONSTRAINT EdgePropertiesLowerCaseCheck
CHECK(NOT array_includes(COALESCE(JSON_KEYS(properties, 1), []), key->key<>LOWER(key)));

Aplicar a existência da propriedade

É possível criar uma restrição que verifica se uma propriedade existe para um identificador.

No exemplo abaixo, a restrição verifica se um nó person tem uma propriedade name.

ALTER TABLE GraphNode
ADD CONSTRAINT NameMustExistForPersonConstraint
CHECK (IF(label = 'person', properties.name IS NOT NULL, TRUE));

Aplicar a exclusividade da propriedade

É possível criar restrições baseadas em propriedades que verificam se a propriedade de um nó ou aresta é exclusiva entre nós ou arestas com o mesmo rótulo. Para isso, use um ÍNDICE ÚNICO nas colunas geradas de propriedades.

No exemplo a seguir, o índice exclusivo verifica se as propriedades name e country combinadas são exclusivas para qualquer nó person.

  1. Adicione uma coluna gerada para PersonName.

    ALTER TABLE GraphNode
    ADD COLUMN person_name STRING(MAX)
    AS (IF(label = 'person', STRING(properties.name), NULL)) Hidden;
    
  2. Adicione uma coluna gerada para PersonCountry.

    ALTER TABLE GraphNode
    ADD COLUMN person_country STRING(MAX)
    AS (IF(label = 'person', STRING(properties.country), NULL)) Hidden;
    
  3. Crie um índice exclusivo NULL_FILTERED para as propriedades PersonName e PersonCountry.

    CREATE UNIQUE NULL_FILTERED INDEX NameAndCountryMustBeUniqueForPerson
    ON GraphNode (person_name, person_country);
    

Aplicar tipos de dados de propriedade

É possível aplicar um tipo de dados de propriedade usando uma restrição de tipo de dados em um valor de propriedade para um rótulo, conforme mostrado no exemplo abaixo. Este exemplo usa a função JSON_TYPE para verificar se a propriedade name do rótulo person usa o tipo STRING.

ALTER TABLE GraphNode
ADD CONSTRAINT PersonNameMustBeStringTypeConstraint
CHECK (IF(label = 'person', JSON_TYPE(properties.name) = 'string', TRUE));

Como combinar rótulos definidos e dinâmicos

O Spanner permite que os nós no gráfico de propriedades tenham rótulos definidos (definidos no esquema) e dinâmicos (derivados dos dados). É possível personalizar rótulos para usar essa flexibilidade.

Considere o esquema a seguir que mostra a criação da tabela GraphNode:

CREATE OR REPLACE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNode
      LABEL Entity -- Defined label
      DYNAMIC LABEL (label) -- Dynamic label from data column 'label'
      DYNAMIC PROPERTIES (properties)
  );

Aqui, cada nó criado a partir de GraphNode tem o rótulo Entity definido. Além disso, cada nó tem um rótulo dinâmico determinado pelo valor na coluna de rótulos.

Em seguida, você pode escrever consultas que correspondem a nós com base em qualquer tipo de marcador. Por exemplo, a consulta a seguir encontra nós usando o rótulo Entity definido:

GRAPH FinGraph
MATCH (node:Entity {id: 1}) -- Querying by the defined label
RETURN node.name;

Embora essa consulta use o rótulo definido Entity, lembre-se de que o nó correspondente também tem um rótulo dinâmico com base nos dados.

Exemplos de esquema

Você pode usar os exemplos de esquema nesta seção como modelos para criar seus próprios esquemas. Os principais componentes do esquema incluem:

O exemplo a seguir mostra como criar tabelas de entrada e um gráfico de propriedades:

CREATE TABLE GraphNode (
  id INT64 NOT NULL,
  label STRING(MAX) NOT NULL,
  properties JSON
) PRIMARY KEY (id);

CREATE TABLE GraphEdge (
  id INT64 NOT NULL,
  dest_id INT64 NOT NULL,
  edge_id INT64 NOT NULL,
  label STRING(MAX) NOT NULL,
  properties JSON
) PRIMARY KEY (id, dest_id, edge_id),
  INTERLEAVE IN PARENT GraphNode;

CREATE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNode
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  )
  EDGE TABLES (
    GraphEdge
      SOURCE KEY (id) REFERENCES GraphNode(id)
      DESTINATION KEY (dest_id) REFERENCES GraphNode(id)
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  );

O exemplo a seguir usa um índice para melhorar a travessia reversa de arestas. A cláusula STORING (properties) inclui uma cópia das propriedades de borda, o que acelera as consultas que filtram essas propriedades. Você pode omitir a cláusula STORING (properties) se as consultas não se beneficiarem dela.

CREATE INDEX R_EDGE ON GraphEdge (dest_id, id, edge_id) STORING (properties),
INTERLEAVE IN GraphNode;

O exemplo a seguir usa um índice de rótulos para acelerar a correspondência de nós por rótulos.

CREATE INDEX IDX_NODE_LABEL ON GraphNode (label);

O exemplo a seguir adiciona restrições que exigem rótulos e propriedades em minúsculas. Os dois últimos exemplos usam a função JSON_KEYS. Opcionalmente, é possível forçar a verificação de letras minúsculas na lógica do aplicativo.

ALTER TABLE GraphNode ADD CONSTRAINT node_label_lower_case
CHECK(LOWER(label) = label);

ALTER TABLE GraphEdge ADD CONSTRAINT edge_label_lower_case
CHECK(LOWER(label) = label);

ALTER TABLE GraphNode ADD CONSTRAINT node_property_keys_lower_case
CHECK(
  NOT array_includes(COALESCE(JSON_KEYS(properties, 1), []), key->key<>LOWER(key)));

ALTER TABLE GraphEdge ADD CONSTRAINT edge_property_keys_lower_case
CHECK(
  NOT array_includes(COALESCE(JSON_KEYS(properties, 1), []), key->key<>LOWER(key)));

Como otimizar atualizações em lote de propriedades dinâmicas com DML

Modificar propriedades dinâmicas usando funções como JSON_SET e JSON_REMOVE envolve operações de leitura/modificação/gravação. Eles podem gerar custos mais altos em comparação com a atualização de propriedades do tipo STRING ou INT64.

Se as cargas de trabalho envolverem atualizações em lote de propriedades dinâmicas usando DML, considere as seguintes recomendações para melhorar o desempenho:

  • Atualize várias linhas em uma única instrução DML em vez de processar linhas individualmente.

  • Ao atualizar um intervalo de chaves amplo, agrupe e classifique as linhas afetadas pelas chaves primárias. Atualizar intervalos não sobrepostos com cada DML reduz a contenção de bloqueio.

  • Use parâmetros de consulta em instruções DML em vez de codificá-los para melhorar o desempenho.

Com base nessas sugestões, o exemplo a seguir atualiza a propriedade is_blocked para 100 nós em uma única instrução DML. Os parâmetros de consulta incluem o seguinte:

  1. @node_ids: as chaves das linhas GraphNode, armazenadas em um parâmetro ARRAY. Se aplicável, agrupar e classificar os dados em DMLs melhora a performance.

  2. @is_blocked_values: os valores correspondentes a serem atualizados, armazenados em um parâmetro ARRAY.

UPDATE GraphNode
SET properties = JSON_SET(
  properties,
  '$.is_blocked',
  CASE id
    WHEN @node_ids[OFFSET(0)] THEN @is_blocked_values[OFFSET(0)]
    WHEN @node_ids[OFFSET(1)] THEN @is_blocked_values[OFFSET(1)]
    ...
    WHEN @node_ids[OFFSET(99)] THEN @is_blocked_values[OFFSET(99)]
  END,
  create_if_missing => TRUE)
WHERE id IN UNNEST(@node_ids)

Resolver problemas

Esta seção descreve como resolver problemas com dados sem esquema.

A propriedade aparece mais de uma vez no resultado TO_JSON

Problema

O nó a seguir modela as propriedades birthday e name como propriedades dinâmicas na coluna JSON. Propriedades duplicadas de birthday e name aparecem no resultado JSON do elemento do gráfico.

GRAPH FinGraph
MATCH (n: Person {id: 14})
RETURN SAFE_TO_JSON(n) AS n;

Isso retorna resultados semelhantes a estes:

{
  ,
  "properties": {
    "birthday": "1991-12-21 00:00:00",
    "name": "Alex",
    "id": 14,
    "label": "person",
    "properties": {
      "birthday": "1991-12-21 00:00:00",
      "name": "Alex"
    }
  }
  
}

Possível causa

Por padrão, todas as colunas da tabela base são definidas como propriedades. O uso de TO_JSON ou SAFE_TO_JSON para retornar elementos de gráfico resulta em propriedades duplicadas. Isso ocorre porque a coluna JSON (ou seja, properties) é uma propriedade definida pelo esquema, enquanto as chaves de primeiro nível da JSON são modeladas como propriedades dinâmicas.

Solução recomendada

Para evitar esse comportamento, use a cláusula PROPERTIES ALL COLUMNS EXCEPT para excluir a coluna properties ao definir propriedades no esquema, conforme mostrado no exemplo abaixo:

CREATE OR REPLACE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNode
      PROPERTIES ALL COLUMNS EXCEPT (properties)
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  );

Após a mudança de esquema, os elementos de gráfico retornados do tipo de dados JSON não têm duplicações.

GRAPH FinGraph
MATCH (n: Person {id: 1})
RETURN TO_JSON(n) AS n;

Essa consulta retorna o seguinte:

{
  
  "properties": {
    "birthday": "1991-12-21 00:00:00",
    "name": "Alex",
    "id": 1,
    "label": "person",
  }
}

Problemas comuns quando os valores de propriedade não são convertidos corretamente

Uma correção comum para os problemas a seguir é sempre usar conversões de valor de propriedade ao usar uma propriedade em uma expressão de consulta.

Comparação de valores de propriedades sem conversão

Problema

No matching signature for operator = for argument types: JSON, STRING

Possível causa

A consulta não converte os valores de propriedade corretamente. Por exemplo, a propriedade name não é convertida para o tipo STRING na comparação:

GRAPH FinGraph
MATCH (p:Person)
WHERE p.name = "Alex"
RETURN p.id;

Solução recomendada

Para corrigir esse problema, use uma conversão de valor antes da comparação.

GRAPH FinGraph
MATCH (p:Person)
WHERE STRING(p.name) = "Alex"
RETURN p.id;

Isso retorna resultados semelhantes a estes:

+------+
| id   |
+------+
| 1    |
+------+

Como alternativa, use um filtro de propriedade para simplificar as comparações de igualdade em que a conversão de valor é feita automaticamente. O tipo do valor ("Alex") precisa corresponder exatamente ao tipo STRING da propriedade em JSON.

GRAPH FinGraph
MATCH (p:Person {name: 'Alex'})
RETURN p.id;

Isso retorna resultados semelhantes a estes:

+------+
| id   |
+------+
| 1    |
+------+

Uso do valor da propriedade RETURN DISTINCT sem conversão

Problema

Column order_number_str of type JSON cannot be used in `RETURN DISTINCT

Possível causa

No exemplo abaixo, order_number_str não foi convertido antes de ser usado na instrução RETURN DISTINCT:

GRAPH FinGraph
MATCH -[t:Transfers]->
RETURN DISTINCT t.order_number_str AS order_number_str;

Solução recomendada

Para corrigir esse problema, use uma conversão de valor antes de RETURN DISTINCT.

GRAPH FinGraph
MATCH -[t:Transfers]->
RETURN DISTINCT STRING(t.order_number_str) AS order_number_str;

Isso retorna resultados semelhantes a estes:

+-----------------+
| order_number_str|
+-----------------+
| 302290001255747 |
| 103650009791820 |
| 304330008004315 |
| 304120005529714 |
+-----------------+

Propriedade usada como chave de agrupamento sem conversão

Problema

Grouping by expressions of type JSON is not allowed.

Possível causa

No exemplo abaixo, t.order_number_str não é convertido antes de ser usado para agrupar objetos JSON:

GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN t.order_number_str, COUNT(*) AS total_transfers;

Solução recomendada

Para corrigir esse problema, use uma conversão de valor antes de usar a propriedade como uma chave de agrupamento.

GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN STRING(t.order_number_str) AS order_number_str, COUNT(*) AS total_transfers;

Isso retorna resultados semelhantes a estes:

+-----------------+------------------+
| order_number_str | total_transfers |
+-----------------+------------------+
| 302290001255747 |                1 |
| 103650009791820 |                1 |
| 304330008004315 |                1 |
| 304120005529714 |                2 |
+-----------------+------------------+

Propriedade usada como chave de ordenação sem conversão

Problema

ORDER BY does not support expressions of type JSON

Possível causa

No exemplo abaixo, t.amount não é convertido antes de ser usado para ordenar resultados:

GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN a.Id AS from_account, b.Id AS to_account, t.amount
ORDER BY t.amount DESC
LIMIT 1;

Solução recomendada

Para corrigir esse problema, faça uma conversão em t.amount na cláusula ORDER BY.

GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN a.Id AS from_account, b.Id AS to_account, t.amount
ORDER BY DOUBLE(t.amount) DESC
LIMIT 1;

Isso retorna resultados semelhantes a estes:

+--------------+------------+--------+
| from_account | to_account | amount |
+--------------+------------+--------+
|           20 |          7 | 500    |
+--------------+------------+--------+

Incompatibilidade de tipo durante a conversão

Problema

The provided JSON input is not an integer

Possível causa

No exemplo abaixo, a propriedade order_number_str é armazenada como um tipo de dados JSON STRING. Se você tentar realizar uma conversão para INT64, ela retornará um erro.

GRAPH FinGraph
MATCH -[e:Transfers]->
WHERE INT64(e.order_number_str) = 302290001255747
RETURN e.amount;

Solução recomendada

Para corrigir esse problema, use o conversor de valor exato que corresponde ao tipo de valor.

GRAPH FinGraph
MATCH -[e:Transfers]->
WHERE STRING(e.order_number_str) = "302290001255747"
RETURN e.amount;

Isso retorna resultados semelhantes a estes:

+-----------+
| amount    |
+-----------+
| JSON"200" |
+-----------+

Como alternativa, use um conversor flexível quando o valor puder ser convertido para o tipo de destino, conforme mostrado no exemplo a seguir:

GRAPH FinGraph
MATCH -[e:Transfers]->
WHERE LAX_INT64(e.order_number_str) = 302290001255747
RETURN e.amount;

Isso retorna resultados semelhantes a estes:

+-----------+
| amount    |
+-----------+
| JSON"200" |
+-----------+

A seguir