Gestionar datos sin esquema

En esta página se explica cómo gestionar datos sin esquema en Spanner Graph. También ofrece prácticas recomendadas y consejos para solucionar problemas. Te recomendamos que te familiarices con el esquema y las consultas de Spanner Graph.

La gestión de datos sin esquema te permite crear una definición de gráfico flexible. Puede añadir, actualizar o eliminar definiciones de tipos de nodos y aristas sin cambiar el esquema. Este enfoque admite el desarrollo iterativo y reduce la sobrecarga de gestión de esquemas, a la vez que mantiene la experiencia de consulta de gráficos familiar.

La gestión de datos sin esquema es útil en los siguientes casos:

  • Gestionar 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 gestión de tablas de entrada.

Para obtener más información sobre cuándo usar la gestión de datos sin esquema, consulta Consideraciones sobre la gestión de datos sin esquema.

Modelar datos sin esquema

Spanner Graph te permite crear un gráfico a partir de tablas que asigna filas a nodos y aristas. En lugar de usar tablas independientes 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.

Crear tablas de entrada

Puede crear una tabla GraphNode y una tabla GraphEdge para almacenar datos sin esquema, como se muestra en el siguiente ejemplo. Los nombres de las tablas son meramente ilustrativos, 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, identificados por un id ú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 un edge_id como parte de la clave principal para permitir más de un borde de un par id a dest_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 gestión de datos sin esquema, consulta las definiciones de claves principales para nodos y aristas.

Crear un grafo de propiedades

La instrucción CREATE PROPERTY GRAPH mapea las tablas de entrada de la sección anterior como nodos y aristas. Usa las siguientes cláusulas para definir etiquetas y propiedades de datos sin esquema:

  • DYNAMIC LABEL: crea la etiqueta de un nodo o un borde a partir de una STRING columna de la tabla de entrada.
  • DYNAMIC PROPERTIES: crea propiedades de un nodo o un borde a partir de una JSON columna 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)
  );

Definir etiquetas dinámicas

La cláusula DYNAMIC LABEL designa una columna de tipo de datos STRING para almacenar los valores de etiqueta.

Por ejemplo, en una fila GraphNode, si la columna label tiene un valor person, se asigna a un nodo Person del gráfico. Del mismo modo, en una fila GraphEdge, si la columna de etiquetas tiene el valor owns, se asigna a una arista Owns del gráfico.

Asignar una etiqueta GraphNode a una etiqueta GraphEdge

Para obtener más información sobre las limitaciones al usar etiquetas dinámicas, consulta Limitaciones.

Definir 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 las propiedades age y name con 43 y "David" como valores respectivos.

.

Consideraciones sobre la gestión de datos sin esquema

Puede que no quieras usar la gestión de datos sin esquema en los siguientes casos:

  • Los tipos de nodos y aristas de los datos de tu gráfico están bien definidos o sus etiquetas y propiedades no requieren actualizaciones frecuentes.
  • Sus datos ya están almacenados en Spanner y prefiere crear gráficos a partir de tablas que ya tiene en lugar de introducir tablas de nodos y aristas nuevas y específicas.
  • Las limitaciones de los datos sin esquema impiden su adopción.

Además, si tu carga de trabajo es muy sensible al rendimiento de escritura, sobre todo 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 de un grafo sin usar etiquetas y propiedades de datos dinámicas, consulta la descripción general del esquema de Spanner Graph.

Consultar datos de gráficos sin esquema

Puedes consultar datos de gráficos sin esquema mediante Graph Query Language (GQL). Puedes usar las consultas de ejemplo de la descripción general de las consultas de gráficos de Spanner y de la referencia de GQL con algunas modificaciones.

Buscar coincidencias de nodos y aristas mediante etiquetas

Puedes buscar nodos y aristas usando la expresión de etiqueta en GQL.

La siguiente consulta coincide con los nodos y aristas 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;

Acceder a las propiedades

Spanner modeliza 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

   {
     "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;

La consulta devuelve resultados similares a los siguientes:

JSON"Tom"

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 SQL, primero debes convertir las propiedades a un tipo SQL.

En el siguiente ejemplo, la consulta realiza las siguientes conversiones de tipos 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 order_number_str en un número entero de forma segura como tipo de valor devuelto.
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;

Se devolverán resultados similares a los siguientes:

+-----------------------+
| order_number_as_int64 |
+-----------------------+
| 302290001255747       |
+-----------------------+

En cláusulas como GROUP BY y ORDER BY, también debe convertir el tipo de datos JSON. En el siguiente ejemplo, se convierte la propiedad city en un tipo de cadena, lo que le permite usarla para agrupar.

GRAPH FinGraph
MATCH (person:Person {country: "South Korea"})
RETURN STRING(person.city) as person_city, COUNT(*) as cnt
LIMIT 10

Consejos para convertir tipos de datos JSON en tipos de datos SQL:

  • Los convertidores estrictos, como INT64, realizan comprobaciones rigurosas de tipos y valores. Usa convertidores estrictos cuando se conozca y se aplique el tipo de datos JSON, 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 devuelven NULL cuando la conversión no es viable. Usa convertidores flexibles cuando no sea necesario hacer una comprobación rigurosa o cuando sea difícil aplicar los tipos.

Para obtener más información sobre la conversión de datos, consulta los consejos para solucionar problemas.

Filtrar por valores de propiedad

En los filtros de propiedades, 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 JSON boolean y order_number_str como un JSON string.

GRAPH FinGraph
MATCH (a:Account {is_blocked: false})-[t:Transfers {order_number_str:"302290001255747"}]->()
RETURN a.id AS account_id;

Se devolverán 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 JSON.

GRAPH FinGraph
MATCH (a:Account {is_blocked: false})-[t:Transfers {order_number_str: 302290001255747}]->()
RETURN t.order_number_str;

Acceder 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 de JSON 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 propiedades anidadas con el operador de acceso del campo JSON.

GRAPH FinGraph
MATCH (person:Person {id: 1})
RETURN STRING(person.location.city);

Se devolverán resultados similares a los siguientes:

"New York"

Modificar datos sin esquema

Spanner Graph asigna datos de tablas a nodos y aristas 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 Insertar, actualizar o eliminar datos de gráficos de Spanner.

Consultas de ejemplo

En esta sección se proporcionan ejemplos que muestran cómo crear, actualizar y eliminar datos de gráficos.

Insertar datos de un gráfico

En el siguiente ejemplo se inserta un nodo person. Los nombres de las etiquetas y las propiedades deben usar minúsculas.

INSERT INTO GraphNode (id, label, properties)
VALUES (4, "person", JSON'{"name": "David", "age": 43}');

Actualizar datos de gráficos

En el siguiente ejemplo se actualiza un nodo Account y se usa la función JSON_SET para definir 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, el resto de las propiedades no se modifican.

UPDATE GraphNode
SET properties = JSON_REMOVE(
  properties,
  '$.is_blocked'
)
WHERE label = "account" AND id = 16;

Eliminar datos de gráficos

En el siguiente ejemplo se elimina la arista Transfers de los nodos Account que se han transferido 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 gestión de datos sin esquema.

Requisito de una sola tabla para las etiquetas dinámicas

Solo puede haber 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 aristas. 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 aristas con una etiqueta dinámica junto con cualquier otra tabla de aristas.
  • 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, debe almacenar los valores de cadena de las etiquetas en minúsculas. Te recomendamos que apliques esta regla en el código de la aplicación o mediante restricciones de esquema.

Aunque los valores de cadena de las etiquetas deben almacenarse en minúsculas, no distinguen entre mayúsculas y 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úsculas:

INSERT INTO GraphNode (id, label) VALUES (1, "account");
INSERT INTO GraphNode (id, label) VALUES (2, "account");

Puede usar etiquetas que no distingan entre mayúsculas y 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 mediante restricciones de esquema.

Aunque los nombres de las propiedades deben almacenarse en minúsculas, no distinguen entre mayúsculas y minúsculas cuando se hace referencia a ellos en una consulta.

En el siguiente ejemplo se insertan las propiedades name y age en 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 entre mayúsculas y 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

  • Los modelos de Spanner solo admiten claves de nivel superior del tipo de datos JSON como propiedades.
  • Los tipos de datos de las propiedades deben cumplir las especificaciones del tipo JSON de Spanner.

Prácticas recomendadas para datos sin esquema

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

Definir claves principales para nodos y aristas

La clave de un nodo debe ser única en todos los nodos del gráfico. Por ejemplo, como una columna INT64 o una cadena UUID.

Si hay varias aristas entre dos nodos, introduce un identificador único para la arista. En el ejemplo de esquema se usa una columna INT64 edge_id de lógica de aplicación.

Cuando crees el esquema de las tablas de nodos y aristas, incluye opcionalmente la columna label como columna de clave principal si el valor es inmutable. Si lo haces, la clave compuesta formada por todas las columnas 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 cómo elegir una clave principal, consulta Elegir una clave principal.

Crear 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, cree un índice secundario en una columna de propiedad generada. Después, úsalo en un esquema de gráfico y en consultas.

En el siguiente ejemplo se muestra cómo añadir una columna age generada a la tabla GraphNode de un nodo person. El valor es NULL para los nodos que no tienen la etiqueta person.

ALTER TABLE GraphNode
ADD COLUMN person_age INT64 AS
(IF (label = "person", LAX_INT64(properties.age), NULL));

La siguiente declaración de DDL crea un NULL FILTERED INDEX para person_age y lo entrelaza en la tabla GraphNode para acceder localmente.

CREATE NULL_FILTERED INDEX IdxPersonAge
ON GraphNode(id, label, person_age), INTERLEAVE IN GraphNode;

La tabla GraphNode incluye nuevas columnas que están disponibles como propiedades de nodo de gráfico. Para reflejarlo en la definición del gráfico de propiedades, usa la instrucción CREATE OR REPLACE PROPERTY GRAPH. De esta forma, se vuelve a compilar la definición y se incluye la nueva columna person_age como propiedad.

Para obtener más información, consulta cómo actualizar las definiciones de nodos o aristas.

La siguiente instrucción vuelve a compilar la definición e incluye la nueva columna person_age como 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;

Si quieres, ejecuta el ANALYZE comando 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 comprobación para la integridad de los datos

Spanner admite objetos de esquema, como restricciones de comprobación, para aplicar la integridad de los datos de etiquetas y propiedades. En esta sección se enumeran las recomendaciones para las restricciones de comprobación que puedes usar con datos sin esquema.

Implementar obligatoriamente los valores de las etiquetas

Le recomendamos que utilice NOT NULL en la definición de la columna de etiquetas para evitar valores de etiqueta indefinidos.

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

Aplicar valores de etiquetas y nombres de propiedades en minúsculas

Como los nombres de las etiquetas y las propiedades deben almacenarse en minúsculas, haz una de las siguientes acciones:

En el momento de la consulta, no se distingue entre mayúsculas y minúsculas en el nombre de la etiqueta y de la propiedad.

En el siguiente ejemplo se muestra cómo añadir una restricción de etiqueta de nodo a la tabla GraphNode para asegurarse de 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 añadir una restricción CHECK a la propiedad de arista name. La comprobación usa JSON_KEYS para acceder a las claves de nivel superior. COALESCE convierte la salida en una matriz vacía si JSON_KEYS devuelve NULL y, a continuación, comprueba 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)));

Comprobar que existen propiedades

Crea una restricción que compruebe si una propiedad existe para una etiqueta.

En el siguiente ejemplo, la restricción comprueba 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 comprueben si la propiedad de un nodo o un 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 ejemplo siguiente, el índice único comprueba que las propiedades name y country combinadas sean únicas para cualquier nodo person.

  1. Añade una columna generada para PersonName.

    ALTER TABLE GraphNode
    ADD COLUMN person_name STRING(MAX)
    AS (IF(label = 'person', STRING(properties.name), NULL)) Hidden;
    
  2. Añade 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);
    

Implementar el tipo de datos de la propiedad

Para aplicar un tipo de datos de una propiedad, utilice una restricción de tipo de datos en el valor de una propiedad de una etiqueta, como se muestra en el siguiente ejemplo. En este ejemplo se usa la función JSON_TYPE para comprobar que la propiedad name de la etiqueta person usa el tipo STRING.

ALTER TABLE GraphNode
ADD CONSTRAINT PersonNameMustBeStringTypeConstraint
CHECK (IF(label = 'person', JSON_TYPE(properties.name) = 'string', TRUE));

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

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

En este caso, todos los nodos creados a partir de GraphNode tienen la etiqueta definido Entity. Además, cada nodo tiene una etiqueta dinámica determinada por el valor de su columna de etiquetas.

A continuación, escribe consultas que coincidan con los nodos en función del tipo de etiqueta. Por ejemplo, la siguiente consulta busca nodos mediante 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 tiene 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:

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 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 búsqueda de nodos por etiquetas.

CREATE INDEX IDX_NODE_LABEL ON GraphNode (label);

En el siguiente ejemplo se añaden restricciones que aplican etiquetas y propiedades en minúsculas. En los dos últimos ejemplos se usa la función JSON_KEYS. También puedes aplicar la comprobación de 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)));

Optimizar las actualizaciones por lotes de propiedades dinámicas con DML

Para modificar propiedades dinámicas mediante funciones como JSON_SET y JSON_REMOVE se realizan operaciones de lectura, modificación y escritura. Esto puede conllevar un coste mayor 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 mediante DML, siga estas recomendaciones para mejorar el rendimiento:

  • Actualiza varias filas en una sola instrucción DML en lugar de procesarlas individualmente.

  • Cuando actualice un intervalo de claves amplio, agrupe y ordene las filas afectadas por claves principales. Actualizar intervalos no superpuestos con cada DML reduce la contención de bloqueos.

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

Según estas sugerencias, en el siguiente ejemplo se muestra cómo actualizar la propiedad is_blocked de 100 nodos en una sola instrucción DML. Los parámetros de consulta incluyen lo siguiente:

  1. @node_ids: claves de las filas de GraphNode, almacenadas en un parámetro ARRAY. Si procede, agruparlos y ordenarlos en diferentes DMLs permite obtener un mejor rendimiento.

  2. @is_blocked_values: los valores correspondientes que se van a actualizar, almacenados 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 con datos sin esquema.

La propiedad aparece varias veces en el resultado 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 de gráfico.

GRAPH FinGraph
MATCH (n: Person {id: 14})
RETURN SAFE_TO_JSON(n) AS n;

Se devolverán 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. Si usas TO_JSON o SAFE_TO_JSON para devolver elementos de gráfico, se duplicarán las propiedades. 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 al definir 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 de 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 las propiedades no se convierten correctamente

Para solucionar los siguientes problemas, utilice siempre conversiones de valores de propiedad cuando use una propiedad en 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 las propiedades. 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;

Se devolverán resultados similares a los siguientes:

+------+
| id   |
+------+
| 1    |
+------+

También puede usar 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;

Se devolverán 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 ejemplo siguiente, order_number_str no se ha convertido antes de usarse en la instrucción 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 valor antes de RETURN DISTINCT.

GRAPH FinGraph
MATCH -[t:Transfers]->
RETURN DISTINCT STRING(t.order_number_str) AS order_number_str;

Se devolverán resultados similares a los siguientes:

+-----------------+
| order_number_str|
+-----------------+
| 302290001255747 |
| 103650009791820 |
| 304330008004315 |
| 304120005529714 |
+-----------------+

Propiedad usada 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, utilice una conversión de valor 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;

Se devolverán resultados similares a los siguientes:

+-----------------+------------------+
| order_number_str | total_transfers |
+-----------------+------------------+
| 302290001255747 |                1 |
| 103650009791820 |                1 |
| 304330008004315 |                1 |
| 304120005529714 |                2 |
+-----------------+------------------+

Propiedad usada como clave de ordenación 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, haz 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;

Se devolverán resultados similares a los siguientes:

+--------------+------------+--------+
| from_account | to_account | amount |
+--------------+------------+--------+
|           20 |          7 | 500    |
+--------------+------------+--------+

El tipo no coincide 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 JSON STRING. Si intentas realizar una conversión a INT64, se devuelve un error.

GRAPH FinGraph
MATCH -[e:Transfers]->
WHERE INT64(e.order_number_str) = 302290001255747
RETURN e.amount;

Solución recomendada

Para solucionar este problema, utilice el convertidor de valor exacto que coincida con el tipo de valor.

GRAPH FinGraph
MATCH -[e:Transfers]->
WHERE STRING(e.order_number_str) = "302290001255747"
RETURN e.amount;

Se devolverán resultados similares a los siguientes:

+-----------+
| amount    |
+-----------+
| JSON"200" |
+-----------+

También puedes usar 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;

Se devolverán resultados similares a los siguientes:

+-----------+
| amount    |
+-----------+
| JSON"200" |
+-----------+

Siguientes pasos