1. 事前準備
您使用的許多應用程式都會直接在裝置上儲存資料。舉例來說,「時鐘」會保存週期性鬧鐘,「Google 地圖」會儲存最近搜尋內容的清單,而「聯絡人」可新增、編輯與移除聯絡人資訊。
資料持續性意指在裝置上儲存或保存資料,是 Android 開發工作中重要的一環。持續性資料可確保使用者產生的內容不會在應用程式關閉時遺失,也可確保從網際網路下載的資料妥善儲存,這樣之後就不必重新下載資料。
Android 應用程式經常透過 Android SDK 提供的 SQLite 保存資料。SQLite 提供了關聯資料庫,可讓您以類似使用 Kotlin 類別建立資料結構的方式呈現資料。本程式碼研究室將說明 SQL (結構化查詢語言) 的基礎知識。SQL 雖然不是實際的程式設計語言,但可讓您只編寫幾行程式碼,就輕鬆靈活地讀取及修改 SQLite 資料庫。
瞭解 SQL 基礎知識後,您即可在本單元的後續部分中使用 Room 程式庫,為應用程式加入持續性機制。
2. 關聯資料庫的重要概念
什麼是資料庫?
如果您很熟悉 Google 試算表等試算表程式,就已經瞭解資料庫的基本類比。
試算表是由同一活頁簿中的個別資料表或單個試算表組成。
每份資料表均包含欄和列,欄用來定義資料代表的內容,列則用於呈現個別項目,內含各欄的值。以下圖為例,您可將各欄內容分別定義為學生的 ID、名字、主修科系和成績。
每列都含有一位學生在各欄的資料值。
關聯資料庫採用相同的運作方式。
- 針對您想呈現的資料,資料表定義了高階分組方式,就像上方試算表畫面中的「student」和「professor」工作表。
- 欄則定義資料表中每列包含的資料。
- 列包含資料表中每欄的值所構成的實際資料。
如果您已瞭解 Kotlin 中的類別和物件,同樣概念也適用於關聯資料庫的架構。
data class Student(
id: Int,
name: String,
major: String,
gpa: Double
)
- 類別好比資料表,用於建立您想在應用程式中呈現的資料結構。
- 屬性好比資料欄,定義了類別的每個例項應包含的特定資料內容。
- 物件好比資料列,屬於實際資料。物件含有類別中所定義每個屬性的值,就像資料列含有資料表中所定義各欄的值一樣。
正如試算表可以包含多份工作表,應用程式也能包含多種類別,而資料庫則可包含多份資料表。如果資料庫能夠建立資料表之間的關係結構,便稱為關聯資料庫。舉例來說,一名研究生可以有一位博士論文指導教授,不過這位教授可以指導多名學生的博士論文。
關聯資料庫中的每份資料表都有一欄包含各列的專屬 ID,例如自動遞增的整數。這個 ID 稱為主鍵。
當某份資料表參照另一份資料表的主鍵時,則稱為外鍵。如果出現外鍵,就意味著兩份資料表有所關聯。
什麼是 SQLite?
SQLite 是常用的關聯資料庫。具體來說,SQLite 是指輕量化的 C 程式庫,用於以 SQL (有時唸成「sequel」) 管理關聯資料庫。
您不必瞭解 C 或任何全新的程式設計語言,就能使用關聯資料庫。有了 SQL,只需編寫幾行程式碼,就能輕鬆在關聯資料庫中新增及擷取資料。
使用 SQLite 呈現資料
您已熟悉 Kotlin 中的 Int
和 Boolean
等資料類型,而 SQLite 資料庫也會使用資料類型!資料表的欄必須包含特定資料類型。下表列出常見 Kotlin 資料類型與相等 SQLite 資料類型的對應關係。
Kotlin 資料類型 | SQLite 資料類型 |
| INTEGER |
| VARCHAR 或 TEXT |
| BOOLEAN |
| REAL |
資料庫中的資料表以及各資料表內的欄統稱為「結構定義」。在下一個部分中,您將下載範例資料集,並進一步瞭解結構定義。
3. 下載範例資料集
這個程式碼研究室的資料庫適用於一個虛構的電子郵件應用程式。本程式碼研究室將列舉常見作業為例 (比如排序與篩選郵件,或者依主旨文字或寄件者進行搜尋),展示 SQL 的所有強大功能。這些例子也可確保您在後續課程中使用 Room 前,先具備處理應用程式內各種可能情況的經驗。
請按這裡,從「SQL 基本資訊」程式碼研究室的 GitHub 存放區 compose
分支中下載範例專案。
使用資料庫檢查器
如要使用資料庫檢查器,請按照下列步驟操作:
- 在 Android Studio 中執行「SQL 基本資訊」程式碼研究室的應用程式。應用程式啟動後,會顯示以下畫面。
- 在 Android Studio 中,依序點選「View」>「Tool Windows」>「App Inspection」。
畫面底部現在會出現標示為「App Inspection」的新分頁,且已選取「Database Inspector」分頁標籤。另外還有兩個您不需使用的分頁。載入可能需要幾秒鐘的時間,但載入後,畫面左側會顯示含有資料表的清單,供您選取以執行查詢。
- 按一下「Open New Query Tab」按鈕開啟窗格,即可對資料庫執行查詢。
email
資料表含有 7 個欄:
id
:主鍵。subject
:電子郵件主旨行。sender
:電子郵件的來源電子郵件地址。folder
:郵件所在的資料夾,例如收件匣或垃圾郵件匣。starred
:使用者是否為電子郵件加上星號。read
:使用者是否閱讀電子郵件。received
:收到電子郵件當下的時間戳記。
4. 使用 SELECT 陳述式讀取資料
SQL SELECT
陳述式
SQL 陳述式 (有時稱為查詢) 的用途為讀取或操控資料庫。
您可以使用 SELECT
陳述式讀取 SQLite 資料庫中的資料。簡單的 SELECT
陳述式包含 SELECT
關鍵字,後方依序為資料欄名稱、FROM
關鍵字和資料表名稱。每個 SQL 陳述式皆以半形分號 (;
) 結尾。
SELECT
陳述式也可以傳回多個欄中的資料。您必須使用半形逗號分隔資料欄名稱。
如要選取資料表中的所有欄,請將資料欄名稱替換為萬用字元 (*
)。
無論選取特定或所有資料欄,這類簡單的 SELECT
陳述式都會傳回資料表中的每一列。您只需指定要傳回的資料欄名稱。
使用 SELECT
陳述式讀取電子郵件資料
電子郵件應用程式需執行的其中一項主要作業是顯示郵件清單。透過 SQL 資料庫,您可以使用 SELECT
陳述式取得這類資訊。
- 確認已在「Database Inspector」分頁中選取「email」資料表。
- 首先嘗試選取
email
資料表中每列的所有欄。
SELECT * FROM email;
- 按一下文字方塊右下角的「Run」按鈕。您會發現此查詢傳回整份
email
資料表。
- 接著嘗試只選取每列的主旨。
SELECT subject FROM email;
- 請注意,此查詢會再次傳回所有列,但只有特定一欄的值。
- 您也可以選取多個欄。請嘗試選取主旨和寄件者。
SELECT subject, sender FROM email;
- 您會發現此查詢傳回
email
資料表中的所有列,但只有「subject」和「sender」欄的值。
恭喜!您成功執行了第一個查詢,做得好!您已經踏出學習 SQL 陳述式的第一步。
如要編寫更具體的 SELECT
陳述式,您可以新增子句,用於指定部分資料,甚至變更輸出內容的格式。在後續部分中,您將瞭解常用的 SELECT
陳述式子句,並學習如何設定資料格式。
5. 使用 SELECT 陳述式搭配匯總函式及 DISTINCT 關鍵字
使用匯總函式減少從資料欄傳回的值
SQL 陳述式不只可以傳回資料列。SQL 提供多種函式,可對特定資料欄執行作業或運算程序,例如找出最大值,或者計算特定一欄可能的不重複值數量。這類函式稱為匯總函式。您可以從特定資料欄中傳回一個值,而不是傳回該欄的所有資料。
以下列舉一些 SQL 匯總函式:
COUNT()
:傳回符合查詢的資料列總數。SUM()
:將所選資料欄中每列的值相加,傳回總和。AVG()
:傳回所選資料欄中所有值的平均值。MIN()
:傳回所選資料欄中的最小值。MAX()
:傳回所選資料欄中的最大值。
您可以不直接使用資料欄名稱,改為呼叫匯總函式,並在半形括號中傳入資料欄名稱做為引數。
呼叫匯總函式只會傳回一個值,而非資料表中每列在該欄的值。
如果不需讀取資料庫中的所有資料,即可使用匯總函式,有效率地計算特定值。舉例來說,您可以找出某欄中所有值的平均值,不必將整個資料庫載入清單並手動計算。
以下是實際對 email
資料表使用匯總函式的例子:
- 應用程式可能會需要取得所收到電子郵件的總數。如要計算這個值,您可以使用
COUNT()
函式和萬用字元 (*
)。
SELECT COUNT(*) FROM email;
- 此查詢會傳回一個值。您可以完全使用 SQL 查詢執行這項操作,不必透過任何 Kotlin 程式碼手動計算資料列數量。
- 如要取得收到最新郵件的時間,您可以對「received」欄使用
MAX()
函式,因為最新 Unix 時間戳記的數字最大。
SELECT MAX(received) FROM email;
- 此查詢會傳回一項結果,也就是「received」欄中數字最大 (意即最新) 的時間戳記。
使用 DISTINCT
篩選重複結果
選取資料欄時,您可以在前方加上 DISTINCT
關鍵字。如要從查詢結果中移除重複內容,這種做法就能派上用場。
舉例來說,許多電子郵件應用程式都提供自動完成地址的功能,而且您可能會想在一份清單中加入並顯示所有寄件者地址。
- 執行以下查詢,傳回每列在「
sender
」欄的值。
SELECT sender FROM email;
- 您會發現結果中包含許多重複內容。對使用者而言,這絕對不是理想的體驗!
- 在「sender」欄前方加上
DISTINCT
關鍵字,然後重新執行查詢。
SELECT DISTINCT sender FROM email;
- 請注意,現在結果中包含較少內容,而且每個值都不重複。
您也可以在匯總函式中的欄名稱前方加上 DISTINCT
關鍵字。
假設您想知道資料庫中不重複寄件者的人數,可以使用 COUNT()
匯總函式,並在資料欄名稱 sender
前方加上 DISTINCT
關鍵字,這樣就能計算該人數。
- 執行
SELECT
陳述式,將DISTINCT sender
傳入COUNT()
函式。
SELECT COUNT(DISTINCT sender) FROM email;
- 您會發現此查詢傳回 14 位不重複寄件者。
6. 使用 WHERE 子句篩選查詢
在許多電子郵件應用程式中,使用者可以指定資料、搜尋字詞、資料夾、寄件者等特定篩選條件,只顯示相符郵件。針對這類用途,您可以在 SELECT
查詢中加入 WHERE
子句。
請在資料表名稱後方新增一行,然後依序添加 WHERE
關鍵字和運算式。編寫較複雜的 SQL 查詢時,為了方便閱讀,每行輸入一個子句是很常見的做法。
此查詢會為所選各列執行布林值檢查。如果檢查傳回 true,資料列就會納入查詢結果;如果檢查傳回 false,則資料列不會納入查詢結果。
舉例來說,電子郵件應用程式可能會提供垃圾郵件、垃圾桶、草稿的篩選器,或者讓使用者自行建立篩選器。以下說明如何使用 WHERE
子句執行這項操作:
- 執行
SELECT
陳述式,傳回email
資料表中的所有欄 (*
),並加上WHERE
子句檢查以下條件:folder = 'inbox'
。這並不是輸入錯誤,您可以在 SQL 中使用單等號檢查內容是否相同,並利用單引號 (而非雙引號) 表示字串值。
SELECT * FROM email
WHERE folder = 'inbox';
- 查詢結果只會傳回使用者收件匣中郵件的資料列。
在 WHERE
子句中使用邏輯運算子
SQL WHERE
子句可加入多個運算式。如果希望結果只包含同時符合兩項條件的資料列,可以使用 AND
關鍵字,這等同於 Kotlin and 運算子 (&&
)。
此外,如果希望結果包含符合任一條件的資料列,可以使用 OR
關鍵字,這等同於 Kotlin or 運算子 (||
)。
為了方便閱讀,您也可以使用 NOT
關鍵字否定運算式。
許多電子郵件應用程式都允許使用多個篩選器,例如只顯示未讀取的郵件。
請嘗試對 email
資料表使用下列較複雜的 WHERE
子句:
- 除了只傳回使用者收件匣中的郵件外,也可以嘗試限制查詢結果只包含未讀取的郵件,這類郵件在「read」欄的值是 false。
SELECT * FROM email
WHERE folder = 'inbox' AND read = false;
- 執行此查詢後,您會發現結果只包含使用者收件匣中未讀取的電子郵件。
- 傳回位於 important 資料夾
OR
已加星號 (starred = true
) 的所有電子郵件。如此一來,各個資料夾中的電子郵件只要已加星號,就會納入結果中。
SELECT * FROM email
WHERE folder = 'important' OR starred = true;
- 查看結果。
使用 LIKE
搜尋文字
WHERE
子句有一項非常實用的功能,那就是搜尋特定資料欄中的文字。如果要這麼做,請指定資料欄名稱,後方依序加上 LIKE
關鍵字和搜尋字串。
搜尋字串的開頭是百分比符號 (%
),後方輸入要搜尋的文字 (搜尋字詞),最後再加上另一個百分比符號 (%
)。
如要搜尋前置字串 (以特定文字開頭的結果),請省略第一個百分比符號 (%
)。
此外,如要搜尋後置字串,請省略最後的百分比符號 (%
)。
應用程式可將文字搜尋功能用於許多用途,例如搜尋主旨行包含特定文字的電子郵件,或者在使用者輸入內容時更新自動完成建議。
如要在查詢 email
資料表時使用文字搜尋功能,請按照下列說明操作。
- 這個資料庫中的寄件者和莎士比亞筆下的角色一樣,都喜歡談論傻瓜 (fool)。請執行以下查詢,取得主旨行包含「fool」一詞的電子郵件總數。
SELECT COUNT(*) FROM email
WHERE subject LIKE '%fool%';
- 查看結果。
- 執行以下查詢,傳回主旨結尾為「fool」一詞的所有資料列,當中包含每欄的值。
SELECT * FROM email
WHERE subject LIKE '%fool';
- 您會發現此查詢傳回兩個資料列。
- 執行以下查詢,傳回
sender
欄中開頭為字母h
的不重複值。
SELECT DISTINCT sender FROM email
WHERE sender LIKE 'h%';
- 您會發現此查詢傳回三個值:
helena@example.com
、hyppolytus@example.com
和hermia@example.com
。
7. 將結果分組、排序及限制結果數量
使用 GROUP BY
將結果分組
您已瞭解如何使用匯總函式和 WHERE
子句篩選及減少結果。SQL 還提供其他幾種子句,有助於設定查詢結果的格式。這些子句的功能包括將結果分組、排序,以及限制結果數量。
您可以使用 GROUP BY
子句將結果分組,找出在特定一欄有相同值的所有資料列,讓這幾列在結果中歸入同一組。這個子句不會變更結果,只會改變傳回結果的順序。
如要將 GROUP BY
子句加入 SELECT
陳述式,請新增 GROUP BY
關鍵字,後方加上要做為結果分組依據的資料欄名稱。
常見的用途是將 GROUP BY
子句搭配匯總函式使用,依照不同值區 (例如資料欄的值) 劃分這類函式的結果。舉例來說,假設您想取得 'inbox'
和 'spam'
等各資料夾中的電子郵件數量,可以一併選取「folder
」欄和 COUNT()
匯總函式,並在 GROUP BY
子句中指定「folder
」欄。
- 執行以下查詢,選取「folder」欄和
COUNT()
匯總函式結果。接著使用GROUP BY
子句,依照folder
欄的值將結果歸入不同值區。
SELECT folder, COUNT(*) FROM email
GROUP BY folder;
- 查看結果。此查詢會傳回每個資料夾的電子郵件總數。
使用 ORDER BY
將結果排序
您也可以使用 ORDER BY
子句將查詢結果排序,變更結果的順序。做法是新增 ORDER BY
關鍵字,後方依序加上資料欄名稱和排序方向。
根據預設,排序方向為遞增順序,您可以在 ORDER BY
子句中省略這個關鍵字。如果想以遞減順序排序結果,請在資料欄名稱後方加上 DESC
。
您可能會希望電子郵件應用程式優先顯示最新郵件。以下說明如何使用 ORDER BY
子句執行這項操作。
- 新增
ORDER BY
子句,根據「received
」欄排序未讀取的電子郵件。由於預設為遞增順序 (由低至高或由舊至新),您需要使用DESC
關鍵字。
SELECT * FROM email
ORDER BY received DESC;
- 查看結果。
您可以將 ORDER BY
子句與 WHERE
子句搭配使用。假設使用者想搜尋包含「fool」一詞的舊電子郵件,則可依遞增順序排序結果,優先顯示最舊的郵件。
- 選取主旨包含「fool」一詞的所有電子郵件,然後以遞增順序排序結果。由於預設為遞增順序,無指定順序就會採用遞增順序,因此您不一定要使用
ASC
關鍵字搭配ORDER BY
子句。
SELECT * FROM email
WHERE subject LIKE '%fool%'
ORDER BY received ASC;
- 您會發現此查詢傳回篩選後的結果,並優先顯示最舊 (「received」欄的值最小) 的電子郵件。
使用 LIMIT
限制結果數量
目前為止,所有範例都只從資料庫中傳回符合查詢的單一結果。在許多情況下,您只需顯示資料庫中的少數資料列,這時可以將 LIMIT
子句加入查詢,只傳回特定數量的結果。做法是新增 LIMIT
關鍵字,後方加上要傳回的資料列數量上限。如果適合一併使用排序與數量限制功能,請先編寫 ORDER BY
子句再加上 LIMIT
子句。
您可以視需要加入 OFFSET
關鍵字,後方加上另一個數字,表示要「略過」的資料列數量。舉例來說,假設共有二十筆結果,但您想略過前十筆結果,只傳回剩餘十筆結果,即可使用 LIMIT 10 OFFSET 10
。
在應用程式中,建議您只傳回使用者收件匣中的前十封電子郵件,以便加快郵件載入速度。使用者只要捲動畫面,就能瀏覽後續頁面的電子郵件。以下說明如何使用 LIMIT
子句完成這項操作。
- 執行以下
SELECT
陳述式,以遞減順序取得使用者收件匣中的所有電子郵件,並限制只顯示前十筆結果。
SELECT * FROM email
WHERE folder = 'inbox'
ORDER BY received DESC
LIMIT 10;
- 您會發現此查詢只傳回十筆結果。
- 修改查詢,加入
OFFSET
關鍵字和10
的值,然後重新執行。
SELECT * FROM email
WHERE folder = 'inbox'
ORDER BY received DESC
LIMIT 10 OFFSET 10;
- 此查詢會以遞減順序傳回十筆結果,但會略過前十筆結果。
8. 在資料庫中插入、更新及刪除資料
將資料插入資料庫
除了讀取資料庫內容外,還有其他 SQL 陳述式可將資料寫入資料庫。畢竟資料庫總要先添加資料,才會有可讀取的內容。
您可以使用 INSERT
陳述式,將資料列新增至資料庫。INSERT
陳述式以 INSERT INTO
開頭,後方為要插入資料列的資料表名稱。請在新的一行輸入 VALUES
關鍵字,後方加上一組括號,內含以半形逗號分隔的值清單。您必須按照資料庫中各欄的順序列出這些值。
假設使用者收到新電子郵件,您需要該郵件儲存到應用程式資料庫,這時可以使用 INSERT
陳述式,在 email
資料表中新增一列。
- 執行
INSERT
陳述式,並提供新電子郵件的下列資料。這是新的電子郵件,因此處於未讀取狀態,而且一開始會顯示在收件匣folder
中。系統會為id
欄提供NULL
值,這表示系統會自動以下一個可用的自動遞增整數產生id
。
INSERT INTO email
VALUES (
NULL, 'Lorem ipsum dolor sit amet', 'sender@example.com', 'inbox', false, false, CURRENT_TIMESTAMP
);
- 您會發現這項結果已插入資料庫,而且
id
設為44
。
SELECT * FROM email
WHERE sender = 'sender@example.com';
更新資料庫中的現有資料
資料插入資料表後仍可修改。您可以使用 UPDATE
陳述式,更新一或多個資料欄的值。UPDATE
陳述式以 UPDATE
關鍵字開頭,後方依序為資料表名稱和 SET
子句。
SET
子句包含 SET
關鍵字,後方為要更新的資料欄名稱。
UPDATE
陳述式通常包含 WHERE
子句,用於指定要更新的一或多個資料列,這個列或這些列在各欄的值會更改為指定的組合。
舉例來說,如果使用者想將電子郵件標示為已讀,您可以使用 UPDATE
陳述式更新資料庫。以下說明如何將先前步驟中插入的電子郵件標示為已讀。
- 執行以下
UPDATE
陳述式,設定id
為44
的資料列,讓「read
」欄的值變成true
。
UPDATE email
SET read = true
WHERE id = 44;
- 對這個特定資料列執行
SELECT
陳述式,驗證結果。
SELECT read FROM email
WHERE id = 44;
- 您會發現「read」欄的值現在是代表「true」的
1
,而不是代表「false」的0
。
從資料庫中刪除資料列
最後,您可以使用 SQL DELETE
陳述式刪除資料表中的一或多個列。DELETE
陳述式以 DELETE
關鍵字開頭,後方依序為 FROM
關鍵字、資料表名稱和 WHERE
子句,這個子句用於指定要刪除的一或多個列。
以下說明如何使用 DELETE
陳述式,從資料庫中刪除先前插入後經過更新的資料列。
- 執行以下
DELETE
陳述式,從資料庫中刪除id
為44
的資料列。
DELETE FROM email
WHERE id = 44;
- 使用
SELECT
陳述式驗證變更。
SELECT * FROM email
WHERE id = 44;
- 您會發現
id
為44
的資料列已不存在。
9. 摘要
恭喜!本次學習成果相當豐碩!您現在可以使用 SELECT
陳述式讀取資料庫內容,並加入 WHERE
、GROUP BY
、ORDER BY
和 LIMIT
子句篩選結果。此外,也瞭解了常用的匯總函式、如何以 DISTINCT
關鍵字指定不重複結果,以及如何運用 LIKE
關鍵字對資料欄中的值執行文字搜尋。最後,您還學到如何使用 INSERT
、UPDATE
和 DELETE
陳述式,在資料表中插入、更新及刪除資料列。
這些技巧將可直接應用到 Room;有了 SQL 相關知識,您就能在日後開發的應用程式中更得心應手地維持資料持續性。
SELECT
陳述式語法:
10. 瞭解詳情
雖然我們至今一直將重點放在 SQL 基礎知識和常見的 Android 開發用途,但 SQL 還有更多豐富功能等您發掘。請參閱下列資源,當做強化學習成果的額外參考資料,或者進一步瞭解各項主題。