שאילתות ב-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 ואם היא כוללת מסנן כזה, היא לא יכולה לכלול מסננים אחרים של אי-שוויון.

האופרטור 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. מזהה (שם המפתח או מזהה מספרי)

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

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

מסנני צאצאים

אפשר לסנן את השאילתות ב-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 של האפליקציה . שרת הפיתוח מוסיף באופן אוטומטי הצעות לקובץ הזה כשהוא נתקל בשאילתות שלא ניתן להריץ עם האינדקסים הקיימים. אפשר לשנות את ההגדרות של האינדקסים באופן ידני על ידי עריכת הקובץ לפני העלאת האפליקציה.

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

ה-API של Java Datastore ברמה נמוכה מספק את המחלקה 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);

מה השלב הבא?