טיפ neo4j: איחוד צמתים

13/07/2023

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

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

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

MATCH (w:Word)-[:IN_LANGUAGE]-(l:Language)

CALL {
    WITH w
    MATCH (m:Word)-[:IN_LANGUAGE]-(l)
    WHERE m <> w AND m.text = w.text
    RETURN w AS innerWord
}

WITH innerWord.text as text, collect(innerWord) AS nodes
CALL apoc.refactor.mergeNodes(nodes) YIELD node
RETURN node;

בואו נקרא ונתרגם:

  1. שורה ראשונה מחפשת מילים. כל פעם שהיא מוצאת מילה בבסיס הנתונים היא שומרת את הצומת במשתנה בשם w, ואת השפה שבה המילה כתובה במשתנה בשם l.

  2. בתוך בלוק ה CALL מופעלת תת-שאילתה. תת השאילתה לוקחת את w מבחוץ ומחפשת צמתים אחרים של מילים שאינם w אבל הן באותה שפה ויש להן את אותו טקסט.

  3. מחוץ לבלוק ה CALL הפקודה WITH מייצרת אגרגציה ומסדרת את השורות שחוזרות מהשאילתה - בכל שורה יהיה הטקסט של המילה ורשימת כל צמתי המילים שיש להם את הטקסט הזה.

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

סך הכל Cypher (שזו שפת השאילתות של neo4j) יותר פשוט לכתיבה וקריאה מ SQL, וכולל המון פונקציות מובנות כמו collect ו mergeNodes שעוזרות בכתיבת שאילתות.

נ.ב. הפיל שבחדר הוא כמובן חוסר התמיכה של neo4j באינדקס ייחודי על מספר מאפיינים (במקרה שלנו טקסט של מילה + השפה שלה). יש שמועה שמנגנון כזה קיים בגירסת ה Enterprise שלהם אבל לא ניסיתי. אופציה שכן עובדת במקום להיתקל בבאגים מסוג זה היא ליצור מאפיין ייחודי בצומת שיהיה מורכב מהטקסט של המילה וקוד שפה שלה. כמובן שאז צריך לזכור ליצור ולעדכן את המאפיין הזה מתוך הקוד בכל עבודה עם הצומת.