שאילתות ב-Datastore

שאילתה ב-Datastore מאחזרת ישויות מ-Datastore שעומדות בקבוצה מסוימת של תנאים.

שאילתה טיפוסית כוללת את הרכיבים הבאים:

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

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

בדף הזה מתוארים המבנה והסוגים של שאילתות שמשמשות ב-App Engine לאחזור נתונים מ-Datastore.

מסננים

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

מסננים של מאפיינים

מסנן נכסים מציין

  • שם של נכס
  • אופרטור השוואה
  • ערך של מאפיין
לדוגמה:

Filter propertyFilter =
    new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);
Query q = new Query("Person").setFilter(propertyFilter);

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

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

אופרטור משמעות
EQUAL שווה ל:
LESS_THAN קטן מ:
LESS_THAN_OR_EQUAL פחות מ- או שווה ל-
GREATER_THAN גדול מ:
GREATER_THAN_OR_EQUAL גדול מ- או שווה ל-
NOT_EQUAL שונה מ-
IN חבר ב- (שווה לאחד מהערכים ברשימה שצוינה)

האופרטור NOT_EQUAL מבצע בפועל שתי שאילתות: אחת שבה כל המסננים האחרים לא משתנים והמסנן NOT_EQUAL מוחלף במסנן LESS_THAN, ואחת שבה הוא מוחלף במסנן GREATER_THAN. התוצאות ממוזגות לפי הסדר. שאילתה יכולה לכלול לכל היותר מסנן אחד מסוג NOT_EQUAL filter, ושאילתה שכוללת מסנן כזה לא יכולה לכלול מסננים אחרים של אי-שוויון.

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

שאילתה אחת שמכילה את האופרטורים NOT_EQUAL או IN מוגבלת ל-30 שאילתות משנה לכל היותר.

מידע נוסף על האופן שבו שאילתות NOT_EQUAL ו-IN מתורגמות למספר שאילתות במסגרת JDO/JPA זמין במאמר שאילתות עם מסנני != ו-IN.

מסננים עיקריים

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

Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query("Person").setFilter(keyFilter);

יש תמיכה גם במיון בסדר עולה ב-Entity.KEY_RESERVED_PROPERTY.

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

  1. נתיב הצאצאים
  2. סוג הישות
  3. מזהה (שם המפתח או מזהה מספרי)

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

שאילתות על מפתחות משתמשות באינדקסים בדיוק כמו שאילתות על מאפיינים, ונדרשים אינדקסים מותאמים אישית באותם מקרים, עם כמה יוצאים מן הכלל: לא נדרש אינדקס מותאם אישית לסינון אי-שוויון או לסדר מיון עולה במפתח, אבל נדרש אינדקס מותאם אישית לסדר מיון יורד במפתח. כמו בכל השאילתות, שרת פיתוח להצגה באינטרנט (development web server) יוצר רשומות מתאימות בקובץ תצורה כשבודקים שאילתה שצריכה אינדקס בהתאמה אישית.

מסנני צאצאים

אפשר לסנן את השאילתות ב-Datastore לפי ancestor ספציפי, כך שהתוצאות שיוחזרו יכללו רק ישויות שנוצרו מה-ancestor הזה:

Query q = new Query("Person").setAncestor(ancestorKey);

סוגים מיוחדים של שאילתות

יש כמה סוגים ספציפיים של שאילתות שחשוב לציין:

שאילתות בלי סיווג

שאילתה ללא סיווג וללא מסנן ישויות אם מאחזרת את כל הישויות של אפליקציה מ-Datastore. זה כולל ישויות שנוצרו ומנוהלות על ידי תכונות אחרות של App Engine, כמו ישויות של נתונים סטטיסטיים ו ישויות של מטא-נתונים של Blobstore (אם יש כאלה). שאילתות ללא סוג כאלה לא יכולות לכלול מסננים או סדר מיון של ערכי מאפיינים. עם זאת, הם יכולים לסנן לפי מפתחות של ישויות על ידי ציון Entity.KEY_RESERVED_PROPERTY כשם המאפיין:

Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query().setFilter(keyFilter);

שאילתות לגבי ישויות אם

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

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Entity tom = new Entity("Person", "Tom");
Key tomKey = tom.getKey();
datastore.put(tom);

Entity weddingPhoto = new Entity("Photo", tomKey);
weddingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/wedding_photo.jpg");

Entity babyPhoto = new Entity("Photo", tomKey);
babyPhoto.setProperty("imageURL", "http://domain.com/some/path/to/baby_photo.jpg");

Entity dancePhoto = new Entity("Photo", tomKey);
dancePhoto.setProperty("imageURL", "http://domain.com/some/path/to/dance_photo.jpg");

Entity campingPhoto = new Entity("Photo");
campingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/camping_photo.jpg");

List<Entity> photoList = Arrays.asList(weddingPhoto, babyPhoto, dancePhoto, campingPhoto);
datastore.put(photoList);

Query photoQuery = new Query("Photo").setAncestor(tomKey);

// This returns weddingPhoto, babyPhoto, and dancePhoto,
// but not campingPhoto, because tom is not an ancestor
List<Entity> results =
    datastore.prepare(photoQuery).asList(FetchOptions.Builder.withDefaults());

שאילתות לגבי כל ישויות האם

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

Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query().setAncestor(ancestorKey).setFilter(keyFilter);

בדוגמה הבאה אפשר לראות איך מאחזרים את כל הישויות שהן צאצאים של ישות אם נתונה:

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Entity tom = new Entity("Person", "Tom");
Key tomKey = tom.getKey();
datastore.put(tom);

Entity weddingPhoto = new Entity("Photo", tomKey);
weddingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/wedding_photo.jpg");

Entity weddingVideo = new Entity("Video", tomKey);
weddingVideo.setProperty("videoURL", "http://domain.com/some/path/to/wedding_video.avi");

List<Entity> mediaList = Arrays.asList(weddingPhoto, weddingVideo);
datastore.put(mediaList);

// By default, ancestor queries include the specified ancestor itself.
// The following filter excludes the ancestor from the query results.
Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, tomKey);

Query mediaQuery = new Query().setAncestor(tomKey).setFilter(keyFilter);

// Returns both weddingPhoto and weddingVideo,
// even though they are of different entity kinds
List<Entity> results =
    datastore.prepare(mediaQuery).asList(FetchOptions.Builder.withDefaults());

שאילתות עם מילות מפתח בלבד

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

Query q = new Query("Person").setKeysOnly();

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

שאילתות של תחזיות

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

מיון ההזמנות

סדר המיון של שאילתה מציין

  • שם של נכס
  • כיוון המיון (עולה או יורד)

לדוגמה:

// Order alphabetically by last name:
Query q1 = new Query("Person").addSort("lastName", SortDirection.ASCENDING);

// Order by height, tallest to shortest:
Query q2 = new Query("Person").addSort("height", SortDirection.DESCENDING);

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

Query q =
    new Query("Person")
        .addSort("lastName", SortDirection.ASCENDING)
        .addSort("height", SortDirection.DESCENDING);

אם לא מציינים סדר מיון, התוצאות מוחזרות בסדר שבו הן מאוחזרות מ-Datastore.

הערה: בגלל האופן שבו Datastore מבצע שאילתות, אם שאילתה מציינת מסנני אי-שוויון במאפיין וממיינת סדרים במאפיינים אחרים, המאפיין שמשמש במסנני אי-השוויון חייב להיות מסודר לפני המאפיינים האחרים.

מדדים

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

מערכת App Engine מגדירה מראש אינדקס פשוט בכל מאפיין של ישות. אפליקציית App Engine יכולה להגדיר עוד אינדקסים בהתאמה אישית בקובץ הגדרות אינדקס בשםdatastore-indexes.xml, שנוצר בספרייה /war/WEB-INF/appengine-generated של האפליקציה . שרת הפיתוח מוסיף באופן אוטומטי הצעות לקובץ הזה כשהוא נתקל בשאילתות שלא ניתן להריץ עם האינדקסים הקיימים. אפשר לכוונן את האינדקסים באופן ידני על ידי עריכת הקובץ לפני העלאת האפליקציה.

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

‫Java Datastore API ברמה נמוכה מספק את המחלקה Query ליצירת שאילתות ואת הממשק PreparedQuery לאחזור ישויות מ-Datastore:

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Filter heightMinFilter =
    new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);

Filter heightMaxFilter =
    new FilterPredicate("height", FilterOperator.LESS_THAN_OR_EQUAL, maxHeight);

// Use CompositeFilter to combine multiple filters
CompositeFilter heightRangeFilter =
    CompositeFilterOperator.and(heightMinFilter, heightMaxFilter);

// Use class Query to assemble a query
Query q = new Query("Person").setFilter(heightRangeFilter);

// Use PreparedQuery interface to retrieve results
PreparedQuery pq = datastore.prepare(q);

for (Entity result : pq.asIterable()) {
  String firstName = (String) result.getProperty("firstName");
  String lastName = (String) result.getProperty("lastName");
  Long height = (Long) result.getProperty("height");

  out.println(firstName + " " + lastName + ", " + height + " inches tall");
}

שימו לב לשימוש ב-FilterPredicate וב-CompositeFilter כדי ליצור מסננים. אם מגדירים רק מסנן אחד בשאילתה, אפשר להשתמש רק ב-FilterPredicate:

Filter heightMinFilter =
    new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);

Query q = new Query("Person").setFilter(heightMinFilter);

אבל אם רוצים להגדיר יותר ממסנן אחד בשאילתה, צריך להשתמש ב-CompositeFilter, שדורש לפחות שני מסננים. בדוגמה שלמעלה נעשה שימוש בכלי העזר לקיצורי דרך CompositeFilterOperator.and. בדוגמה הבאה מוצגת דרך אחת ליצירת מסנן OR מורכב:

Filter tooShortFilter = new FilterPredicate("height", FilterOperator.LESS_THAN, minHeight);

Filter tooTallFilter = new FilterPredicate("height", FilterOperator.GREATER_THAN, maxHeight);

Filter heightOutOfRangeFilter = CompositeFilterOperator.or(tooShortFilter, tooTallFilter);

Query q = new Query("Person").setFilter(heightOutOfRangeFilter);

מה השלב הבא?