טיפ neo4j - בחירת צומת באקראי

23/05/2023

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

בשביל הדוגמה אני לוקח את בסיס הנתונים Movies מתוך התבנית באתר https://sandbox.neo4j.com. אתר זה מאפשר להקים בסיסי נתונים של neo4j בקלות למשחקים אונליין. בסיס הנתונים של הסרטים כולל מידע על סרטים והאנשים שלקחו בהם חלק, למשל השאילתה:

MATCH (m:Movie) WHERE m.title = 'The Polar Express'
RETURN m.released

תחזיר לי באיזה שנה יצא הסרט The Polar Express, והשאילתה:

MATCH (m:Movie) WHERE m.title = 'The Polar Express'
MATCH (p:Person)-[:ACTED_IN]-(m)
RETURN p.name

תחזיר את שמות השחקנים מהסרט.

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

MATCH (m:Movie)
RETURN m.title
ORDER BY rand()
LIMIT 1

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

MATCH (p:Person{name: "Tom Hanks"})
MATCH (p)-[:ACTED_IN]-(m:Movie)
RETURN m.title

אבל טריק ה rand כבר לא ייתן לי סרט אקראי:

// always returns Apollo 13

MATCH (p:Person{name: "Tom Hanks"})
MATCH (p)-[:ACTED_IN]-(m:Movie)
RETURN m.title
ORDER BY rand()
LIMIT 1

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

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

MATCH (p:Person{name: "Tom Hanks"})
MATCH (p)-[:ACTED_IN]-(m:Movie)
WITH collect(m) as movies
RETURN apoc.coll.randomItem(movies)

סך הכל אפשר למצוא את כל הפונקציות המובנות ב neo4j בתיעוד בקישור: https://neo4j.com/labs/apoc/4.2/overview/, ולקוות לעתיד וורוד ונטול SQL.