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 peloid
.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
). Umedge_id
é incluído como parte da chave primária para permitir mais de uma aresta de um parid
adest_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 colunaSTRING
da tabela de entrada.DYNAMIC PROPERTIES
: cria propriedades de um nó ou uma aresta de 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 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.
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 |
|
|
|
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 retornamNULL
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 |
|
|
|
|
|
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:
- 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 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
.
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
para as propriedadesPersonName
ePersonCountry
.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:
- Criação de tabelas de entrada de gráficos
- Criação de gráficos de propriedade
- Opcional: índice de travessia de borda reversa para aumentar a performance da travessia reversa
- Opcional: índice de 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 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:
@node_ids
: as chaves das linhasGraphNode
, armazenadas em um parâmetroARRAY
. Se aplicável, agrupar e classificar os dados em DMLs melhora a performance.@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
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
- Para saber mais sobre JSON, consulte Modificar dados JSON e Lista de funções JSON.
- Compare o Spanner Graph e o openCypher.
- Migrar para o Spanner Graph.