ניהול נתונים ללא סכימה

בדף הזה מוסבר איך לנהל נתונים ללא סכימה ב-Spanner Graph. בנוסף, הוא כולל שיטות מומלצות וטיפים לפתרון בעיות. מומלץ להכיר את הסכימה של Spanner Graph ואת השאילתות.

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

ניהול נתונים ללא סכימה שימושי בתרחישים הבאים:

  • ניהול גרפים עם שינויים תכופים, כמו עדכונים ותוספות של תוויות ושל מאפיינים של רכיבים.

  • גרפים עם הרבה סוגים של צמתים וקשתות, שמקשים על יצירה וניהול של טבלאות קלט.

מידע נוסף על מקרים שבהם כדאי להשתמש בניהול נתונים ללא סכימה זמין במאמר שיקולים לניהול נתונים ללא סכימה.

נתונים לפי מודל ללא סכימה

‫Spanner Graph מאפשר ליצור גרף מטבלאות שממפה שורות לצמתים וקשתות. במקום להשתמש בטבלאות נפרדות לכל סוג רכיב, בדרך כלל משתמשים במודל נתונים ללא סכימה בטבלת צמתים אחת ובטבלת קשתות אחת עם עמודה STRINGלתווית ועמודה JSON למאפיינים.

יצירת טבלאות קלט

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

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;

בדוגמה הזו מתבצעות הפעולות הבאות:

  • כל הצמתים מאוחסנים בטבלה אחת, GraphNode, שמזוהים באמצעות מזהה ייחודי id.

  • כל הקצוות מאוחסנים בטבלה אחת, GraphEdge, שמזוהים באמצעות שילוב ייחודי של מקור (id), יעד (dest_id) ומזהה משלהם (edge_id). המזהה edge_id נכלל כחלק מהמפתח הראשי כדי לאפשר יותר מקצה אחד מזוג של id ל-dest_id.

גם לטבלאות הצמתים וגם לטבלאות הקשתות יש עמודות label ו-properties משלהן. העמודות האלה הן מסוג STRING ו-JSON, בהתאמה.

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

יצירת גרף נכסים

ההצהרה CREATE PROPERTY GRAPH ממפה את טבלאות הקלט בקטע הקודם כצמתים וקשתות. כדי להגדיר תוויות ומאפיינים לנתונים ללא סכימה, משתמשים בסעיפים הבאים:

  • DYNAMIC LABEL: יוצרת את התווית של צומת או קצה מעמודה STRING שמגיעה מטבלת הקלט.
  • DYNAMIC PROPERTIES: יוצרת מאפיינים של צומת או קצה מעמודה JSON שמגיעה מטבלת הקלט.

בדוגמה הבאה אפשר לראות איך ליצור תרשים באמצעות הסעיפים האלה:

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

הגדרת תוויות דינמיות

הפסקה DYNAMIC LABEL מציינת עמודה של נתונים מסוג STRING לאחסון ערכי התווית.

לדוגמה, בשורה GraphNode, אם בעמודה label יש ערך person, היא ממופה לצומת Person בגרף. באופן דומה, בשורה GraphEdge, אם בעמודת התווית מופיע הערך owns, הוא ממופה לקצה Owns בגרף.

מיפוי של תווית GraphNode לתווית GraphEdge

מידע נוסף על מגבלות השימוש בתוויות דינמיות זמין במאמר מגבלות.

הגדרת מאפיינים דינמיים

הפסקה DYNAMIC PROPERTIES מציינת עמודה מסוג נתונים JSON לאחסון מאפיינים. מפתחות JSON מייצגים שמות של מאפיינים, וערכי JSON מייצגים ערכים של מאפיינים.

לדוגמה, אם בעמודה properties בשורה GraphNode יש את ערך ה-JSON‏ '{"name": "David", "age": 43}', ‏ Spanner ממפה אותו לצומת עם מאפיינים age ו-name והערכים 43 ו-"David" בהתאמה.

שיקולים לניהול נתונים ללא סכימה

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

  • סוגי הצמתים והקשתות בנתוני הגרף מוגדרים היטב, או שהתוויות והמאפיינים שלהם לא דורשים עדכונים תכופים.
  • הנתונים שלכם כבר מאוחסנים ב-Spanner, ואתם מעדיפים ליצור גרפים מטבלאות קיימות במקום להוסיף טבלאות חדשות של צמתים וקשתות.
  • המגבלות של נתונים ללא סכימה מונעות אימוץ.

בנוסף, אם עומס העבודה רגיש מאוד לביצועי כתיבה, במיוחד כשמעדכנים את הנכסים בתדירות גבוהה, שימוש במאפיינים שמוגדרים בסכימה עם סוגי נתונים פרימיטיביים כמו STRING או INT64 יעיל יותר משימוש במאפיינים דינמיים עם הסוג JSON.

מידע נוסף על הגדרת סכימת הגרף בלי להשתמש בתוויות ובמאפיינים של נתונים דינמיים זמין במאמר סקירה כללית על סכימת הגרף של Spanner.

שליחת שאילתות לנתוני תרשים ללא סכימה

אפשר לשלוח שאילתות לנתוני גרף ללא סכימה באמצעות Graph Query Language‏ (GQL). אפשר להשתמש בשאילתות לדוגמה שמופיעות במאמרים סקירה כללית של Spanner Graph Query והפניה ל-GQL עם שינויים מוגבלים.

התאמה בין צמתים וקשתות באמצעות תוויות

אפשר להתאים צמתים וקשתות באמצעות ביטוי התווית ב-GQL.

השאילתה הבאה מתאימה לקודקודים ולקשתות מחוברים שיש להם את הערכים account ו-transfers בעמודת התווית.

GRAPH FinGraph
MATCH (a:Account {id: 1})-[t:Transfers]->(d:Account)
RETURN COUNT(*) AS result_count;

גישה למאפיינים

מודלים של Spanner מציגים מפתחות וערכים ברמה העליונה של סוג הנתונים JSON כמאפיינים, כמו age ו-name בדוגמה הבאה.

JSON document Properties

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

בדוגמה הבאה אפשר לראות איך ניגשים למאפיין name מהצומת Person.

GRAPH FinGraph
MATCH (person:Person {id: 1})
RETURN person.name;

השאילתה מחזירה תוצאות שדומות לתוצאות הבאות:

JSON"Tom"

המרת סוגי נתונים של נכסים

ב-Spanner, מאפיינים נחשבים לערכים של סוג הנתונים JSON. במקרים מסוימים, כמו השוואות עם סוגי SQL, צריך קודם להמיר את המאפיינים לסוג SQL.

בדוגמה הבאה, השאילתה מבצעת את ההמרות הבאות של סוגי נתונים:

  • הפונקציה ממירה את המאפיין is_blocked לסוג בוליאני כדי להעריך את הביטוי.
  • הפונקציה ממירה את המאפיין order_number_str לסוג מחרוזת ומשווה אותו לערך המילולי "302290001255747".
  • הפונקציה LAX_INT64 משמשת להמרה בטוחה של order_number_str למספר שלם כסוג ההחזרה.
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;

התוצאה תהיה דומה לזו:

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

בסעיפים כמו GROUP BY ו-ORDER BY, צריך גם להמיר את סוג הנתונים JSON. בדוגמה הבאה, המאפיין city מומר לסוג מחרוזת, כך שאפשר להשתמש בו לקיבוץ.

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

טיפים להמרת סוגי נתונים ב-JSON לסוגי נתונים ב-SQL:

  • ממירים מחמירים, כמו INT64, מבצעים בדיקות קפדניות של סוגים וערכים. משתמשים בממירים מחמירים כשסוג הנתונים של JSON ידוע ומאוכף, למשל באמצעות אילוצי סכימה כדי לאכוף את סוג הנתונים של המאפיין.
  • ממירים גמישים, כמו LAX_INT64, ממירים את הערך בצורה בטוחה כשאפשר, ומחזירים NULL כשאי אפשר לבצע המרה. משתמשים בממירים גמישים כשלא נדרשת בדיקה קפדנית או כשקשה לאכוף סוגים.

מידע נוסף על המרת נתונים זמין במאמר טיפים לפתרון בעיות.

סינון לפי ערכי מאפיינים

במסנני נכסים,‏ Spanner מתייחס לפרמטרים של המסנן כאל ערכים מסוג הנתונים JSON. לדוגמה, בשאילתה הבאה, Spanner מתייחס אל is_blocked כאל JSON boolean ואל order_number_str כאל JSON string.

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

התוצאה תהיה דומה לזו:

+-----------------------+
| account_id            |
+-----------------------+
| 7                     |
+-----------------------+

פרמטר המסנן צריך להתאים לסוג ולערך של הנכס. לדוגמה, אם פרמטר המסנן order_number_str הוא מספר שלם, מערכת Spanner לא מוצאת התאמה כי המאפיין הוא JSON string.

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

גישה למאפייני JSON מוטמעים

ב-Spanner, מפתחות וערכים של JSON מוטמעים לא מוגדרים כמאפיינים. בדוגמה הבאה, Spanner לא ממדל את מפתחות ה-JSON‏ city, state ו-country כמאפיינים כי הם מוטמעים בתוך location. אבל אפשר לגשת אליהם באמצעות אופרטור גישה לשדה JSON או אופרטור של אינדקס 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",
}

בדוגמה הבאה אפשר לראות איך ניגשים למאפיינים מקוננים באמצעות אופרטור הגישה של שדה JSON.

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

התוצאה תהיה דומה לזו:

"New York"

שינוי נתונים ללא סכימה

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

שאילתות לדוגמה

בקטע הזה מופיעות דוגמאות שמראות איך ליצור, לעדכן ולמחוק נתוני גרף.

הוספת נתוני תרשים

בדוגמה הבאה מוסיפים צומת person. שמות התוויות והמאפיינים צריכים להיות באותיות קטנות.

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

עדכון נתוני התרשים

בדוגמה הבאה מתבצע עדכון של צומת Account ונעשה שימוש בפונקציה JSON_SET כדי להגדיר את המאפיין is_blocked שלו.

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

בדוגמה הבאה מעדכנים צומת person עם קבוצה חדשה של מאפיינים.

UPDATE GraphNode
SET properties = JSON'{"name": "David", "age": 43}'
WHERE label = "person" AND id = 4;

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

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

מחיקת נתוני התרשים

בדוגמה הבאה מוצגות קצוות Transfers שנמחקים בצמתים Account שהועברו לחשבונות חסומים.

DELETE FROM GraphEdge
WHERE label = "transfers" AND id IN {
  GRAPH FinGraph
  MATCH (a:Account)-[:Transfers]->{1,2}(:Account {is_blocked: TRUE})
  RETURN a.id
}

מגבלות ידועות

בקטע הזה מפורטות המגבלות של שימוש בניהול נתונים ללא סכימה.

דרישה לטבלה יחידה לתוויות דינמיות

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

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

לדוגמה, הקוד הבא נכשל כשמנסים ליצור כמה צמתים של גרף עם תוויות דינמיות.

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

שמות התוויות צריכים להיות באותיות קטנות

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

ערכי מחרוזות של תוויות צריכים להישמר באותיות קטנות, אבל הם לא תלויי-רישיות כשמפנים אליהם בשאילתה.

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

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

אפשר להשתמש בתוויות לא תלויות-רישיות כדי להתאים ל-GraphNode או ל-GraphEdge.

GRAPH FinGraph
MATCH (accnt:Account {id: 1})-[:Transfers]->(dest_accnt:Account)
RETURN dest_accnt.id;

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

שמות הנכסים צריכים להיות כתובים באותיות קטנות. מומלץ לאכוף את הכלל הזה בקוד האפליקציה או באמצעות אילוצים של סכימה.

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

בדוגמה הבאה מוסיפים את המאפיינים name ו-age באותיות קטנות.

INSERT INTO GraphNode (id, label, properties)
VALUES (25, "person", JSON '{"name": "Kim", "age": 27}');

בטקסט של השאילתה, שמות המאפיינים הם לא תלויי-רישיות. לדוגמה, אפשר להשתמש ב-Age או ב-age כדי לגשת לנכס.

GRAPH FinGraph
MATCH (n:Person {Age: 27})
RETURN n.id;

מגבלות נוספות

  • מודלים של Spanner יכולים להשתמש רק במפתחות ברמה העליונה של סוג הנתונים JSON כמאפיינים.
  • סוגי הנתונים של המאפיינים צריכים להתאים למפרטים של סוג ה-JSON ב-Spanner.

שיטות מומלצות לנתונים ללא סכימה

בקטע הזה מתוארות שיטות מומלצות שיעזרו לכם ליצור מודל של נתונים ללא סכימה.

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

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

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

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

מידע נוסף על בחירת מפתח ראשי זמין במאמר בחירת מפתח ראשי.

יצירת אינדקס משני לנכס שמתבצעת אליו גישה לעיתים קרובות

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

בדוגמה הבאה אפשר לראות איך מוסיפים עמודה של age שנוצרה לטבלה GraphNode של צומת person. הערך הוא NULL לצמתים בלי התווית person.

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

הפקודה הבאה של DDL יוצרת את NULL FILTERED INDEX עבור person_age ומשלבת אותה בטבלה GraphNode לגישה מקומית.

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

הטבלה GraphNode כוללת עמודות חדשות שזמינות כמאפיינים של צומת גרף. כדי לשקף את זה בהגדרת גרף הנכסים, משתמשים בהצהרה CREATE OR REPLACE PROPERTY GRAPH. הפעולה הזו תבצע קומפילציה מחדש של ההגדרה ותכלול את העמודה החדשה person_age כמאפיין.

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

ההצהרה הבאה מבצעת קומפילציה מחדש של ההגדרה וכוללת את העמודה החדשה person_age כמאפיין.

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

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

GRAPH FinGraph
MATCH (person:Person {person_age: 43})
RETURN person.id, person.name;

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

שימוש באילוצי בדיקה לשמירה על תקינות הנתונים

‫Spanner תומך באובייקטים של סכימות כמו אילוצי בדיקה כדי לאכוף את שלמות הנתונים של התוויות והמאפיינים. בקטע הזה מפורטות המלצות לאילוצי בדיקה שאפשר להשתמש בהם עם נתונים ללא סכימה.

אכיפת ערכים של תוויות

מומלץ להשתמש ב-NOT NULL בהגדרת עמודת התוויות כדי להימנע מערכי תוויות לא מוגדרים.

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

אכיפת שימוש באותיות קטנות בערכי תוויות ובשמות נכסים

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

  • אוכפים את הבדיקה בלוגיקה של האפליקציה.
  • יצירת אילוצי בדיקה בסכימה.

בזמן השאילתה, התווית ושם המאפיין לא תלויי-רישיות.

בדוגמה הבאה אפשר לראות איך מוסיפים אילוץ של תווית צומת לטבלה GraphNode כדי לוודא שהתווית היא באותיות קטנות.

ALTER TABLE GraphNode ADD CONSTRAINT NodeLabelLowerCaseCheck
CHECK(LOWER(label) = label);

בדוגמה הבאה אפשר לראות איך מוסיפים אילוץ מסוג check למאפיין הקצה name. בבדיקה נעשה שימוש ב-JSON_KEYS כדי לגשת למקשים ברמה העליונה. ‫COALESCE ממירה את הפלט למערך ריק אם הפונקציה JSON_KEYS מחזירה NULL, ואז בודקת שכל מפתח הוא באותיות קטנות.

ALTER TABLE GraphEdge ADD CONSTRAINT EdgePropertiesLowerCaseCheck
CHECK(NOT array_includes(COALESCE(JSON_KEYS(properties, 1), []), key->key<>LOWER(key)));

אכיפה של קיום נכסים

יוצרים מגבלה שבודקת אם מאפיין קיים עבור תווית.

בדוגמה הבאה, ההגבלה בודקת אם לצומת person יש מאפיין name.

ALTER TABLE GraphNode
ADD CONSTRAINT NameMustExistForPersonConstraint
CHECK (IF(label = 'person', properties.name IS NOT NULL, TRUE));

אכיפה של מאפיינים ייחודיים

יצירת אילוצים מבוססי-מאפיינים שבודקים אם המאפיין של צומת או קשת הוא ייחודי בצמתים או בקשתות עם אותה תווית. כדי לעשות את זה, משתמשים ב-UNIQUE INDEX מול העמודות שנוצרו של הנכסים.

בדוגמה הבאה, האינדקס הייחודי בודק שהשילוב של המאפיינים name ו-country ייחודי לכל צומת person.

  1. מוסיפים עמודה שנוצרה עבור PersonName.

    ALTER TABLE GraphNode
    ADD COLUMN person_name STRING(MAX)
    AS (IF(label = 'person', STRING(properties.name), NULL)) Hidden;
    
  2. מוסיפים עמודה שנוצרה עבור PersonCountry.

    ALTER TABLE GraphNode
    ADD COLUMN person_country STRING(MAX)
    AS (IF(label = 'person', STRING(properties.country), NULL)) Hidden;
    
  3. יוצרים אינדקס ייחודי NULL_FILTERED למאפיינים PersonName ו-PersonCountry.

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

אכיפה של סוג הנתונים של המאפיין

אפשר לאכוף סוג נתונים של מאפיין באמצעות אילוץ של סוג נתונים על ערך של מאפיין של תווית, כמו בדוגמה הבאה. בדוגמה הזו נעשה שימוש בפונקציה JSON_TYPE כדי לבדוק שהמאפיין name של התווית person הוא מסוג STRING.

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

שילוב של תוויות מוגדרות ודינמיות

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

לדוגמה, נבחן את הסכימה הבאה שבה מוצגת יצירה של טבלה 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)
  );

במקרה הזה, לכל צומת שנוצר מ-GraphNode יש את התווית definedEntity. בנוסף, לכל צומת יש תווית דינמית שנקבעת לפי הערך בעמודה label.

לאחר מכן, כותבים שאילתות שתואמות לצמתים על סמך סוג התווית. לדוגמה, השאילתה הבאה מוצאת צמתים באמצעות התווית Entity שהוגדרה:

GRAPH FinGraph
MATCH (node:Entity {id: 1}) -- Querying by the defined label
RETURN node.name;

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

דוגמאות לסכימה

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

בדוגמה הבאה מוצג איך ליצור טבלאות קלט וגרף מאפיינים:

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

בדוגמה הבאה נעשה שימוש באינדקס כדי לשפר את המעבר בין קצוות הפוכים. הסעיף STORING (properties) כולל עותק של מאפייני קצה, מה שמאיץ את השאילתות שמסננות לפי המאפיינים האלה. אפשר להשמיט את הסעיף STORING (properties) אם השאילתות לא מפיקות ממנו תועלת.

CREATE INDEX R_EDGE ON GraphEdge (dest_id, id, edge_id) STORING (properties),
INTERLEAVE IN GraphNode;

בדוגמה הבאה נעשה שימוש באינדקס של תוויות כדי להאיץ את ההתאמה של צמתים לפי תוויות.

CREATE INDEX IDX_NODE_LABEL ON GraphNode (label);

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

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

אופטימיזציה של עדכונים בכמות גדולה של מאפיינים דינמיים באמצעות DML

שינוי של מאפיינים דינמיים באמצעות פונקציות כמו JSON_SET ו-JSON_REMOVE כולל פעולות קריאה-שינוי-כתיבה. העדכון הזה יכול להוביל לעלות גבוהה יותר בהשוואה לעדכון מאפיינים מסוג STRING או INT64.

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

  • עדכון של כמה שורות בפקודת DML אחת, במקום עיבוד של שורות בנפרד.

  • כשמעדכנים טווח רחב של מפתחות, כדאי לקבץ ולמיין את השורות המושפעות לפי מפתחות ראשיים. עדכון של טווחים לא חופפים בכל DML מפחית את התחרות על נעילה.

  • כדי לשפר את הביצועים, מומלץ להשתמש בפרמטרים של שאילתות בהצהרות DML במקום להגדיר אותם בהגדרות קשיחות.

על סמך ההצעות האלה, בדוגמה הבאה מוצג איך לעדכן את המאפיין is_blocked ל-100 צמתים בפקודת DML אחת. הפרמטרים של השאילתה כוללים את הפרטים הבאים:

  1. @node_ids: מפתחות של שורות GraphNode, מאוחסנים בפרמטר ARRAY. אם אפשר, קיבוץ ומיון של הפקודות האלה ב-DMLs משפרים את הביצועים.

  2. @is_blocked_values: הערכים התואמים שצריך לעדכן, שמאוחסנים בפרמטר 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)

פתרון בעיות

בקטע הזה מוסבר איך לפתור בעיות שקשורות לנתונים ללא סכימה.

הנכס מופיע כמה פעמים בתוצאה TO_JSON

בעיה

הצומת הבא מדגים את המאפיינים birthday ו-name כמאפיינים דינמיים בעמודה JSON שלו. המאפיינים birthday ו-name מופיעים כפולים בתוצאת ה-JSON של רכיב הגרף.

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

התוצאה תהיה דומה לזו:

{
  ,
  "properties": {
    "birthday": "1991-12-21 00:00:00",
    "name": "Alex",
    "id": 14,
    "label": "person",
    "properties": {
      "birthday": "1991-12-21 00:00:00",
      "name": "Alex"
    }
  }
  
}

סיבה אפשרית

כברירת מחדל, כל העמודות בטבלת הבסיס מוגדרות כמאפיינים. שימוש ב-TO_JSON או ב-SAFE_TO_JSON כדי להחזיר רכיבי גרף גורם לשכפול מאפיינים. הסיבה לכך היא שהעמודה JSON (properties) היא מאפיין שמוגדר בסכימה, ואילו המפתחות ברמה הראשונה של JSON מוגדרים כמאפיינים דינמיים.

הפתרון המומלץ

כדי להימנע מהתנהגות כזו, משתמשים בסעיף PROPERTIES ALL COLUMNS EXCEPT כדי להחריג את העמודה properties כשמגדירים מאפיינים בסכימה, כמו בדוגמה הבאה:

CREATE OR REPLACE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNode
      PROPERTIES ALL COLUMNS EXCEPT (properties)
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  );

אחרי שינוי הסכימה, אין כפילויות ברכיבי הגרף שמוחזרים מסוג הנתונים JSON.

GRAPH FinGraph
MATCH (n: Person {id: 1})
RETURN TO_JSON(n) AS n;

השאילתה הזו מחזירה את התוצאות הבאות:

{
  
  "properties": {
    "birthday": "1991-12-21 00:00:00",
    "name": "Alex",
    "id": 1,
    "label": "person",
  }
}

בעיות נפוצות כשערכי הנכסים לא מומרים בצורה תקינה

כדי לפתור את הבעיות הבאות, תמיד צריך להשתמש בהמרות של ערכי מאפיינים כשמשתמשים במאפיין בתוך ביטוי של שאילתה.

השוואה בין ערכי מאפיינים ללא המרה

בעיה

No matching signature for operator = for argument types: JSON, STRING

סיבה אפשרית

השאילתה לא ממירה את ערכי הנכס בצורה תקינה. לדוגמה, המאפיין name לא מומר לסוג STRING בהשוואה:

GRAPH FinGraph
MATCH (p:Person)
WHERE p.name = "Alex"
RETURN p.id;

הפתרון המומלץ

כדי לפתור את הבעיה הזו, צריך להשתמש בהמרת ערך לפני ההשוואה.

GRAPH FinGraph
MATCH (p:Person)
WHERE STRING(p.name) = "Alex"
RETURN p.id;

התוצאה תהיה דומה לזו:

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

אפשרות אחרת היא להשתמש במסנן נכסים כדי לפשט השוואות של ערכים שווים, שבהן מתבצעת המרה אוטומטית של ערכים. שימו לב שסוג הערך (Alex) צריך להיות זהה לסוג המאפיין STRING ב-JSON.

GRAPH FinGraph
MATCH (p:Person {name: 'Alex'})
RETURN p.id;

התוצאה תהיה דומה לזו:

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

RETURN DISTINCT שימוש בערך הנכס ללא המרה

בעיה

Column order_number_str of type JSON cannot be used in `RETURN DISTINCT

סיבה אפשרית

בדוגמה הבאה, הערך order_number_str לא הומר לפני השימוש בו בהצהרה RETURN DISTINCT:

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

הפתרון המומלץ

כדי לפתור את הבעיה, צריך להשתמש בהמרת ערך לפני RETURN DISTINCT.

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

התוצאה תהיה דומה לזו:

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

מאפיין שמשמש כמפתח לקיבוץ ללא המרה

בעיה

Grouping by expressions of type JSON is not allowed.

סיבה אפשרית

בדוגמה הבאה, הערך t.order_number_str לא מומר לפני שמשתמשים בו כדי לקבץ אובייקטים ב-JSON:

GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN t.order_number_str, COUNT(*) AS total_transfers;

הפתרון המומלץ

כדי לפתור את הבעיה, צריך להשתמש בהמרת ערך לפני השימוש במאפיין כמפתח קיבוץ.

GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN STRING(t.order_number_str) AS order_number_str, COUNT(*) AS total_transfers;

התוצאה תהיה דומה לזו:

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

מאפיין שמשמש כמפתח לסידור ללא המרה

בעיה

ORDER BY does not support expressions of type JSON

סיבה אפשרית

בדוגמה הבאה, הערך t.amount לא מומר לפני שהוא משמש לסידור התוצאות:

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;

הפתרון המומלץ

כדי לפתור את הבעיה, צריך לבצע המרה ב-t.amount בסעיף 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;

התוצאה תהיה דומה לזו:

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

חוסר התאמה בין סוגים במהלך ההמרה

בעיה

The provided JSON input is not an integer

סיבה אפשרית

בדוגמה הבאה, המאפיין order_number_str מאוחסן כסוג נתונים של JSON‏ STRING. אם תנסו לבצע המרה ל-INT64, תוחזר שגיאה.

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

הפתרון המומלץ

כדי לפתור את הבעיה, צריך להשתמש בממיר הערכים המדויק שמתאים לסוג הערך.

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

התוצאה תהיה דומה לזו:

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

אפשרות אחרת היא להשתמש בממיר גמיש כשהערך ניתן להמרה לסוג היעד, כמו בדוגמה הבאה:

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

התוצאה תהיה דומה לזו:

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

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