Gerenciar dados sem esquema com o Spanner Graph

Esta página descreve como gerenciar dados sem esquema no Spanner Graph. Ele também detalha práticas recomendadas e 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 aresta podem ser adicionadas, atualizadas ou excluídas sem mudanças no esquema. A abordagem oferece suporte ao desenvolvimento iterativo e a menos sobrecarga de gerenciamento de esquemas, além de preservar a experiência familiar de consulta de gráficos.

O gerenciamento de dados sem esquema é particularmente útil nos 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ós e arestas, o que dificulta a criação e o gerenciamento de tabelas de entrada.

Modelar dados sem esquema

Com o Spanner Graph, é possível criar um gráfico de tabelas em que as linhas são mapeadas para nós e arestas. Em vez de usar tabelas separadas para cada tipo de elemento, a modelagem de dados sem esquema geralmente emprega uma única tabela de nós e uma única tabela de arestas 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, como mostrado no exemplo a seguir. Os nomes das tabelas são apenas 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 de forma exclusiva pelo id.

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

As tabelas de nós e arestas têm colunas label e properties do tipo STRING e JSON, respectivamente.

Criar um gráfico de propriedades

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 de dados sem esquema:

  • DYNAMIC LABEL: cria o rótulo de um nó ou uma aresta com base em uma coluna STRING da tabela de entrada.
  • DYNAMIC PROPERTIES: cria propriedades de um nó ou uma aresta com base em 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 do 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.

Mapear 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ós e arestas dos dados de gráfico estão bem definidos ou os rótulos e 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 tabelas de nós e arestas novas e dedicadas.
  • As limitações dos dados sem esquema impedem a adoção.

Além disso, se sua carga de trabalho for muito sensível à performance de gravação, principalmente quando as propriedades são atualizadas com frequência, usar propriedades definidas pelo esquema com tipos de dados primitivos, como STRING ou INT64, será 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áficos sem esquema usando a Graph Query Language (GQL). É possível usar as consultas de exemplo na Visão geral da consulta do Spanner Graph e na Referência do GQL com modificações limitadas.

Fazer a correspondência de nós e arestas usando rótulos

É possível corresponder nós e arestas usando a expressão de rótulo em GQL.

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

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 comparações com tipos SQL, eles precisam ser convertidos primeiro para um tipo SQL.

No exemplo a seguir, 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 order_number_str com segurança 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 estritos, 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 é 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 porque 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 a seguir, as chaves JSON city, state e country não são modeladas como propriedades porque estão aninhadas em location. No entanto, é possível acessar esses dados com um operador de acesso a campo ou um operador de subscrito.

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 diretamente nos dados do gráfico correspondente. Para mais informações sobre mutação de dados de gráficos, consulte Inserir, atualizar ou excluir dados do Spanner Graph.

Exemplos

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

Inserir dados do gráfico

O exemplo a seguir insere um nó person. Os nomes de rótulos e propriedades precisam usar 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 dele.

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 em 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.

Requisito 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 dela. Essa restrição também se aplica à tabela de arestas. Não é permitido o seguinte:

  • Definir uma tabela de nós com um rótulo dinâmico ao lado de qualquer outra tabela de nós.
  • Definir uma tabela de borda com um rótulo dinâmico ao lado de qualquer outra tabela de borda.
  • Definir várias tabelas de nós ou de arestas que usam um rótulo dinâmico.

Por exemplo, o código a seguir falha ao tentar 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 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 de 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");

É possível usar rótulos que não diferenciam maiúsculas de minúsculas para corresponder ao GraphNode ou GraphEdge.

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

Os nomes das propriedades precisam ser minúsculos

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 de 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

  • Somente 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 única em todos os nós do gráfico. Por exemplo, como uma coluna INT64 ou string UUID.

Se você tiver várias arestas entre dois nós, introduza 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 nós e arestas, 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 será exclusiva em todos os nós ou arestas. Essa técnica melhora a performance de consultas 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 o desempenho das consultas de uma propriedade usada com frequência em filtros, crie um índice secundário em uma coluna de propriedade gerada e use-o no esquema de gráfico e nas consultas.

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ó do 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, execute 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 é compatível com objetos de esquema, como restrições de verificação, para garantir 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 marcador válidos

Recomendamos usar NOT NULL na definição da coluna de rótulo 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 marcador e nomes de propriedades em letras minúsculas

Como os nomes de rótulos e propriedades precisam ser armazenados como valores em letras minúsculas, recomendamos que você faça uma destas açõ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 identificador de nó à tabela GraphNode para garantir que o identificador 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 aresta. 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 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 rótulo.

No exemplo a seguir, 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));

Forçar a exclusividade da propriedade

É possível criar restrições baseadas em propriedades que verificam se a propriedade de um nó ou aresta é única em 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 nas propriedades PersonName e PersonCountry.

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

Aplicar tipos de dados de propriedades

Você pode aplicar um tipo de dado de propriedade usando uma restrição de tipo de dado em um valor de propriedade para um rótulo, conforme mostrado no exemplo a seguir. 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 seu gráfico de propriedades tenham rótulos definidos (no esquema) e dinâmicos (derivados de dados). Você pode personalizar os 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 com base em GraphNode tem o marcador definido Entity. Além disso, cada nó tem um rótulo dinâmico determinado pelo valor na coluna de rótulo.

Em seguida, é possível escrever consultas que correspondem a nós com base em qualquer tipo de rótulo. 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 dele.

Exemplos de esquema

Use 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 de arestas inversas. A cláusula STORING (properties) inclui uma cópia das propriedades de borda, o que acelera consultas que filtram essas propriedades. Você pode omitir a cláusula STORING (properties) se suas 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ótulo 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 aplicam rótulos e propriedades em letras minúsculas. Os dois últimos exemplos usam a função JSON_KEYS. Opcionalmente, você pode aplicar 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)));

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

A modificação de propriedades dinâmicas usando funções como JSON_SET e JSON_REMOVE envolve operações de leitura/modificação/gravação. Eles podem resultar em um custo maior 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 disputa 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 de 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, agrupá-los e classificá-los em DMLs melhora o desempenho.

  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

Nesta seção, descrevemos 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 de 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 de base são definidas como propriedades. Usar TO_JSON ou SAFE_TO_JSON para retornar elementos do gráfico resulta em propriedades duplicadas. Isso acontece porque a coluna JSON (ou seja, properties) é uma propriedade definida pelo esquema, enquanto as chaves de primeiro nível de 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 a seguir:

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 duplicatas.

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 propriedade sem conversão

Problema

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

Possível causa

A consulta não converte corretamente os valores das propriedades. 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 a seguir, 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 uma chave de agrupamento sem conversão

Problema

Grouping by expressions of type JSON is not allowed.

Possível causa

No exemplo a seguir, 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 uma chave de ordenação sem conversão

Problema

ORDER BY does not support expressions of type JSON

Possível causa

No exemplo a seguir, 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 tipos durante a conversão

Problema

The provided JSON input is not an integer

Possível causa

No exemplo a seguir, a propriedade order_number_str é armazenada como um tipo de dados JSON STRING. Se você tentar fazer uma conversão para INT64, um erro será retornado.

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