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 peloid
.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
). Umedge_id
é incluído como parte da chave primária para permitir mais de uma aresta de um parid
paradest_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 colunaSTRING
da tabela de entrada.DYNAMIC PROPERTIES
: cria propriedades de um nó ou uma aresta com base em uma colunaJSON
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.
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 |
|
|
|
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 retornamNULL
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 |
|
|
|
|
|
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:
- Aplique a verificação na lógica do aplicativo.
- Crie restrições de verificação no esquema.
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
.
Adicione uma coluna gerada para
PersonName
.ALTER TABLE GraphNode ADD COLUMN person_name STRING(MAX) AS (IF(label = 'person', STRING(properties.name), NULL)) Hidden;
Adicione uma coluna gerada para
PersonCountry
.ALTER TABLE GraphNode ADD COLUMN person_country STRING(MAX) AS (IF(label = 'person', STRING(properties.country), NULL)) Hidden;
Crie um índice exclusivo
NULL_FILTERED
nas propriedadesPersonName
ePersonCountry
.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:
- Criação de tabelas de entrada de gráficos
- Criação de gráficos de propriedades
- Opcional: inverta o índice de travessia de arestas para aumentar o desempenho da travessia inversa.
- Opcional: indexar rótulos para melhorar a performance das consultas por rótulos
- Opcional: restrições de esquema para aplicar rótulos em letras minúsculas e nomes de propriedades
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:
@node_ids
: as chaves das linhasGraphNode
, armazenadas em um parâmetroARRAY
. Se aplicável, agrupá-los e classificá-los em DMLs melhora o desempenho.@is_blocked_values
: os valores correspondentes a serem atualizados, armazenados em um parâmetroARRAY
.
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
- Para saber mais sobre JSON, consulte Modificar dados JSON e Lista de funções JSON.
- Compare o Spanner Graph e o openCypher.
- Migre para o Spanner Graph.