En esta página, se explica cómo administrar datos sin esquema en Spanner Graph. También proporciona prácticas recomendadas y sugerencias para solucionar problemas. Te recomendamos que te familiarices con el esquema y las consultas de Spanner Graph.
La administración de datos sin esquema te permite crear una definición de gráfico flexible. Puedes agregar, actualizar o borrar definiciones de tipos de nodos y aristas sin realizar cambios en el esquema. Este enfoque admite el desarrollo iterativo y reduce la sobrecarga de administración del esquema, a la vez que conserva la experiencia familiar de las consultas de gráficos.
La administración de datos sin esquema es útil en las siguientes situaciones:
Administrar gráficos con cambios frecuentes, como actualizaciones y adiciones de etiquetas y propiedades de elementos
Gráficos con muchos tipos de nodos y aristas, lo que dificulta la creación y administración de tablas de entrada.
Para obtener más información sobre cuándo usar la administración de datos sin esquema, consulta Consideraciones para la administración de datos sin esquema.
Cómo modelar datos sin esquema
Spanner Graph te permite crear un gráfico a partir de tablas que asignan filas a nodos y aristas.
En lugar de usar tablas separadas para cada tipo de elemento, el modelado de datos sin esquema suele emplear una sola tabla de nodos y una sola tabla de aristas con una columna STRING
para la etiqueta y una columna JSON
para las propiedades.
Crea tablas de entrada
Puedes crear una sola tabla GraphNode
y una sola tabla GraphEdge
para almacenar datos sin esquema, como se muestra en el siguiente ejemplo. Los nombres de las tablas se proporcionan solo a modo de ilustración. Puedes elegir los que quieras.
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;
En este ejemplo, se realizan las siguientes acciones:
Almacena todos los nodos en una sola tabla,
GraphNode
, identificada por unid
único.Almacena todos los bordes en una sola tabla,
GraphEdge
, identificada por una combinación única de origen (id
), destino (dest_id
) y su propio identificador (edge_id
). Se incluye unedge_id
como parte de la clave primaria para permitir más de un borde de un parid
a un pardest_id
.
Tanto la tabla de nodos como la de aristas tienen sus propias columnas label
y properties
.
Estas columnas son de tipo STRING
y JSON
, respectivamente.
Para obtener más información sobre las opciones de claves para la administración de datos sin esquema, consulta Definiciones de claves primarias para nodos y aristas.
Crea un gráfico de propiedades
La sentencia CREATE PROPERTY GRAPH asigna las tablas de entrada de la sección anterior como nodos y aristas. Usa las siguientes cláusulas para definir etiquetas y propiedades para los datos sin esquema:
DYNAMIC LABEL
: Crea la etiqueta de un nodo o una arista a partir de una columnaSTRING
de la tabla de entrada.DYNAMIC PROPERTIES
: Crea propiedades de un nodo o un borde a partir de una columnaJSON
de la tabla de entrada.
En el siguiente ejemplo, se muestra cómo crear un gráfico con estas 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)
);
Define etiquetas dinámicas
La cláusula DYNAMIC LABEL
designa una columna de tipo de datos STRING
para almacenar los valores de la etiqueta.
Por ejemplo, en una fila GraphNode
, si la columna label
tiene un valor person
, se asigna a un nodo Person
dentro del gráfico. Del mismo modo, en una fila GraphEdge
, si la columna de etiquetas tiene un valor de owns
, se asigna a una arista Owns
dentro del gráfico.
Para obtener más información sobre las limitaciones cuando se usan etiquetas dinámicas, consulta Limitaciones.
Define propiedades dinámicas
La cláusula DYNAMIC PROPERTIES
designa una columna de tipo de datos JSON
para almacenar propiedades. Las claves JSON representan nombres de propiedades, y los valores JSON representan valores de propiedades.
Por ejemplo, cuando la columna properties
de una fila GraphNode
tiene el valor JSON '{"name": "David", "age": 43}'
, Spanner lo asigna a un nodo que tiene propiedades age
y name
con 43
y "David"
como sus respectivos valores.
Consideraciones para la administración de datos sin esquema
Es posible que no desees usar la administración de datos sin esquema en las siguientes situaciones:
- Los tipos de nodos y aristas de tus datos de gráfico están bien definidos, o bien sus etiquetas y propiedades no requieren actualizaciones frecuentes.
- Tus datos ya están almacenados en Spanner y prefieres compilar gráficos a partir de tablas existentes en lugar de introducir tablas de nodos y bordes nuevas y dedicadas.
- Las limitaciones de los datos sin esquema impiden la adopción.
Además, si tu carga de trabajo es muy sensible al rendimiento de escritura, en especial cuando las propiedades se actualizan con frecuencia, es más eficaz usar propiedades definidas por el esquema con tipos de datos primitivos, como STRING
o INT64
, que usar propiedades dinámicas con el tipo JSON
.
Para obtener más información sobre cómo definir el esquema del gráfico sin usar etiquetas y propiedades de datos dinámicos, consulta la descripción general del esquema de Spanner Graph.
Consulta datos de gráficos sin esquema
Puedes consultar datos de gráficos sin esquema con Graph Query Language (GQL). Puedes usar las consultas de muestra en la Descripción general de las consultas de Spanner Graph y la Referencia de GQL con modificaciones limitadas.
Cómo hacer coincidir nodos y aristas con etiquetas
Puedes hacer coincidir nodos y aristas con la expresión de etiqueta en GQL.
La siguiente consulta coincide con los nodos y los bordes conectados que tienen los valores account
y transfers
en su columna de etiquetas.
GRAPH FinGraph
MATCH (a:Account {id: 1})-[t:Transfers]->(d:Account)
RETURN COUNT(*) AS result_count;
Propiedades de acceso
Spanner modela las claves y los valores de nivel superior del tipo de datos JSON
como propiedades, como age
y name
en el siguiente ejemplo.
JSON document |
Properties |
|
|
|
En el siguiente ejemplo, se muestra cómo acceder a la propiedad name
desde el nodo Person
.
GRAPH FinGraph
MATCH (person:Person {id: 1})
RETURN person.name;
La consulta devuelve resultados similares a los siguientes:
JSON"Tom"
Cómo convertir tipos de datos de propiedades
Spanner trata las propiedades como valores del tipo de datos JSON. En algunos casos, como en las comparaciones con tipos de SQL, primero debes convertir las propiedades a un tipo de SQL.
En el siguiente ejemplo, la consulta realiza las siguientes conversiones de tipo de datos:
- Convierte la propiedad
is_blocked
en un tipo booleano para evaluar la expresión. - Convierte la propiedad
order_number_str
en un tipo de cadena y la compara con el valor literal"302290001255747"
. - Usa la función LAX_INT64 para convertir de forma segura
order_number_str
en un número entero como tipo de datos que se devuelve.
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;
Esto devuelve resultados similares a los siguientes:
+-----------------------+
| order_number_as_int64 |
+-----------------------+
| 302290001255747 |
+-----------------------+
En cláusulas como GROUP BY
y ORDER BY
, también debes convertir el tipo de datos JSON. En el siguiente ejemplo, se convierte la propiedad city
en un tipo de cadena, lo que te permite usarla para agrupar.
GRAPH FinGraph
MATCH (person:Person {country: "South Korea"})
RETURN STRING(person.city) as person_city, COUNT(*) as cnt
LIMIT 10
Sugerencias para convertir tipos de datos JSON en tipos de datos SQL:
- Los convertidores estrictos, como
INT64
, realizan verificaciones rigurosas de tipos y valores. Usa convertidores estrictos cuando el tipo de datos JSON se conozca y se aplique, por ejemplo, usando restricciones de esquema para aplicar el tipo de datos de la propiedad. - Los convertidores flexibles, como
LAX_INT64
, convierten el valor de forma segura cuando es posible y devuelvenNULL
cuando la conversión no es factible. Usa convertidores flexibles cuando no se requiera una verificación rigurosa o los tipos sean difíciles de aplicar.
Para obtener más información sobre la conversión de datos, consulta las sugerencias para solucionar problemas.
Cómo filtrar por valores de propiedad
En los filtros de propiedad, Spanner trata los parámetros de filtro como valores del tipo de datos JSON
. Por ejemplo, en la siguiente consulta, Spanner trata is_blocked
como un boolean
de JSON y order_number_str
como un string
de JSON.
GRAPH FinGraph
MATCH (a:Account {is_blocked: false})-[t:Transfers {order_number_str:"302290001255747"}]->()
RETURN a.id AS account_id;
Esto devuelve resultados similares a los siguientes:
+-----------------------+
| account_id |
+-----------------------+
| 7 |
+-----------------------+
El parámetro de filtro debe coincidir con el tipo y el valor de la propiedad. Por ejemplo, cuando el parámetro de filtro order_number_str
es un número entero, Spanner no encuentra ninguna coincidencia porque la propiedad es un string
de JSON.
GRAPH FinGraph
MATCH (a:Account {is_blocked: false})-[t:Transfers {order_number_str: 302290001255747}]->()
RETURN t.order_number_str;
Accede a propiedades JSON anidadas
Spanner no modela las claves y los valores JSON anidados como propiedades.
En el siguiente ejemplo, Spanner no modela las claves JSON city
, state
y country
como propiedades porque están anidadas en location
. Sin embargo, puedes acceder a ellos con un operador de acceso a campos o un operador de subíndice de JSON.
JSON document |
Properties |
|
|
|
|
|
En el siguiente ejemplo, se muestra cómo acceder a las propiedades anidadas con el operador de acceso del campo JSON.
GRAPH FinGraph
MATCH (person:Person {id: 1})
RETURN STRING(person.location.city);
Esto devuelve resultados similares a los siguientes:
"New York"
Modifica datos sin esquema
Spanner Graph asigna datos de tablas a nodos y vínculos de gráficos. Cuando cambias los datos de la tabla de entrada, este cambio provoca directamente mutaciones en los datos del gráfico correspondientes. Para obtener más información sobre la mutación de datos de gráficos, consulta Cómo insertar, actualizar o borrar datos de gráficos de Spanner.
Consultas de ejemplo
En esta sección, se proporcionan ejemplos que muestran cómo crear, actualizar y borrar datos de gráficos.
Cómo insertar datos de gráficos
En el siguiente ejemplo, se inserta un nodo person
. Las etiquetas y los nombres de las propiedades deben usar letras minúsculas.
INSERT INTO GraphNode (id, label, properties)
VALUES (4, "person", JSON'{"name": "David", "age": 43}');
Actualiza los datos del gráfico
En el siguiente ejemplo, se actualiza un nodo Account
y se usa la función JSON_SET
para establecer su propiedad is_blocked
.
UPDATE GraphNode
SET properties = JSON_SET(
properties,
'$.is_blocked', false
)
WHERE label = "account" AND id = 16;
En el siguiente ejemplo, se actualiza un nodo person
con un nuevo conjunto de propiedades.
UPDATE GraphNode
SET properties = JSON'{"name": "David", "age": 43}'
WHERE label = "person" AND id = 4;
En el siguiente ejemplo, se usa la función JSON_REMOVE
para quitar la propiedad is_blocked
de un nodo Account
. Después de la ejecución, todas las demás propiedades existentes permanecen sin cambios.
UPDATE GraphNode
SET properties = JSON_REMOVE(
properties,
'$.is_blocked'
)
WHERE label = "account" AND id = 16;
Borra los datos del gráfico
En el siguiente ejemplo, se borra la arista Transfers
en los nodos Account
que se transfirieron a cuentas 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
}
Limitaciones conocidas
En esta sección, se enumeran las limitaciones del uso de la administración de datos sin esquema.
Requisito de una sola tabla para las etiquetas dinámicas
Solo puedes tener una tabla de nodos si se usa una etiqueta dinámica en su definición. Esta restricción también se aplica a la tabla de borde. Spanner no permite lo siguiente:
- Definir una tabla de nodos con una etiqueta dinámica junto con cualquier otra tabla de nodos
- Definir una tabla de borde con una etiqueta dinámica junto con cualquier otra tabla de borde
- Definir varias tablas de nodos o varias tablas de aristas que usen una etiqueta dinámica
Por ejemplo, el siguiente código falla cuando intenta crear varios nodos de gráfico con etiquetas dinámicas.
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 (
...
);
Los nombres de las etiquetas deben estar en minúsculas.
Para que coincidan, debes almacenar los valores de cadena de la etiqueta en minúsculas. Te recomendamos que apliques esta regla en el código de la aplicación o con restricciones del esquema.
Si bien los valores de cadena de la etiqueta deben almacenarse en minúsculas, no distinguen mayúsculas de minúsculas cuando se hace referencia a ellos en una consulta.
En el siguiente ejemplo, se muestra cómo insertar etiquetas en valores en minúscula:
INSERT INTO GraphNode (id, label) VALUES (1, "account");
INSERT INTO GraphNode (id, label) VALUES (2, "account");
Puedes usar etiquetas que no distinguen mayúsculas de minúsculas para que coincidan con GraphNode
o GraphEdge
.
GRAPH FinGraph
MATCH (accnt:Account {id: 1})-[:Transfers]->(dest_accnt:Account)
RETURN dest_accnt.id;
Los nombres de las propiedades deben estar en minúsculas
Debes almacenar los nombres de las propiedades en minúsculas. Te recomendamos que apliques esta regla en el código de la aplicación o con restricciones de esquema.
Si bien los nombres de las propiedades deben almacenarse en minúsculas, no distinguen entre mayúsculas y minúsculas cuando los referencias en tu consulta.
En el siguiente ejemplo, se insertan las propiedades name
y age
con letras minúsculas.
INSERT INTO GraphNode (id, label, properties)
VALUES (25, "person", JSON '{"name": "Kim", "age": 27}');
En el texto de la consulta, los nombres de las propiedades no distinguen mayúsculas de minúsculas. Por ejemplo, puedes usar Age
o age
para acceder a la propiedad.
GRAPH FinGraph
MATCH (n:Person {Age: 27})
RETURN n.id;
Limitaciones adicionales
- Spanner solo modela las claves de nivel superior del tipo de datos
JSON
como propiedades. - Los tipos de datos de propiedad deben cumplir con las especificaciones del tipo JSON de Spanner.
Prácticas recomendadas para los datos sin esquema
En esta sección, se describen las prácticas recomendadas que te ayudan a modelar datos sin esquema.
Cómo definir claves primarias para nodos y aristas
La clave de un nodo debe ser única en todos los nodos del gráfico. Por ejemplo, como una columna de INT64
o de cadena UUID
.
Si existen varias aristas entre dos nodos, introduce un identificador único para la arista. En el ejemplo de esquema, se usa una columna INT64
de lógica de la aplicación edge_id
.
Cuando crees el esquema para las tablas de nodos y aristas, incluye de forma opcional la columna label
como una columna de clave primaria si el valor es inmutable. Si lo haces, la clave compuesta formada por todas las columnas de clave debe ser única en todos los nodos o aristas. Esta técnica mejora el rendimiento de las búsquedas que solo se filtran por etiqueta.
Para obtener más información sobre la elección de la clave primaria, consulta Cómo elegir una clave primaria.
Crea un índice secundario para una propiedad a la que se accede con frecuencia
Para mejorar el rendimiento de las consultas de una propiedad que se usa con frecuencia en los filtros, crea un índice secundario en una columna de propiedad generada. Luego, úsalo en un esquema de gráfico y en consultas.
En el siguiente ejemplo, se muestra cómo agregar una columna age
generada a la tabla GraphNode
para un nodo person
. El valor es NULL
para los nodos sin la etiqueta person
.
ALTER TABLE GraphNode
ADD COLUMN person_age INT64 AS
(IF (label = "person", LAX_INT64(properties.age), NULL));
La siguiente declaración DDL crea un NULL FILTERED INDEX
para person_age
y lo intercala en la tabla GraphNode
para el acceso local.
CREATE NULL_FILTERED INDEX IdxPersonAge
ON GraphNode(id, label, person_age), INTERLEAVE IN GraphNode;
La tabla GraphNode
incluye columnas nuevas que están disponibles como propiedades de nodos del gráfico. Para reflejar esto en la definición del gráfico de propiedades, usa la instrucción CREATE OR
REPLACE PROPERTY GRAPH
. Esto vuelve a compilar la definición y, luego, incluye la nueva columna person_age
como una propiedad.
Para obtener más información, consulta cómo actualizar definiciones de nodos o bordes existentes.
La siguiente instrucción vuelve a compilar la definición y, además, incluye la nueva columna person_age
como una propiedad.
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)
);
En el siguiente ejemplo, se ejecuta una consulta con la propiedad indexada.
GRAPH FinGraph
MATCH (person:Person {person_age: 43})
RETURN person.id, person.name;
De manera opcional, ejecuta el comando ANALYZE
después de crear el índice para que el optimizador de consultas se actualice con las estadísticas de la base de datos más recientes.
Usar restricciones de verificación para la integridad de los datos
Spanner admite objetos de esquema, como restricciones de verificación, para aplicar la integridad de los datos de etiquetas y propiedades. En esta sección, se enumeran recomendaciones para las restricciones de verificación que puedes usar con datos sin esquema.
Aplicar valores de etiqueta
Te recomendamos que uses NOT NULL
en la definición de la columna de etiquetas para evitar valores de etiquetas no definidos.
CREATE TABLE GraphNode (
id INT64 NOT NULL,
label STRING(MAX) NOT NULL,
properties JSON,
) PRIMARY KEY (id);
Aplicar nombres de propiedad y valores de etiqueta en minúsculas
Dado que los nombres de etiquetas y propiedades deben almacenarse como valores en minúscula, haz una de las siguientes acciones:
- Aplica la verificación en la lógica de la aplicación.
- Crea restricciones de verificación en el esquema.
En el momento de la consulta, la etiqueta y el nombre de la propiedad no distinguen mayúsculas de minúsculas.
En el siguiente ejemplo, se muestra cómo agregar una restricción de etiqueta de nodo a la tabla GraphNode
para garantizar que la etiqueta esté en minúsculas.
ALTER TABLE GraphNode ADD CONSTRAINT NodeLabelLowerCaseCheck
CHECK(LOWER(label) = label);
En el siguiente ejemplo, se muestra cómo agregar una restricción de verificación al nombre de la propiedad de borde. La verificación usa JSON_KEYS
para acceder a las claves de nivel superior.
COALESCE
convierte el resultado en un array vacío si JSON_KEYS
devuelve NULL
y, luego, verifica que cada clave esté en minúsculas.
ALTER TABLE GraphEdge ADD CONSTRAINT EdgePropertiesLowerCaseCheck
CHECK(NOT array_includes(COALESCE(JSON_KEYS(properties, 1), []), key->key<>LOWER(key)));
Aplicar que existan propiedades
Crea una restricción que verifique si existe una propiedad para una etiqueta.
En el siguiente ejemplo, la restricción verifica si un nodo person
tiene una propiedad name
.
ALTER TABLE GraphNode
ADD CONSTRAINT NameMustExistForPersonConstraint
CHECK (IF(label = 'person', properties.name IS NOT NULL, TRUE));
Aplicar propiedades únicas
Crea restricciones basadas en propiedades que verifiquen si la propiedad de un nodo o borde es única en todos los nodos o bordes con la misma etiqueta. Para ello, usa un ÍNDICE ÚNICO en las columnas generadas de las propiedades.
En el siguiente ejemplo, el índice único verifica que las propiedades name
y country
combinadas sean únicas para cualquier nodo person
.
Agrega una columna generada para
PersonName
.ALTER TABLE GraphNode ADD COLUMN person_name STRING(MAX) AS (IF(label = 'person', STRING(properties.name), NULL)) Hidden;
Agrega una columna generada para
PersonCountry
.ALTER TABLE GraphNode ADD COLUMN person_country STRING(MAX) AS (IF(label = 'person', STRING(properties.country), NULL)) Hidden;
Crea un índice único
NULL_FILTERED
en las propiedadesPersonName
yPersonCountry
.CREATE UNIQUE NULL_FILTERED INDEX NameAndCountryMustBeUniqueForPerson ON GraphNode (person_name, person_country);
Aplicar el tipo de datos de la propiedad
Aplica un tipo de datos de propiedad con una restricción de tipo de datos en un valor de propiedad para una etiqueta, como se muestra en el siguiente ejemplo. En este ejemplo, se usa la función JSON_TYPE
para verificar que la propiedad name
de la etiqueta person
use el tipo STRING
.
ALTER TABLE GraphNode
ADD CONSTRAINT PersonNameMustBeStringTypeConstraint
CHECK (IF(label = 'person', JSON_TYPE(properties.name) = 'string', TRUE));
Combina etiquetas definidas y dinámicas
Spanner permite que los nodos del gráfico de propiedades tengan etiquetas definidas (en el esquema) y etiquetas dinámicas (derivadas de los datos). Personaliza las etiquetas para aprovechar esta flexibilidad.
Considera el siguiente esquema que muestra la creación de la tabla 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)
);
Aquí, cada nodo creado a partir de GraphNode
tiene la etiqueta definida Entity
. Además, cada nodo tiene una etiqueta dinámica determinada por el valor de su columna de etiquetas.
Luego, escribe consultas que coincidan con los nodos según el tipo de etiqueta. Por ejemplo, la siguiente consulta busca nodos con la etiqueta Entity
definida:
GRAPH FinGraph
MATCH (node:Entity {id: 1}) -- Querying by the defined label
RETURN node.name;
Aunque esta consulta usa la etiqueta definida Entity
, recuerda que el nodo coincidente también incluye una etiqueta dinámica basada en sus datos.
Ejemplos de esquemas
Usa los ejemplos de esquemas de esta sección como plantillas para crear tus propios esquemas. Entre los componentes clave del esquema, se incluyen los siguientes:
- Creación de tablas de entrada de gráficos
- Creación de gráficos de propiedades
- Opcional: Índice de recorrido de borde inverso para mejorar el rendimiento del recorrido inverso
- Opcional: Etiqueta de índice para mejorar el rendimiento de las búsquedas por etiquetas
- Opcional: Restricciones de esquema para aplicar etiquetas en minúsculas y nombres de propiedades
En el siguiente ejemplo, se muestra cómo crear tablas de entrada y un gráfico de propiedades:
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)
);
En el siguiente ejemplo, se usa un índice para mejorar el recorrido de los bordes inversos. La cláusula STORING (properties)
incluye una copia de las propiedades de borde, lo que acelera las consultas que filtran en función de estas propiedades. Puedes omitir la cláusula STORING (properties)
si tus consultas no se benefician de ella.
CREATE INDEX R_EDGE ON GraphEdge (dest_id, id, edge_id) STORING (properties),
INTERLEAVE IN GraphNode;
En el siguiente ejemplo, se usa un índice de etiquetas para acelerar la coincidencia de nodos por etiquetas.
CREATE INDEX IDX_NODE_LABEL ON GraphNode (label);
En el siguiente ejemplo, se agregan restricciones que aplican etiquetas y propiedades en minúsculas. En los últimos dos ejemplos, se usa la función JSON_KEYS
. De manera opcional, puedes aplicar la verificación de letras minúsculas en la lógica de la aplicación.
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)));
Optimiza las actualizaciones por lotes de propiedades dinámicas con DML
La modificación de propiedades dinámicas con funciones como JSON_SET
y JSON_REMOVE
implica operaciones de lectura-modificación-escritura. Esto puede generar un costo más alto en comparación con la actualización de propiedades de tipo STRING
o INT64
.
Si las cargas de trabajo implican actualizaciones por lotes de propiedades dinámicas con DML, usa las siguientes recomendaciones para lograr un mejor rendimiento:
Actualiza varias filas en una sola declaración DML en lugar de procesar las filas de forma individual.
Cuando actualices un rango de claves amplio, agrupa y ordena las filas afectadas por claves primarias. Actualizar rangos no superpuestos con cada DML reduce la contención de bloqueos.
Usa parámetros de búsqueda en las instrucciones DML en lugar de codificarlos de forma rígida para mejorar el rendimiento.
Según estas sugerencias, en el siguiente ejemplo, se muestra cómo actualizar la propiedad is_blocked
para 100 nodos en una sola declaración DML. Los parámetros de consulta incluyen lo siguiente:
@node_ids
: Son las claves de las filas deGraphNode
, almacenadas en un parámetroARRAY
. Si corresponde, agruparlos y ordenarlos en todos los DML permite lograr un mejor rendimiento.@is_blocked_values
: Son los valores correspondientes que se actualizarán y que se almacenan en un 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)
Solucionar problemas
En esta sección, se describe cómo solucionar problemas relacionados con los datos sin esquema.
La propiedad aparece varias veces en el resultado de TO_JSON
Problema
El siguiente nodo modela las propiedades birthday
y name
como propiedades dinámicas en su columna JSON
. Las propiedades birthday
y name
duplicadas aparecen en el resultado JSON del elemento del gráfico.
GRAPH FinGraph
MATCH (n: Person {id: 14})
RETURN SAFE_TO_JSON(n) AS n;
Esto devuelve resultados similares a los siguientes:
{
…,
"properties": {
"birthday": "1991-12-21 00:00:00",
"name": "Alex",
"id": 14,
"label": "person",
"properties": {
"birthday": "1991-12-21 00:00:00",
"name": "Alex"
}
}
…
}
Causa posible
De forma predeterminada, todas las columnas de la tabla base se definen como propiedades. Usar TO_JSON
o SAFE_TO_JSON
para devolver elementos del gráfico genera propiedades duplicadas. Esto ocurre porque la columna JSON
(properties
) es una propiedad definida por el esquema, mientras que las claves de primer nivel de JSON
se modelan como propiedades dinámicas.
Solución recomendada
Para evitar este comportamiento, usa la cláusula PROPERTIES ALL COLUMNS EXCEPT
para excluir la columna properties
cuando definas propiedades en el esquema, como se muestra en el siguiente ejemplo:
CREATE OR REPLACE PROPERTY GRAPH FinGraph
NODE TABLES (
GraphNode
PROPERTIES ALL COLUMNS EXCEPT (properties)
DYNAMIC LABEL (label)
DYNAMIC PROPERTIES (properties)
);
Después del cambio de esquema, los elementos del gráfico devueltos del tipo de datos JSON
no tienen duplicados.
GRAPH FinGraph
MATCH (n: Person {id: 1})
RETURN TO_JSON(n) AS n;
Esta consulta devuelve lo siguiente:
{
…
"properties": {
"birthday": "1991-12-21 00:00:00",
"name": "Alex",
"id": 1,
"label": "person",
}
}
Problemas habituales cuando los valores de propiedad no se convierten correctamente
Para corregir los siguientes problemas, siempre usa conversiones de valores de propiedad cuando uses una propiedad dentro de una expresión de consulta.
Comparación de valores de propiedad sin conversión
Problema
No matching signature for operator = for argument types: JSON, STRING
Causa posible
La consulta no convierte correctamente los valores de propiedad. Por ejemplo, la propiedad name
no se convierte al tipo STRING
en la comparación:
GRAPH FinGraph
MATCH (p:Person)
WHERE p.name = "Alex"
RETURN p.id;
Solución recomendada
Para solucionar este problema, utiliza una conversión de valor antes de la comparación.
GRAPH FinGraph
MATCH (p:Person)
WHERE STRING(p.name) = "Alex"
RETURN p.id;
Esto devuelve resultados similares a los siguientes:
+------+
| id |
+------+
| 1 |
+------+
Como alternativa, usa un filtro de propiedad para simplificar las comparaciones de igualdad en las que la conversión de valores se produce automáticamente. Ten en cuenta que el tipo del valor ("Alex") debe coincidir exactamente con el tipo STRING
de la propiedad en JSON
.
GRAPH FinGraph
MATCH (p:Person {name: 'Alex'})
RETURN p.id;
Esto devuelve resultados similares a los siguientes:
+------+
| id |
+------+
| 1 |
+------+
Uso del valor de la propiedad RETURN DISTINCT
sin conversión
Problema
Column order_number_str of type JSON cannot be used in `RETURN DISTINCT
Causa posible
En el siguiente ejemplo, order_number_str
no se convirtió antes de usarse en la sentencia RETURN DISTINCT
:
GRAPH FinGraph
MATCH -[t:Transfers]->
RETURN DISTINCT t.order_number_str AS order_number_str;
Solución recomendada
Para solucionar este problema, usa una conversión de valores antes de RETURN DISTINCT
.
GRAPH FinGraph
MATCH -[t:Transfers]->
RETURN DISTINCT STRING(t.order_number_str) AS order_number_str;
Esto devuelve resultados similares a los siguientes:
+-----------------+
| order_number_str|
+-----------------+
| 302290001255747 |
| 103650009791820 |
| 304330008004315 |
| 304120005529714 |
+-----------------+
Propiedad utilizada como clave de agrupación sin conversión
Problema
Grouping by expressions of type JSON is not allowed.
Causa posible
En el siguiente ejemplo, t.order_number_str
no se convierte antes de usarse para agrupar objetos JSON:
GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN t.order_number_str, COUNT(*) AS total_transfers;
Solución recomendada
Para solucionar este problema, usa una conversión de valores antes de usar la propiedad como clave de agrupación.
GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN STRING(t.order_number_str) AS order_number_str, COUNT(*) AS total_transfers;
Esto devuelve resultados similares a los siguientes:
+-----------------+------------------+
| order_number_str | total_transfers |
+-----------------+------------------+
| 302290001255747 | 1 |
| 103650009791820 | 1 |
| 304330008004315 | 1 |
| 304120005529714 | 2 |
+-----------------+------------------+
Propiedad utilizada como clave de ordenamiento sin conversión
Problema
ORDER BY does not support expressions of type JSON
Causa posible
En el siguiente ejemplo, t.amount
no se convierte antes de usarse para ordenar los 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;
Solución recomendada
Para solucionar este problema, realiza una conversión en t.amount
en la 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;
Esto devuelve resultados similares a los siguientes:
+--------------+------------+--------+
| from_account | to_account | amount |
+--------------+------------+--------+
| 20 | 7 | 500 |
+--------------+------------+--------+
No coincide el tipo durante la conversión
Problema
The provided JSON input is not an integer
Causa posible
En el siguiente ejemplo, la propiedad order_number_str
se almacena como un tipo de datos STRING
JSON. Si intentas realizar una conversión a INT64
, se muestra un error.
GRAPH FinGraph
MATCH -[e:Transfers]->
WHERE INT64(e.order_number_str) = 302290001255747
RETURN e.amount;
Solución recomendada
Para solucionar este problema, usa el convertidor de valores exactos que coincida con el tipo de valor.
GRAPH FinGraph
MATCH -[e:Transfers]->
WHERE STRING(e.order_number_str) = "302290001255747"
RETURN e.amount;
Esto devuelve resultados similares a los siguientes:
+-----------+
| amount |
+-----------+
| JSON"200" |
+-----------+
Como alternativa, usa un convertidor flexible cuando el valor se pueda convertir al tipo de destino, como se muestra en el siguiente ejemplo:
GRAPH FinGraph
MATCH -[e:Transfers]->
WHERE LAX_INT64(e.order_number_str) = 302290001255747
RETURN e.amount;
Esto devuelve resultados similares a los siguientes:
+-----------+
| amount |
+-----------+
| JSON"200" |
+-----------+
¿Qué sigue?
- Para obtener más información sobre JSON, consulta Modifica datos de JSON y la lista de funciones de JSON.
- Compara Spanner Graph y openCypher.
- Migra a Spanner Graph.