Administra datos sin esquema con Spanner Graph

En esta página, se describe cómo administrar datos sin esquema en Spanner Graph. También se detallan las prácticas recomendadas y las sugerencias para solucionar problemas. Se recomienda tener conocimientos sobre el esquema y las consultas de Spanner Graph.

La administración de datos sin esquema te permite crear una definición flexible de un grafo, en la que las definiciones de tipo de nodo y arista se pueden agregar, actualizar o borrar sin cambios en el esquema. Este enfoque admite el desarrollo iterativo y una menor sobrecarga de administración de esquemas, al mismo tiempo que conserva la experiencia familiar de las consultas de gráficos.

La administración de datos sin esquema es particularmente útil en las siguientes situaciones:

  • Administras gráficos con cambios frecuentes, como actualizaciones y adiciones de etiquetas y propiedades de elementos.
  • Tu gráfico tiene muchos tipos de nodos y aristas, lo que dificulta la creación y administración de las tablas de entrada.

Cómo modelar datos sin esquema

Spanner Graph te permite crear un gráfico a partir de tablas en las que las filas se asignan 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 incluyen solo a modo de ejemplo, y 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 acciones siguientes:

  • Almacena todos los nodos en una sola tabla GraphNode, identificados de forma única por el id.

  • Almacena todos los bordes en una sola tabla GraphEdge, identificados de forma única por la combinación de origen (id), destino (dest_id) y su propio identificador (edge_id). Se incluye un edge_id como parte de la clave primaria para permitir más de un borde de un par id a un par dest_id.

Tanto la tabla de nodos como la de aristas tienen sus propias columnas label y properties de tipo STRING y JSON, respectivamente.

Crea un gráfico de propiedades

Con la instrucción CREATE PROPERTY GRAPH, las tablas de entrada de la sección anterior se asignan como nodos y aristas. Debes usar 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 columna STRING de la tabla de entrada.
  • DYNAMIC PROPERTIES: Crea propiedades de un nodo o un borde a partir de una columna JSON de la tabla de entrada.

En el siguiente ejemplo, se muestra cómo crear un gráfico con esas 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)
  );

Etiqueta dinámica

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.

Cómo asignar una etiqueta de `GraphNode` a una etiqueta de `GraphEdge`

Propiedades dinámicas

La cláusula DYNAMIC PROPERTIES designa una columna de tipo de datos JSON para almacenar propiedades. Las claves JSON son los nombres de las propiedades y los valores JSON son los valores de las propiedades.

Por ejemplo, cuando la columna properties de una fila GraphNode tiene el valor JSON '{"name": "David", "age": 43}', se asigna a un nodo con las propiedades age y name, con 43 y "David" como valores de propiedad.

Cuándo no usar 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 los datos de tu gráfico están bien definidos, o bien sus etiquetas y propiedades no necesitan 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 tu 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, usar propiedades definidas por el esquema con tipos de datos primitivos como STRING o INT64 es más eficaz 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

Las claves y los valores de nivel superior del tipo de datos JSON se modelan como propiedades, como age y name en el siguiente ejemplo.

JSON document Properties

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

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;

Esto devuelve resultados similares a los siguientes:

JSON"Tom"

Cómo convertir tipos de datos de propiedades

Las propiedades se tratan como valores del tipo de datos JSON. En algunos casos, como para las comparaciones con tipos de SQL, primero deben convertirse 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 el 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 a un tipo de cadena, lo que 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. Esto se recomienda cuando se conoce y se aplica el tipo de datos JSON, por ejemplo, cuando se usan 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 devuelven NULL cuando la conversión no es factible. Se recomienda cuando no se requiere una verificación rigurosa o los tipos son difíciles de aplicar.

Puedes obtener más información sobre la conversión de datos en las sugerencias para solucionar problemas.

Cómo filtrar por valores de propiedad

En los filtros de propiedad, los parámetros del filtro se tratan como valores del tipo de datos JSON. Por ejemplo, en la siguiente consulta, is_blocked se trata como un boolean JSON y order_number_str como un string 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, no se encuentra ninguna coincidencia, ya que 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

Las claves y los valores JSON anidados no se modelan como propiedades. En el siguiente ejemplo, las claves JSON city, state y country no se modelan 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

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

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, se producen mutaciones directamente 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.

Ejemplos

En esta sección, se proporcionan ejemplos de cómo crear, actualizar y borrar datos de grafos.

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

En esta sección, se enumeran las limitaciones del uso de la administración de datos sin esquema.

Requisito de una sola tabla para la etiqueta dinámica

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. No se 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.

Los valores de cadena de la etiqueta deben almacenarse en minúsculas para que coincidan. Te recomendamos que apliques esta regla en el código de la aplicación o con restricciones de esquema.

Si bien los valores de cadena de etiquetas deben almacenarse en minúsculas, no distinguen mayúsculas de minúsculas cuando se hace referencia a ellos en una búsqueda.

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

Los nombres de las propiedades deben almacenarse 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 mayúsculas de 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;

Otras limitaciones

Prácticas recomendadas

En esta sección, se describen las prácticas recomendadas para modelar datos sin esquema.

Definiciones de clave primaria 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 tienes varias aristas entre dos nodos, debes introducir un identificador único para la arista. El ejemplo de esquema usa una columna INT64 edge_id de lógica de la aplicación.

Cuando creas el esquema para las tablas de nodos y aristas, puedes incluir 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 consultas 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.

Í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, puedes crear un índice secundario en una columna de propiedad generada y, luego, usarlo en el esquema y las consultas del gráfico.

En el siguiente ejemplo, se agrega 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));

Luego, 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 ahora 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.

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, puedes ejecutar 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.

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.

Aplica valores de etiqueta válidos

Te recomendamos que uses NOT NULL en la definición de la columna de etiquetas para evitar valores de etiquetas indefinidos.

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

Aplicar los valores de etiqueta y los nombres de propiedad en minúsculas

Dado que los nombres de etiquetas y propiedades deben almacenarse como valores en minúscula, te recomendamos que hagas una de las siguientes acciones:

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 agrega 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 agrega 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 la existencia de la propiedad

Puedes crear 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));

Cómo aplicar la unicidad de la propiedad

Puedes crear 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.

  1. Agrega una columna generada para PersonName.

    ALTER TABLE GraphNode
    ADD COLUMN person_name STRING(MAX)
    AS (IF(label = 'person', STRING(properties.name), NULL)) Hidden;
    
  2. Agrega una columna generada para PersonCountry.

    ALTER TABLE GraphNode
    ADD COLUMN person_country STRING(MAX)
    AS (IF(label = 'person', STRING(properties.country), NULL)) Hidden;
    
  3. Crea un índice único NULL_FILTERED en las propiedades PersonName y PersonCountry.

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

Aplica tipos de datos de propiedad

Puedes aplicar 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));

Combinación de etiquetas definidas y dinámicas

Spanner permite que los nodos de tu gráfico de propiedades tengan etiquetas definidas (en el esquema) y etiquetas dinámicas (derivadas de los datos). Puedes personalizar 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, puedes escribir 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

Puedes usar 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:

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)));

Cómo optimizar las actualizaciones por lotes de propiedades dinámicas con DML

Modificar propiedades dinámicas con funciones como JSON_SET y JSON_REMOVE implica operaciones de lectura-modificación-escritura. Pueden generar un costo más alto en comparación con la actualización de las propiedades de tipo STRING o INT64.

Si tus cargas de trabajo implican actualizaciones por lotes de propiedades dinámicas con DML, considera 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 sus claves principales. La actualización de rangos que no se superponen con cada DML reduce la contención de bloqueos.

  • Usa parámetros de consulta en las instrucciones de DML en lugar de codificarlos de forma rígida para mejorar el rendimiento.

Según estas sugerencias, en el siguiente ejemplo, se actualiza la propiedad is_blocked para 100 nodos en una sola declaración DML. Los parámetros de consulta incluyen lo siguiente:

  1. @node_ids: Son las claves de las filas de GraphNode, almacenadas en un parámetro ARRAY. Si corresponde, agruparlos y ordenarlos en todos los DML permite lograr un mejor rendimiento.

  2. @is_blocked_values: Son los valores correspondientes que se actualizarán y que se almacenan en un parámetro ARRAY.

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

Solucionar problemas

En esta sección, se describe cómo solucionar problemas relacionados con los datos sin esquema.

La propiedad aparece más de una vez 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 duplicadas de birthday y name 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 se debe a que la columna JSON (es decir, 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

Una solución común para los siguientes problemas es usar siempre conversiones de valores de propiedad cuando se usa 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, usa 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 realiza 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?