2014年8月17日 星期日

Google Cloud Datastore with Go 學習筆記 - Queries

Google Cloud Datastore with Go 學習筆記 - Queries

類似關聯式資料庫,應用成是同樣也可以針對 entity 進行查詢 & 排序…等操作。

  • 原本的 where 過濾條件,在 Datastore 中變成了 filters 的方式,而 filter 可用在 property 的 value、keys、或是 ancestor 上。

  • 排序則是使用 sort orders 來進行。

  • 最後的回傳資料可以是完整的 entity 集合,也可以是包含部分屬性的 entity 集合,甚至可以只是 entity keys。

而標準的查詢包含了以下資訊:

  • entity kind
  • 0 或多個 filter 條件
  • 0 或多個用來排序結果的 sort orders 條件

為了確保執行的效能 & 節省記憶體,建議盡量每次查詢都限定回傳的資料筆數。

Query structure

每個 query 會包含有 entity kind、零或多個 filter、以及零或多個 sort order

Filters

filter 可以用在 property / key / ancestor 三種資訊上。

Property Filter

以下直接用範例來說明:

type Person struct {
    Name      string
    City      string
    BirthYear int
    Height    int
}

func handler_BasicQuery(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    //查詢 Height 屬性大於 170 的資料
    //根據 Height 屬性進行 desc 排序
    q := datastore.NewQuery("Person").Filter("Height > ", 170).Order("-Height")
    var people []Person
    q.GetAll(c, &people)
    for p := range people {
        fmt.Fprintf(w, `Name=%q, City=%q, BirthYear=%d, Height=%d\n`, people[p].Name, people[p].City, people[p].BirthYear, people[p].Height)
    }
}

Key Filter

若要使用 entity key 來進行 filter,必須使用特別的關鍵字 _key_

以下舉個例:

q := datastore.NewQuery("Person").Filter("__key__ >", lastSeenKey)

key 值在 datastore 中是如何排序的呢? 是依照以下的順序來排:

  1. Ancestor path
  2. Entity kind
  3. Identifier (Numeric ID 排在自訂的 key name 之前)

Ancestor Filter

指定 ancestor 也是種 filter 的方式,以下舉個簡單的例子:

q := datastore.NewQuery("Person").Ancestor(ancestorKey)

ancestorKey 若是 nil 會產生錯誤,不會回傳 root entities 喔!

Sort orders

要進行排序需要指定 property & 排序方式(asc or desc),以下範例說明:

//Name asc
q := datastore.NewQuery("Person").Order("Name")

//Height desc
q := datastore.NewQuery("Person").Order("-Height")

//多個條件排序
q := datastore.NewQuery("Person").Order("Name").Order("-Height")

Special query types

Kindless queries

若查詢中沒有指定 kind & ancestor,則表示是針對此應用程式在 Datastore 中所有的 entity 進行查詢。

有些特別的資訊(例如:statistics entities & Blobstore metadata entities)就必須要透過這種方式搜尋。

而不指定 kind & ancestor 的搜尋方式也是有限制的,就是不能使用 property 進行搜尋 or 將資料排序,只能使用 _key_ 關鍵字來針對 entity key 進行搜尋。

Keys-only queries

透過 KeysOnly() 方法取得 entity 的 key 值集合,舉個簡單範例:

q := datastore.NewQuery("Person").KeysOnly()

搜尋限制

在 Datastore 上進行搜尋是有些限制的,說明如下:

  • 在搜尋中沒有包含指定 property 的 entity 都將被忽略
    同樣 kind 底下的 entity 的 property 內容是可以不同的,因此在 filter or sort 時若指定的 property 有可能不會存在於所有的 entity 中,這時沒有該 property 的 entity 都將被忽略。

  • “不等於”的 filter 最多只能用在一個 property 上

//合法搜尋,兩個 filter 都用在相同的 property 上
q := datastore.NewQuery("Person").
        Filter("BirthYear >=", minBirthYear).
        Filter("BirthYear <=", maxBirthYear)

//不合法的搜尋,兩個 filter 分別用在不同的 property 上
q := datastore.NewQuery("Person").
        Filter("BirthYear >=", minBirthYear).
        Filter("Height <=", maxHeight)

//跟"="混用的話,沒有 property 數量限制(但不等於的 filter 還是只能用在同一個 property 上)
q := datastore.NewQuery("Person").
        Filter("Name =", targetName).
        Filter("City =", targetCity).
        Filter("BirthYear >=", minBirthYear).
        Filter("BirthYear <=", maxBirthYear)
  • “不等於” filter 所指定的 property 必須先進行排序
//合法搜尋,BirthYear 為第一個排序的 property
q := datastore.NewQuery("Person").
        Filter("BirthYear >=", minBirthYear).
        Order("BirthYear").
        Order("Name")

//不合法搜尋,BirthYear 必須放置於第一個 property 進行排序
q := datastore.NewQuery("Person").
        Filter("BirthYear >=", minBirthYear).
        Order("Name")

//不合法搜尋,Name 為第一個排序的 property (必須是 BirthYear)
q := datastore.NewQuery("Person").
        Filter("BirthYear >=", minBirthYear).
        Order("Name").
        Order("BirthYear")
  • 含有多個 value 的 property 在 filter or sort 時可能會有出乎意料之外的結果
    這個部分等我真的遇到再來寫…..

  • 在 transaction 中的 query,必須包含 ancestor filter
    因為在 transaction 中的 query 必須屬於相同的 entity group,因此必須帶上 ancestor filter 資訊。

取得查詢結果

取得查詢結果有幾種方式,介紹如下:

使用 Run method

透過 Run method 可以得到一個 Iterator,並搭配 Next method 可以一個一個的取得搜尋結果,範例如下:

q := datastore.NewQuery("Person")
t := q.Run(c)
for {
        var p Person
        k, err := t.Next(&p)
        if err == datastore.Done {
                break // No further entities match the query.
        }
        if err != nil {
                c.Errorf("fetching next Person: %v", err)
                break
        }
        // Do something with Person p and Key k
}

使用 GetAll method

這個方式可以將搜尋結果一次取回來,可參考之前的範例!

使用 peojection query

可用來取得 entity 中部分的 property 的資訊。

使用 keys-only query

透過 KeyOnly method 僅取得 entity key。

使用 Limit & Offset (達到資料分頁目的)

可透過 Limit & Offset 兩個 method 來取得特定範圍的 entity 資料,達到資料分頁效果,範例如下:

//取得第 6~10 筆的資料
q := datastore.NewQuery("Person").Order("-Height").Limit(5).Offset(5)

比較需要注意的是, 雖然 Offset 可以忽略前幾筆資料,但其實資料還是有回傳的(這些都是要收費的),因此若是要 僅回傳 特定筆數資料,就必須使用 query cursor 來處理。

Query cursors

透過 query cursor 就可以取得特定範圍的資料而沒有 Offset 的問題。

當執行了一個取得資料的動作後,應用程式可以取得一個 cursor(base64 編碼過的字串),用來表是上一次取得結果的索引位置,如此一來就可以用 cursor 來取得下一批的資料。

除了可以透過 cursor 指定取得資料的起始點之外,還可以透過指定 end cursor 來限制回傳資料的範圍。

使用限制

cursor 在使用上有些限制存在:

  • cursor 僅有在相同的應用程式內,並使用相同的 query(相同的 kind & ancestor & property filter) 時才有效。

  • 若用在有多個值的 property 上時,cursor 的運作可能會不如預期。

  • 新的 App Engine 功能釋出時,可能會造成某些 cursor 不再有效,此時 Datastore 會回傳 error。

沒有留言:

張貼留言