עבודה עם מדדים

במאמר הזה מוסבר איך להגדיר מדדים ולשאול שאילתות לגביהם בתרשימים. אתם יכולים להשתמש במדדים כדי לוודא שהצבירות מתבצעות בצורה נכונה בחיבורים.

סקירה כללית

מדד הוא מאפיין מצטבר שמוגדר בסעיף PROPERTIES של טבלת צמתים או קשתות. המדדים מוגדרים באמצעות מילת המפתח MEASURE ואחת מפונקציות הצבירה הנתמכות הבאות:

  • SUM
  • AVG
  • COUNT
  • COUNT(DISTINCT)
  • MIN
  • MAX

המדדים מגדירים את הצבירה שלהם ביחס לKEY של טבלת הצמתים או הקצוות שבהם הם מוגדרים. כלומר, כשמבצעים שאילתה על מדד, הצבירה מתבצעת בצורה נכונה גם אם הטבלה הבסיסית מצורפת באופן שגורם לשורות להיות כפולות.

אי אפשר להפנות למאפיין שהוגדר על ידי מדד בשאילתת GQL. במקום זאת, ניגשים למדדים על ידי קריאה ל-GRAPH_EXPAND TVF כדי ליצור ייצוג של הטבלה בצורה שטוחה של הגרף. הפונקציה הזו לא מקבלת את כל סוגי הגרפים. מידע נוסף על הגרפים שמהווים קלט תקין מופיע בקטע מגבלות הקלט.

אפשר להפעיל את הפונקציה AGG על פלט מ-TVF‏ GRAPH_EXPAND כדי לצבור את המאפיינים בהתאם לפונקציית הצבירה שהגדרתם במדד.

הגדרת מדדים

מגדירים מדדים בפסקה PROPERTIES של הגדרת טבלת צמתים או קשתות באמצעות מילת המפתח MEASURE() סביב פונקציית צבירה נתמכת.

בדוגמה הבאה נוצר מערך נתונים בשם university וטבלאות למכללות, למחלקות ולקורסים:

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

ההצהרה הבאה יוצרת תרשים שנקרא SchoolGraph ומגדיר מדדים בחלק מהמאפיינים בצמתי Department ו-Course. חובה לספק כינוי למאפיינים שמוגדרים על ידי מדד.

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

המחשה של תרשים SchoolGraph.

המדד total_budget מוגדר כ-MEASURE(SUM(budget)). המדד נועל את הצבירה לKEY, שהוא dept_id.

הסבר על ספירה כפולה

כשמצטרפים לטבלאות, הנתונים חוזרים על עצמם בכל פעם שיש קשר של אחד לרבים בנתונים. לדוגמה, אם מצטרפים לטבלאות Course, Department ו-College, מחלקה עם כמה קורסים מופיעה בכמה שורות בפלט:

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 |
 +------------------------+------------------------+-----------------+----------*/

אם מנסים לחשב את התקציב הכולל לכל מכללה באמצעות SUM(budget), התקציב של המחלקה למדעי המחשב נספר פעמיים. כדי להימנע מהבעיה הזו, אפשר לשלוח שאילתה ישירות לטבלה Department, אבל הגישה הזו לא מתאימה אם רוצים לחשב כמה צבירות מטבלאות שונות שמשפיעות על הנתונים המצורפים. בקטע הבא מוסבר איך המדדים פותרים את הבעיה הזו.

שאילתת גרף עם מדדים

אפשר להריץ שאילתה על תרשים עם מדדים באמצעות GQL, אבל אי אפשר להפנות ישירות בשאילתה למאפיין כלשהו שהוגדר על ידי מדד. לדוגמה, השאילתה הבאה מתייחסת לצמתים שמוגדרים להם מאפייני מדד, אבל לא משתמשת בשדות המדד ולא מחזירה אותם:

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    |
 +------------------------+------------------------+-----------------*/

עבודה עם מדדים

כדי לעבוד עם מדדים, משתמשים בפונקציה GRAPH_EXPAND מסוג TVF (פונקציה שמחזירה טבלה) כדי לשלוח שאילתה לגרף כטבלה משוטחת אחת. העמודות בטבלת הפלט נגזרות מהמאפיינים שהוגדרו בתרשים לכל טבלת צמתים וקשתות. כדי למנוע התנגשויות בין שמות, השמות של העמודות נוצרים על ידי שרשור של התווית של טבלת הצמתים או הקשתות ושם המאפיין – לדוגמה, Course_course_name או Department_total_budget. השאילתה הבאה מציגה פלט לדוגמה מ-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       |
 +------------------------+------------------------+-------------------+--------------------+

הפונקציה GRAPH_EXPAND יוצרת את הטבלה השטוחה על ידי החלה של סדרה של פעולות LEFT JOIN על טבלאות של צמתים וקשתות. גרף קלט תקין צריך לכלול טבלת צומת שורש אחת בלבד, שהערך KEY שלה לא מופיע באף טבלה אחרת. נתונים שלא ניתן להגיע אליהם מטבלת צומת הבסיס באמצעות סדרת הצטרפויות לא מופיעים בפלט. בדוגמה הקודמת, הטבלה Course היא טבלת הצומת של הרמה הבסיסית (root). המחלקה Research לא מופיעה בפלט כי אין בה קורסים.

אי אפשר לבחור עמודה ישירות בשביל נכס שמוגדר על ידי מדד. במקום זאת, צריך להוסיף אותם לAGG() הפונקציה. הפונקציה הזו מוודאת שהצבירה שמוגדרת במדדים מתבצעת בדיוק פעם אחת לכל מפתח.

השאילתה הבאה מחשבת בו-זמנית את התקציב הכולל ואת המספר הכולל של הקורסים לכל מכללה:

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             |
 +------------------------+----------------+---------------*/

שיטות מומלצות

כשמעצבים תרשים עם מדדים, כדאי לפעול לפי השיטות המומלצות הבאות:

הגדרת צמתים וקשתות בטבלה אחת

מומלץ להשתמש באותה טבלה להגדרת טבלת צמתים ולהגדרות של טבלאות קשתות שמקשרות אליה. שימוש חוזר בטבלה מבטיח יחס מדויק של אחד לאחד (1:1) בין טבלת הצמתים לבין טבלת הקשתות, ומונע fan-out. כך אפשר לוודא שהפונקציה GRAPH_EXPAND לא מתעלמת מהקשת כקשת מעורפלת.

לדוגמה, אם יש לכם טבלת קלט Enrollment שמכילה מפתחות זרים שמפנים לטבלאות Student ו-Course, צריך להגדיר את הצומת Enrollment ואת הקצוות היוצאים שלו באמצעות טבלת הקלט Enrollment.

הצגת קשרי גומלין מסוג רבים לרבים כטבלאות צמתים

קשר רבים לרבים (M:N) מתרחש כששורה בטבלה A יכולה להתאים לכמה שורות בטבלה B, ושורה בטבלה B יכולה להתאים לכמה שורות בטבלה A. לדוגמה, תלמיד אחד יכול להירשם לכמה קורסים, וקורס אחד יכול לכלול הרבה תלמידים. אפשר להפריד קשר מסוג רבים לרבים לשני קשרים מסוג אחד לרבים באמצעות טבלת צומת ביניים. לדוגמה, אפשר להגדיר טבלה Enrollment שמכילה מפתחות זרים שמפנים לטבלאות Student ו-Course. אם אתם יוצרים מודל של טבלת צומת ביניים כטבלת קצוות שמקשרת בין שתי טבלאות של צמתי מאפיינים בהצהרת CREATE PROPERTY GRAPH, הפונקציה GRAPH_EXPAND מתעלמת מהקצוות האלה כי הקשר שלהם לא ברור: הוא לא חד-משמעי, לא רבים-לאחד ולא אחד-לאחד.

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

הפונקציה GRAPH_EXPAND מתעלמת מהקצוות האלה כי הם לא חד-משמעיים (לא ברור אם מדובר בקשרים של רבים לאחד או של אחד לאחד בין טבלאות הצמתים עצמן). אם המערכת מתעלמת מכל הקצוות בתרשים, קריאות ל-GRAPH_EXPAND או ל-BQ.SHOW_GRAPH_EXPAND_SCHEMA נכשלות עם שגיאה.

כדי ליצור מודל נכון של קשר M:N למדדים, מקדמים את טבלת הצומת הביניים לטבלת צומת. במבנה הזה, טבלת קישור צומת משמשת כטבלת צומת שורש יחידה (טבלת צומת עם דרגת כניסה של אפס). לאחר מכן מגדירים שתי טבלאות קצה מסוג 'רבים לאחד' (N:1) שמשתפות את טבלת הצומת כדי לקשר בין N:1 לכל אחת מטבלאות צומת המאפיינים:

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

במבנה הזה, הטבלה Enrollment משמשת כטבלת צומת השורש היחידה (דרגת כניסה אפס) שמקשרת N:1 לטבלאות המאפיינים Student ו-Course (דרגת כניסה אחת). הפונקציה GRAPH_EXPAND יכולה לשטח את הגרף הזה על ידי מעבר החוצה מ-Enrollment אל Student ו-Course. אם מגדירים מדדים באחת מהטבלאות האלה, אפשר להפעיל את הפונקציה AGG בעמודות המדדים כדי לחשב את הצבירות בצורה נכונה בלי לספור כפילויות.

הצגת סכימת GRAPH_EXPAND

כדי להציג את הסכימה של הטבלה שמוחזרת על ידי הפונקציה GRAPH_EXPAND בלי להפעיל את הפונקציה, משתמשים בהליך המערכת BQ.SHOW_GRAPH_EXPAND_SCHEMA:

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

ההליך הזה מאכלס את המשתנה schema עם השם, הסוג והמצב של כל עמודה. בנוסף, העמודה מציינת אם הנכס שאליו מתייחסים הוא מדד, ומציגה את התיאור או המילים הנרדפות שהגדרתם עבורו.

המאמרים הבאים