Trabaja con mediciones

En este documento, se muestra cómo definir y consultar mediciones en tus grafos. Puedes usar medidas para asegurarte de que las agregaciones se realicen correctamente en las uniones.

Descripción general

Una medida es una propiedad agregada definida dentro de la cláusula PROPERTIES de una tabla de nodos o bordes. Las mediciones se definen con una expresión de medición, que se crea con la palabra clave MEASURE alrededor de una de las siguientes opciones:

  • una expresión de agregación
  • una expresión que combina funciones de agregación con operaciones escalares
  • una medición derivada, que es una combinación de mediciones unidas en la función AGG que se definen en la misma tabla de elementos

Las mediciones admiten las siguientes funciones de agregación:

  • SUM
  • AVG
  • COUNT
  • MIN
  • MAX
  • AGG (para mediciones derivadas)

Todas las funciones de agregación, excepto AGG, admiten la palabra clave DISTINCT. Para obtener más información sobre la expresión de medición admitida, consulta el tipo de medición.

Las mediciones definen su agregación en referencia a la KEY de la tabla de nodos o aristas en la que se definen. Esto significa que, cuando consultas una medición, la agregación se realiza correctamente, incluso si la tabla subyacente se une de una manera que hace que se dupliquen las filas.

No puedes hacer referencia a una propiedad definida por una medición en una consulta de GQL. En su lugar, accedes a las mediciones llamando a la TVF GRAPH_EXPAND para consultar una representación relacional de tu grafo. De forma predeterminada, GRAPH_EXPAND devuelve una representación de tabla plana de tu grafo con una columna por propiedad. También puedes especificar flatten_results => FALSE para devolver una tabla jerárquica con columnas STRUCT anidadas. Si tu grafo contiene varias tablas de nodos raíz, puedes especificar el argumento start_node para elegir qué tabla raíz recorrer.

Esta función no acepta todos los tipos de grafos. Para obtener más información sobre qué grafos forman una entrada válida, consulta las limitaciones de entrada. Para ejecutar consultas de GQL, debes tener una reserva que use la edición Enterprise o Enterprise Plus. Si usas precios a pedido, puedes llamar a la función GRAPH_EXPAND para ejecutar consultas en SQL en tu grafo.

Puedes llamar a la función AGG en el resultado de la TVF GRAPH_EXPAND para agregar las propiedades según la agregación definida en la medición.

Define mediciones

Las mediciones se definen dentro de la cláusula PROPERTIES de una definición de tabla de nodos o aristas con la palabra clave MEASURE() alrededor de una expresión de medición.

En el siguiente ejemplo, se crea un conjunto de datos llamado university y tablas para universidades, departamentos y cursos:

CREATE SCHEMA IF NOT EXISTS university;

CREATE OR REPLACE TABLE university.College (
  college_id INT64 PRIMARY KEY NOT ENFORCED,
  college_name STRING
);

CREATE OR REPLACE TABLE university.Department (
  dept_id INT64 PRIMARY KEY NOT ENFORCED,
  dept_name STRING,
  college_id INT64,
  budget FLOAT64,
  FOREIGN KEY (college_id) REFERENCES university.College(college_id) NOT ENFORCED
);

CREATE OR REPLACE TABLE university.Course (
  course_id INT64 PRIMARY KEY NOT ENFORCED,
  course_name STRING,
  dept_id INT64,
  credits INT64,
  FOREIGN KEY (dept_id) REFERENCES university.Department(dept_id) NOT ENFORCED
);

INSERT INTO university.College (college_id, college_name)
VALUES (101, 'College of Engineering'),
       (102, 'College of Arts');

INSERT INTO university.Department (dept_id, dept_name, college_id, budget)
VALUES (1001, 'Computer Science', 101, 500000),
       (1002, 'Mechanical Engineering', 101, 400000),
       (1003, 'Fine Arts', 102, 200000),
       (1004, 'Research', 101, 50000);

INSERT INTO university.Course (course_id, course_name, dept_id, credits)
VALUES (1, 'Intro to CS', 1001, 3),
       (2, 'Data Structures', 1001, 4),
       (3, 'Thermodynamics', 1002, 3),
       (4, 'Oil Painting', 1003, 2);

La siguiente sentencia crea un grafo llamado SchoolGraph que define medidas en las propiedades de los nodos Department y Course. Debes proporcionar un alias para las propiedades definidas por una medida.

CREATE OR REPLACE PROPERTY GRAPH university.SchoolGraph
  NODE TABLES (
    university.College
      KEY(college_id)
      PROPERTIES(college_id, college_name),
    university.Department
      KEY(dept_id)
      PROPERTIES(dept_id, dept_name, college_id,
        budget OPTIONS(description="Department budget in USD"),
        MEASURE(SUM(budget)) AS total_budget,
        MEASURE(SUM(budget) * 1.10) AS budget_with_overhead OPTIONS(
          description="Total department budget including 10% overhead",
          extensions=[("unit", "USD"), ("tier", "planning")]
        )),
    university.Course
      KEY(course_id)
      PROPERTIES(
        course_id,
        course_name,
        credits,
        dept_id,
        MEASURE(AVG(credits)) AS avg_credits,
        MEASURE(SUM(credits)) AS total_credits,
        MEASURE(COUNT(course_id)) AS course_count,
        MEASURE(COUNT(DISTINCT CASE WHEN credits >= 3 THEN course_id ELSE NULL END)) AS major_course_count,
        MEASURE(SAFE_DIVIDE(AGG(total_credits), NULLIF(AGG(course_count), 0))) AS computed_avg_credits)
  )
  EDGE TABLES (
    university.Department AS CollegeDept
      SOURCE KEY (college_id) REFERENCES College (college_id)
      DESTINATION KEY (dept_id) REFERENCES Department (dept_id),
    university.Course AS DeptCourse
      SOURCE KEY (dept_id) REFERENCES Department (dept_id)
      DESTINATION KEY (course_id) REFERENCES Course (course_id)
  );

Una visualización del gráfico SchoolGraph.

La medición total_budget se define como MEASURE(SUM(budget)). La medición bloquea la agregación en KEY, que es dept_id.

Mediciones derivadas

Una medida derivada es una medida que hace referencia a otras medidas definidas en la misma tabla de elementos con la función AGG(). Esta funcionalidad te permite crear jerarquías de mediciones reutilizables.

Por ejemplo, en university.Course, la medición computed_avg_credits calcula la proporción entre las mediciones total_credits y course_count:

MEASURE(SAFE_DIVIDE(AGG(total_credits), NULLIF(AGG(course_count), 0))) AS computed_avg_credits

Las mediciones derivadas conservan la propiedad de bloqueo de nivel de detalle de la clave de la tabla de elementos subyacente. Las dependencias de las mediciones se resuelven en el tiempo de compilación del grafo. Las referencias circulares entre mediciones devuelven un error.

Información sobre el recuento excesivo

Cuando unes tablas, los datos se repiten cada vez que hay una relación de uno a varios en tus datos. Por ejemplo, si unes las tablas Course, Department y College, un departamento con varios cursos aparece en varias filas en el resultado:

SELECT
  college_name,
  dept_name,
  course_name,
  budget
FROM university.Course
LEFT JOIN university.Department
  ON Course.dept_id = Department.dept_id
LEFT JOIN university.College
  ON Department.college_id = College.college_id;

/*------------------------+------------------------+-----------------+----------+
 | college_name           | dept_name              | course_name     | budget   |
 +------------------------+------------------------+-----------------+----------+
 | College of Engineering | Computer Science       | Intro to CS     | 500000.0 |
 | College of Engineering | Computer Science       | Data Structures | 500000.0 |
 | College of Engineering | Mechanical Engineering | Thermodynamics  | 400000.0 |
 | College of Arts        | Fine Arts              | Oil Painting    | 200000.0 |
 +------------------------+------------------------+-----------------+----------*/

Si intentas calcular el presupuesto total por facultad con SUM(budget), el presupuesto del departamento de Ciencias de la Computación se cuenta dos veces. Puedes evitar este problema consultando la tabla Department directamente, pero este enfoque no funciona si quieres calcular varias agregaciones de diferentes tablas que contribuyen a tus datos unidos. En la siguiente sección, se muestra cómo las medidas resuelven este problema.

Consulta un grafo con medidas

Puedes consultar un grafo con mediciones usando GQL, pero tu consulta no puede hacer referencia directamente a ninguna propiedad definida por una medición. Por ejemplo, la siguiente consulta hace referencia a nodos que tienen propiedades de medición definidas, pero no usa ni devuelve ninguno de los campos de medición:

GRAPH university.SchoolGraph
MATCH (c:College)->(d:Department)->(course:Course)
RETURN c.college_name, d.dept_name, course.course_name;

/*------------------------+------------------------+-----------------+
 | college_name           | dept_name              | course_name     |
 +------------------------+------------------------+-----------------+
 | College of Engineering | Computer Science       | Intro to CS     |
 | College of Engineering | Computer Science       | Data Structures |
 | College of Engineering | Mechanical Engineering | Thermodynamics  |
 | College of Arts        | Fine Arts              | Oil Painting    |
 +------------------------+------------------------+-----------------*/

Para trabajar con mediciones, usa la función con valor de tabla (TVF) GRAPH_EXPAND para consultar tu grafo como una tabla relacional:

GRAPH_EXPAND(
  graph_name [, start_node => 'NodeName'] [, flatten_results => TRUE|FALSE]
)

La función acepta los siguientes argumentos:

  • graph_name: Es un STRING que contiene el nombre del gráfico.
  • start_node: Es un STRING opcional que especifica la tabla de nodos de inicio para el recorrido. Este argumento es obligatorio si el grafo tiene varias tablas de nodos raíz.
  • flatten_results: Es un BOOL opcional (el valor predeterminado es TRUE):
    • TRUE: Aplana todas las propiedades de nodos y bordes en columnas de nivel superior con prefijos <element>_<property>.
    • FALSE: Devuelve las propiedades del nodo raíz como columnas de nivel superior sin prefijo y las tablas vecinas unidas como columnas STRUCT.

Usa el resultado sin compactar

Cuando flatten_results se establece en TRUE (el modo predeterminado), las columnas de la tabla de salida se nombran concatenando la etiqueta de la tabla de nodos o aristas y el nombre de la propiedad, por ejemplo, Course_course_name o Department_total_budget. En la siguiente consulta, se muestra un resultado de ejemplo de la TVF GRAPH_EXPAND:

SELECT
  College_college_name,
  Department_dept_name,
  Department_budget,
  Course_course_name
FROM
  GRAPH_EXPAND("university.SchoolGraph");

/*------------------------+------------------------+-------------------+--------------------+
 | College_college_name   | Department_dept_name   | Department_budget | Course_course_name |
 +------------------------+------------------------+-------------------+--------------------+
 | College of Engineering | Computer Science       | 500000.0          | Intro to CS        |
 | College of Engineering | Computer Science       | 500000.0          | Data Structures    |
 | College of Engineering | Mechanical Engineering | 400000.0          | Thermodynamics     |
 | College of Arts        | Fine Arts              | 200000.0          | Oil Painting       |
 +------------------------+------------------------+-------------------+--------------------*/

La función GRAPH_EXPAND produce la tabla compactada aplicando una serie de operaciones LEFT JOIN a las tablas de nodos y aristas. Si no especificas un nodo de inicio, el grafo de entrada debe tener una sola tabla de nodo raíz en el grafo de relaciones de esquema. De manera equivalente, la tabla del nodo raíz debe tener una relación de varios a uno con todas las demás tablas de nodos con las que comparte un borde. Los datos a los que no se puede acceder desde la tabla del nodo raíz a través de una serie de uniones no aparecen en el resultado. En el ejemplo anterior, Course es la tabla del nodo raíz. El departamento Research se omite del resultado porque no tiene ningún curso.

No puedes seleccionar directamente una columna para una propiedad definida por una medición. En su lugar, debes unirlas en la función AGG(). Esta función garantiza que la agregación definida en las mediciones se realice exactamente una vez por clave.

La siguiente consulta calcula de forma simultánea el presupuesto total y la cantidad total de cursos para cada facultad:

SELECT
  College_college_name,
  AGG(Department_total_budget) AS college_budget,
  AGG(Course_course_count) AS total_courses
FROM
  GRAPH_EXPAND("university.SchoolGraph")
GROUP BY
  College_college_name;

/*------------------------+----------------+---------------+
 | College_college_name   | college_budget | total_courses |
 +------------------------+----------------+---------------+
 | College of Engineering | 900000.0       | 3             |
 | College of Arts        | 200000.0       | 1             |
 +------------------------+----------------+---------------*/

Usa un resultado jerárquico

Cuando especificas flatten_results => FALSE, GRAPH_EXPAND devuelve una relación jerárquica en lugar de columnas con prefijo:

  • Las propiedades del nodo raíz inicial aparecen como columnas de nivel superior sin prefijo (por ejemplo, course_name o credits).
  • Las propiedades de las tablas vecinas unidas, que pueden ser tablas de nodos o de aristas, aparecen como campos STRUCT anidados que llevan el nombre de la tabla de elementos. Por ejemplo, si el nodo de inicio es Course, usarías la propiedad DeptCourse.Department.CollegeDept.College.college_nameto access thecollege_name`.
  • Las mediciones dentro de structs anidados se consultan con la notación de puntos dentro de AGG(), por ejemplo, AGG(DeptCourse.Department.total_budget).
  • Si una tabla de elementos tiene una propiedad con el mismo nombre que una tabla de elementos adyacente, no puedes consultar el grafo con flatten_results => FALSE. En su lugar, debes compactar los resultados.
SELECT
  DeptCourse.Department.dept_name AS department_name,
  DeptCourse.Department.CollegeDept.College.college_name,
  AGG(DeptCourse.Department.total_budget) AS total_budget,
  AGG(course_count) AS total_courses
FROM GRAPH_EXPAND(
       "university.SchoolGraph",
       start_node => "Course",
       flatten_results => FALSE)
GROUP BY department_name, college_name;

Trabaja con varias tablas de nodos raíz

Los grafos suelen contener varias tablas de nodos raíz, también llamadas tablas de hechos, que comparten tablas de dimensiones. Para consultar un grafo con varias tablas de nodos raíz, usa el argumento start_node en GRAPH_EXPAND. La función recorre el subgrafo al que se puede acceder desde el nodo de inicio.

Por ejemplo, puedes agregar una tabla Instructor al conjunto de datos university que también se vincule a Department:

CREATE OR REPLACE TABLE university.Instructor (
  instructor_id INT64 PRIMARY KEY NOT ENFORCED,
  instructor_name STRING,
  dept_id INT64,
  salary FLOAT64,
  FOREIGN KEY (dept_id) REFERENCES university.Department(dept_id) NOT ENFORCED
);

INSERT INTO university.Instructor (instructor_id, instructor_name, dept_id, salary)
VALUES (501, 'Dr. Dana', 1001, 120000),
       (502, 'Dr. Lee', 1001, 110000),
       (503, 'Dr. Taylor', 1002, 115000),
       (504, 'Prof. Izumi', 1003, 90000);

Puedes crear un gráfico nuevo llamado university.SchoolGraphMultiRoot que incluya la tabla de nodos Instructor y un borde de Instructor a Department:

CREATE OR REPLACE PROPERTY GRAPH university.SchoolGraphMultiRoot
  NODE TABLES (
    university.College
      KEY(college_id)
      PROPERTIES(college_id, college_name),
    university.Department
      KEY(dept_id)
      PROPERTIES(dept_id, dept_name, college_id, budget,
        MEASURE(SUM(budget)) AS total_budget),
    university.Course
      KEY(course_id)
      PROPERTIES(
        course_id, course_name, credits, dept_id,
        MEASURE(COUNT(course_id)) AS course_count),
    university.Instructor
      KEY(instructor_id)
      PROPERTIES(
        instructor_id, instructor_name, dept_id, salary,
        MEASURE(COUNT(instructor_id)) AS instructor_count,
        MEASURE(SUM(salary)) AS total_salary)
  )
  EDGE TABLES (
    university.Department AS CollegeDept
      SOURCE KEY (college_id) REFERENCES College (college_id)
      DESTINATION KEY (dept_id) REFERENCES Department (dept_id),
    university.Course AS DeptCourse
      SOURCE KEY (dept_id) REFERENCES Department (dept_id)
      DESTINATION KEY (course_id) REFERENCES Course (course_id),
    university.Instructor AS DeptInstructor
      SOURCE KEY (dept_id) REFERENCES Department (dept_id)
      DESTINATION KEY (instructor_id) REFERENCES Instructor (instructor_id)
  );

En este gráfico, Course y Instructor funcionan como tablas de nodos raíz que se vinculan a Department y College. Puedes segmentar cualquiera de los subgrafos pasando start_node:

-- Expand the Course subgraph (Course -> Department -> College)
SELECT
  College_college_name,
  AGG(Course_course_count) AS total_courses
FROM GRAPH_EXPAND("university.SchoolGraphMultiRoot", start_node => "Course")
GROUP BY College_college_name;

-- Expand the Instructor subgraph (Instructor -> Department -> College)
SELECT
  College_college_name,
  AGG(Instructor_total_salary) AS total_faculty_salary
FROM GRAPH_EXPAND("university.SchoolGraphMultiRoot", start_node => "Instructor")
GROUP BY College_college_name;

Para analizar las métricas en varias tablas de hechos, consulta cada subgrafo con GRAPH_EXPAND y únelos en sus dimensiones compartidas en SQL estándar:

SELECT
  C.College_college_name,
  AGG(C.Course_course_count) AS total_courses,
  AGG(I.Instructor_total_salary) AS total_faculty_salary
FROM GRAPH_EXPAND("university.SchoolGraphMultiRoot", start_node => "Course") AS C
INNER JOIN GRAPH_EXPAND("university.SchoolGraphMultiRoot", start_node => "Instructor") AS I
  ON C.Department_dept_id = I.Department_dept_id
GROUP BY C.College_college_name;

Tanto AGG(C.Course_course_count) como AGG(I.Instructor_total_salary) calculan sus agregaciones con precisión en sus respectivos niveles de detalle sin interferencias de la unión.

Prácticas recomendadas

Cuando diseñes un gráfico con medidas, sigue estas prácticas recomendadas:

Define nodos y aristas en una sola tabla

Te recomendamos que uses la misma tabla para una definición de tabla de nodos y sus definiciones de tabla de bordes de conexión. Reutilizar la tabla garantiza una relación uno a uno (1:1) entre la tabla de nodos y la tabla de bordes, lo que evita la distribución ramificada y garantiza que la función GRAPH_EXPAND no ignore el borde como ambiguo.

Por ejemplo, si tienes una tabla de entrada Enrollment que contiene claves externas que apuntan a las tablas Student y Course, define el nodo Enrollment y sus aristas salientes con la tabla de entrada Enrollment.

Evita definir propiedades innecesarias en las tablas de bordes

Cuando definas una tabla de nodos y una tabla de bordes con la misma tabla física, no uses la cláusula PROPERTIES en la tabla de bordes, a menos que haya información nueva para capturar en el borde. Debido a que la tabla de nodos y la tabla de aristas usan la misma tabla física, todas estas propiedades ya forman parte de la tabla de nodos.

Definir propiedades redundantes en la tabla de bordes puede hacer que la función GRAPH_EXPAND trate la tabla de bordes como una entidad independiente de la tabla de nodos compartida. Para optimizar el rendimiento, especifica NO PROPERTIES en la definición de la tabla de aristas cuando no haya propiedades nuevas para capturar en la arista.

Modelar relaciones de varios a varios como tablas de nodos

Una relación de muchos a muchos (M:N) se produce cuando una fila de la tabla A puede corresponder a varias filas de la tabla B, y una fila de la tabla B puede corresponder a varias filas de la tabla A. Por ejemplo, un estudiante puede inscribirse en varios cursos y un curso puede tener muchos estudiantes. Puedes separar una relación de varios a varios en dos relaciones de uno a varios con una tabla de unión intermedia. Por ejemplo, puedes definir una tabla Enrollment que contenga claves externas que apunten a las tablas Student y Course. Si modelas una tabla de unión intermedia como una tabla de aristas que conecta dos tablas de nodos de dimensión en tu sentencia CREATE PROPERTY GRAPH, como en el siguiente ejemplo, la función GRAPH_EXPAND ignora estas aristas porque su relación es ambigua: no es claramente de varios a uno o de uno a uno.

CREATE PROPERTY GRAPH university.SchoolGraph
  NODE TABLES (
    university.Student,
    university.Course
  )
  EDGE TABLES (
    -- Modeling the junction table as a standalone edge causes ambiguity
    university.Enrollment AS StudentEnrollment
      SOURCE KEY (student_id) REFERENCES Student (student_id)
      DESTINATION KEY (course_id) REFERENCES Course (course_id)
  );

Si se ignoran todos los bordes de un grafo, las llamadas a GRAPH_EXPAND o BQ.SHOW_GRAPH_EXPAND_SCHEMA fallan con un error.

Para modelar correctamente una relación M:N para las mediciones, asciende la tabla de unión intermedia a una tabla de nodos. En esta estructura, la tabla de nodos de unión actúa como la única tabla de nodos raíz (una tabla de nodos con un grado de entrada de cero). Luego, define dos tablas perimetrales de muchos a uno (N:1) que compartan la tabla de unión para conectar N:1 a cada una de las tablas de nodos de dimensión:

CREATE PROPERTY GRAPH university.SchoolGraph
  NODE TABLES (
    university.Student KEY (student_id),
    university.Course KEY (course_id),
    -- Promote the junction table to a node table (acting as the single root node)
    university.Enrollment KEY (enrollment_id)
  )
  EDGE TABLES (
    -- Share (reuse) the Enrollment table to define N:1 edges to each dimension
    university.Enrollment AS EnrollmentToStudent
      SOURCE KEY (enrollment_id) REFERENCES Enrollment (enrollment_id)
      DESTINATION KEY (student_id) REFERENCES Student (student_id),
    university.Enrollment AS EnrollmentToCourse
      SOURCE KEY (enrollment_id) REFERENCES Enrollment (enrollment_id)
      DESTINATION KEY (course_id) REFERENCES Course (course_id)
  );

En esta estructura, Enrollment funciona como la única tabla de nodo raíz (grado de entrada de cero) que conecta N:1 con las tablas de dimensiones Student y Course (grado de entrada de uno). La función GRAPH_EXPAND puede compactar este gráfico recorriendo hacia afuera desde Enrollment hasta Student y Course. Si defines mediciones en cualquiera de estas tablas, puedes llamar a la función AGG en las columnas de medición para calcular correctamente las agregaciones sin contar de más.

Elige cómo las mediciones controlan las filas que no coinciden

Cuando una fila no tiene coincidencia en una tabla de elementos conectados, las columnas de esa tabla son NULL en el resultado de GRAPH_EXPAND. Por ejemplo, si un curso no tiene un valor dept_id, las columnas Department de esa fila son NULL. Si una medida en el nodo Department incluye estas filas, depende de dónde escribas la expresión escalar que agrega la medida.

Si escribes la expresión dentro de la función de agregación, esta se evalúa para cada fila del resultado expandido, incluidas las filas sin coincidencia. Cuando la expresión devuelve un valor en lugar de NULL para una de estas filas, ese valor contribuye al resultado.

Si defines la expresión como una propiedad y haces referencia a esa propiedad desde la medición, la expresión se evalúa en las filas de la tabla de nodos, y las filas sin coincidencia no contribuyen al resultado.

En el siguiente ejemplo, se define la misma expresión de ambas maneras en university.Department:

university.Department
  KEY(dept_id)
  PROPERTIES(dept_id, dept_name, college_id, budget,
    COALESCE(budget, 0) AS budget_or_zero,
    -- Includes courses that have no department, as a budget of 0.
    MEASURE(AVG(COALESCE(budget, 0))) AS avg_budget_inline,
    -- Averages only departments that exist.
    MEASURE(AVG(budget_or_zero)) AS avg_budget_property)

Ambas medidas reemplazan un presupuesto de NULL por 0. El formulario intercalado también reemplaza los valores NULL producidos para un curso que no tiene departamento, por lo que ese curso se promedia como un departamento con un presupuesto de 0. Elige el formulario que coincida con lo que quieres medir.

Visualiza el esquema de GRAPH_EXPAND

Para ver el esquema de la tabla que muestra la función GRAPH_EXPAND sin llamar a la función, usa el procedimiento del sistema BQ.SHOW_GRAPH_EXPAND_SCHEMA:

DECLARE schema STRING DEFAULT '';
CALL BQ.SHOW_GRAPH_EXPAND_SCHEMA('university.SchoolGraph', schema);
SELECT schema;

Este procedimiento propaga la variable schema con el nombre, el tipo y el modo de cada columna. También indica si la propiedad a la que hace referencia la columna es una medición y enumera cualquier descripción o sinónimo que hayas definido en ella.

¿Qué sigue?