Android數據庫(1)、SQLite數據庫介紹


一、關系性數據庫

 
關系型數據庫主要有以下三個特征,尤為明顯,如果沒有這個三個特征約束,當多個客戶端使用數據的時候就會出現各種各樣的錯誤,所以關系型數據庫定義這些約束,讓客戶端程序只要遵守這個規則便可以規避很多的錯誤。
 
這三個特征主要是:
  • 強類型
  • 引用完整性
  • 事物
 

1、強類型:

 
    大多數的數據庫都是強類型的,要求數據庫引擎嚴格執行,引擎通常定義一些原生的數據類型,它描述可以存進數據庫當中數據的具體類型,這些類型通常是各種長度的浮點數,字符串,整數等,特定的實現,這個類型不能讓用戶自己特定的去擴展,當引擎定義了關系,數據庫當中將無法企圖把錯誤的數據類型的數據存入列當中,
 
例如:嘗試插入一個包含一個字符串作為第三個特性的元組到一個指定第三特性應該是一個浮點數的關系當中,通常引擎會報錯,將插入失敗。
 

2、引用完整性

 
    在關系型數據庫當中,原生類型可以通過聲明一個關系中的列到另一個關系的一個引用進行擴展,也就是我們常說的主外建關系,數據庫架構師可以聲明一個關系中一些列的內容是其他關系中類似類型列的一個引用,下圖進行外建的演示:
上述就是最簡單的主外建關系,通過表A當中的外建和表B進行連接,從而如果可以在表A當中獲取到數據就可以在表B當中進行查詢,
 
想要實現上述的要求,必須遵循數據庫引擎提供的引用完整性,因為數據庫引擎將會強制的去執行引用完整性約束,該約束主要分為以下的兩種類型,
 
2.1 強制會去執行表B的主鍵約束,就如上述表B當中的主鍵。要保證在表B當中主鍵列當中主鍵的唯一性,也就是不可以重復,例如當表B當中的主鍵有A,B,C三個值,現在插入一行數據的主鍵列為A或B或C將會失敗,因為破壞了主鍵約束的唯一性。
 
2.2、強調表A的引用完整性,所謂什么是引用完整性呢,就是表A當中的外鍵要么是null,要么該指就一定在表B的主鍵當中存在,下面有兩種情況會破環引用的完整性,
  • 如果給表A當中插入一行數據,該數據的外鍵值在表B當中沒有,那么該操作則失敗。
  • 如果刪除表B當中的一條數據,並且該條數據被表A當中的外鍵所引用,那么該操作失敗
 
上述兩種情況也是我們開發當中常常會用到情況,所以我們的解決方式便是,
 
  • 先插入表B當中的數據,然后插入表A當中的數據
  • 先將表A當中引用表B的外鍵值置為null,然后刪除表B當中數據
 

3、數據庫事務

 
    事務:對數據的一組操作當作為一個單位去執行,要么全部失敗,要么全部成功。事物有以下幾個特點,常常被開發者侃侃而談,便是
  • 原子性:全部成功,全部失敗,操作被視為一個單位,不能將操作分離
  • 一致性:事務開啟之前,數據庫處於有效狀態,那么事務開啟之后,數據庫仍然處於有效狀態,不能因為事物導致數據約束被破壞
  • 隔離性:執行完事物以后,數據庫的狀態可以通過依次執行事物中的命令達到同樣的狀態
  • 持久性:事物一旦完畢,就不能丟失,比如電源故障等,物理環境都不能撼動。

 

二、SQL語言

 
什么是SQL語言呢,就是操作數據庫以及數據庫當中的數據的命令。稱之為SQL語言,SQL語言是由數據庫引擎解釋並且執行的,SQL語言大多數開發者都或多或少的已經掌握,所以該部分內容為回顧內容,筆者並不打算詳細講解。如有不同,可以自行Google,Baidu。
 
SQL語言主要分為六大類,分別是DDL(date definition Language)數據定義語言、DML(Data Manipulation Language)數據操作語言、DQL(Data Query Language)數據查詢語言,TPL事物處理語言,DCL數據控制語言、CCL指針控制語言。
 
其中最為常用的便是上述當中的前三類語言,而后面的三類用的不是很多,所以對前三類語言做詳細介紹,而后面的三類簡單了解就可以了。
 

1、DDL(數據定義語言)

 
主要描述的是數據庫當中所包含的數據結構,最常見的DDL語言就是用來定義一張表,包含列數、列類型、還有刪除一張表,下面為創建一個User表的SQL語言描述。
 
 
 1 #刪除表結構
 2 DROP TABLE users; 3 4 #創建一個新的表結構 5 CREATE TABLE users{ 6 _id INTEGER PRIMARY KEY AUTOINCREMENT, 7 name_raw_user_id INTEGER REFERENCES raw_users(_id), 8 photo_id INTEGER REFERENCES data(_id), 9 photo_file_id INTEGER REFERENCES photo_files(_id), 10 custom_ringtone TEXT, 11 send_to_voicemail INTEGER NOT NULL DEFAULT 0, 12 time_contacted INTEGER NOT NULL DEFAULT 0, 13 last_time_contacted INTEGER, 14 starred INTEGER NOT NULL DEFAULT 0, 15 has_phone_number INTEGER NOT NULL DEFAULT 0, 16 lookup TEXT, 17 status_update_id INTEGER REFERENCES data(_id) 18 19 };

 

這里需要注意的是:在定義一張表的時候,所有數據庫語法的關鍵字大寫,並且列名稱是唯一的,這里不允許列名稱之間相互重復。並且表的名稱在同一個數據庫當中也不能重復,通常使用的是一個復數名詞
 
除了創建表以外,SQL的DDL允許創建其他標准的RDBMS的數據結構,而RDBMS當中常見的標准的數據結構除了相應的表以外,還有相應的視圖(View)、觸發器(trigger)、索引(index)。這里把定義一個給定數據庫當中所有對象的DDL語句集合叫做這個數據庫的模式(schema),其他的數據結構在什么場景下會用到呢?它們分別扮演着什么樣子的角色呢,
 
  • 視圖(View):
  • 觸發器(trigger):
  • 索引(index):
 

 

2、DML(數據庫操作語言)

 
這類型語句主要操作的是數據庫當中數據的變化,有三個類型,分別是插入語句(insert),刪除語句(delete),修改語句(update)。下面我們對上述定義的表結構進行數據上面的操作
 
 1 #插入語句
 2 INSERT INTO users( 3  name_raw_contact_id,photo_id,photo_file_id, 4  last_time_contacted,status_update_id) 5 VALUES(null,null,null,18944658190,null); 6 7 #更新語句 8 UPDATE users SET starred=1, has_phone_number=1 WHERE _id=3; 9 10 #刪除語句 11 DELETE FROM users WHERE _id=2;
 
注意:插入數據的時候,一般要主要強類型的問題,而也要關注主鍵為NULL以及主鍵重復的問題(自增長可以解決),還有就是如果字段通過NOT NULL修飾的時候,一般會在定義表結構的時候會給出相應的默認值。
 

3、DQL(數據庫查詢語句)

 
查詢語句是最難的,也是使用最為平凡的語句,在關系術語當中,查詢創建一個新的關系(通過一個虛擬表),即就是一個或者多個表的叉積的投影約束,具體的查詢語句該怎么寫請各位看客自行Google,或者baidu,如下所示一個查詢示例,
 
1 #查詢語句
2 SELECT rc.display_name, u.starred 3 FROM users u INNER JOIN raw_users ru 4 ON u.name_raw_contact_id=rc._id 5 WHERE NOT ru.display_name IS NULL 6 ORDER BY ru.display_name ASC;
 
連接是兩個表的叉積的重要憑證。兩個表的一個完整叉積把1個表的每一行於第二個表中每一行進行合拼,在上述的語句當中,users表於raw_contacts表進行連接。叉積的結果將有C(users)*C(raw_contacts)行。
 
完整的叉積有時候不是非常有用,在查詢中 ON字句限制只有name_raw_contacts_id列的值和raw_contacts表中_id列的值相等的時候進行叉積,連接所產生的新的關系包含users表當中的行加上raw_contacts表當中的相應信息。這就非常有用了,下面有一個關於叉積理解的圖,相信大家會理解什么是叉積的概念
 
 
根據上述的這個小示例,我們很快就會明白多表查詢的原理是什么,主要就是通過條件去約束叉積,然后拿到我們想要的數據。到了這里,基本的SQL語言也就差不多了,剩下三種語言類型。
 

三、SQLite入門

 
Android端使用的數據庫就是開源數據庫SQLite,它是一個小型的無服務器的數據庫,由於這種數據庫的種種特性,讓它成為了在手機端極具吸引力的數據庫之一,大體說一下都有什么好處,讓該數據庫這么火熱,
 
該數據庫是2000年開發的,當初開發的初衷就是輕量級方式管理結構話數據,所以非常的輕便,具體的優點有以下幾點
 
  • 存儲在里面的數據持久化的跨進程和電源存在。
  • 跨越系統軟件升級和重新安裝
  • 處理多種異常情況,非常優雅,比如:低內存環境、磁盤錯誤、電池電量不足、都不足矣破壞已經持久化的數據
 
當然缺點同時也是存在,關系數據庫的幾個重要特征部分丟失,SQLite當中沒有強類型的支持,雖然有引用完整性的支持,但是默認是關閉的,唯獨事務默認情況是開啟的。
 
SQLite數據庫其實就是一個文件。在Android設備當中大多數應用程序把數據庫存儲在文件系統沙箱當中,位於名為datebase的子目錄當中,例如對於包是com.suansuan.application的應用程序,其數據庫極有可能位於目錄/data/data/com.suansuan.application/datebases中。
 
在以前人們想象能否將數據庫放入到SD卡下,讓其自己應用程序的數據庫被多個程序所共享,但是相比於這么做Android提供了更好的解決思路。
 

1、SQLite語法

 
命令行當中是用sqlite3的時候,首先要記住每條命令必須以分號結束,就和我們寫代碼的時候一樣。
 
當然還有一些命令,我們稱之為元命令,這些命令不屬於數據庫所有,而是SQLite3去解釋與執行,這些命令都是以,開頭的命令,其中最常用的就是.help和.exit .schema命令
 
其余的就是常用的SQL命令了,當然也可以叫做SQL語句
 

2、支持的數據類型

 
前文已經說過SQLite數據庫當中沒有強引用的支持,實際上列的類型僅僅只是注釋,列的類型僅僅只是提示而已,以幫助SQL引擎為此刻存儲在該列的數據選擇高效的表示。SQL引擎使用一些簡單的規則來調節“類型相似性”,從而決定內部存儲類型。這些規則幾乎不可見,我們開發能感覺到的是給定數據集占用的磁盤數量而已。
 
實際開發當中,Google開發人員限制自己只使用SQLite的4種基本類型,(integer、real、text、blob)並且顯式的使用文本來表示時間戳,使用integer來表示boolean值。
 
我們通過一個例子,來了解SQLite當中的“類型弱化”
 
1 sqlite> create table test(
2    ...> c1 biginteger, c2 smalldatetime, c3 float(9,3)); 3 sqlite> insert into test values("la","la","la"); 4 sqlite> select * from test; 5 la|la|la

3、關於約束

 
3.1、主鍵約束
我們可以將列的約束定義在表結構當中,比如我們常常用來修飾主鍵的 PRIMARY KEY的約束,唯一,表示這一行。同時SQLite支持非整數主鍵、支持多列復合主鍵,這里需要注意一下,關於非整數主鍵,除了“唯一”約束以外,應該還存在“非空”約束。但是由於SQLite早期版本的疏忽,SQLite允許Null作為非整數主鍵。所以會出現很多行主鍵為NULL的情況(此NULL非彼NULL),從而我們需要使用代碼給插入數據的主鍵做非空判斷,要不然我們數據庫當中就會出現無法通過其主鍵都無法區分的數據,筆者這里把這些數據叫做“臟數據”
 
通過上述的問題,很多開發人員都使用integer來做為主鍵列,同時將主鍵設置為自增長,PRIMARY KEY AUTOINCREMENT。並且SQL引擎為了解決上述問題,會默認的在表當中加入一個隱式的id, 
 
關於自增長,我們可以通過下面示例來了解
 
1 sqlite> create table test(
2    ...> key integer primary key, 3 ...> val text); 4 sqlite> insert into test(val) values("something"); 5 sqlite> insert into test(val) values("liusuansuan"); 6 sqlite> select * from test; 7 1|something 8 2|liusuansuan 9 sqlite>

 

 
自增長非常好用,可以唯一表示我們的數據,並且我們都不用去關心,但是正是因為我們的不關心將導致大量丑陋且愚蠢的代碼存在,比如我們添加一個新行,並且需要將新行的id存儲在SP當中,以便下次快速訪問,這種情況下,各位看客想想該如何實現呢。其實笨辦法確實有,並且筆者只想到了一種,如果看客有好的想法可以下方留言,那就是再查一遍,
 
3.2、外鍵約束
上文提到過外鍵約束,SQLite不強制執行外鍵約束,其實和SQLite列類型類似相似本質上都是注釋,但是可以通過修改相應的屬性來讓SQLite強制執行外鍵約束
  •  pragma FOREIGN_KEYS=true 
通過上述指令開啟外鍵的引用完整性。
 
下面我們來看一下關於外鍵注釋:
 
 1 sqlite> creaet table people(
 2    ...> name text, address integer references addresses(id)); 3 Error: near "creaet": syntax error 4 sqlite> create table people( 5 ...> name text, address integer references addresses(id)); 6 sqlite> create table addresses( 7 ...> id integer primary key, street text); 8 sqlite> insert into people values("liupengcheng",99); 9 sqlite> insert into addresses(street) values("shahe"); 10 sqlite> select * from people; 11 liupengcheng|99 12 sqlite> select * from addresses; 13 1|shahe 14 sqlite> select * from people, addresses where address=id; 15 sqlite>

 

上述語句,如果是在支持引用完整性的數據庫當中,第一個insert將失敗,並且第一個create table people也將失敗,因為找不到addresses這個外鍵表,很顯然上述我們的試驗都成功了,也就意味這SQLite數據庫不會去強制執行外鍵約束。
 
但是SQLite的優點就是靈活方便,如果我們需要設計一個復雜類型的、並且多個表通過外鍵所連接,像這種時候,易於修改和高效的數據存儲反而成為了SQLite的閃光點,Google官方文檔中描述,鼓勵開發人員使用標准范式,然后讓開發這自己編寫代碼的時候自己規划引用完整約束,而不是依賴數據庫去實現。
 
下面我們來模擬一個簡單的連接
 
 
 1 sqlite> create table people(
 2    ...> name text, address integer references addresses(id)); 3 sqlite> create table addresses(id integer primary key, street text); 4 sqlite> insert into addresses(street) values("北京"); 5 sqlite> insert into addresses(street) values("上海"); 6 sqlite> insert into people values("張飛“,1); 7 sqlite> insert into people values("張飛",1); 8 sqlite> insert into people values("關羽",2); 9 sqlite> insert into people values("劉備",2); 10 sqlite> select name,street from people,addresses where address=id; 11 張飛|北京 12 關羽|上海 13 劉備|上海
 
SQLite中還支持一些其他的約束,比如:
 
unique:該約束表示SQLite將拒絕任何試圖在表中添加將導致該列出現重復值的一行。
not null:該約束表述SQLite將拒絕任何試圖將表中數據置為NULL的所有操縱。
check(expression):當次約束修飾列時,每當一個新行添加到表中時,或當修改現有的行時,都會對表達式求值,如果求值的結果轉化整數為0時,該操作將失敗,操作將取消。如果表達式的計算結果為NULL或者任何其他非零值,則操作成功
 
下面展示一個列約束:
 
1 sqlite> create table test(
2    ...> c1 text unique, c2 text not null, c3 text check(c3 in ("OK","Dandy"))); 3 sqlite> insert into test values("dandy","dandy","dandy"); 4 Error: CHECK constraint failed: test 5 sqlite> insert into test values("dandy","dandy","Dandy"); 6 sqlite> insert into test values("dandy","dandy","Dandy"); 7 Error: UNIQUE constraint failed: test.c1 8 sqlite> insert into test values("dandy",null,"Dandy"); 9 Error: NOT NULL constraint failed: test.c2

4、SQLite數據庫示例

 
在開始我們的程序示例之前,我們需要對我們數據庫當中的數據進行一些格式化,好方便我們之后的查看數據。使用SQLite的元命令來進行數據庫數據展示的格式化。
 
1 sqlite>.header on
2 sqlite>.mode column
3 sqlite>.timer on
具體詳細內容,可以參考SQLite元命令 http://www.runoob.com/sqlite/sqlite-commands.html
 
我們來嘗試去創建一個完整的User的數據庫。
 
1、創建User表
1 sqlite> create table users(
2    ...> _id integer primary key autoincrement, 3 ...> name text not null); 4 sqlite> 

 

我們需要一個字段是用來記錄這條記錄是什么時候被修改的,好方便我們以后對數據的修改
1 sqlite> alter table users add last_modified_time text;
2 Run Time: real 0.003 user 0.000000 sys 0.000000
 
2、創建觸發器
 
我們讓剛剛我們定義完的字段,可以根據我們插入數據,或者是添加數據跟隨我們插入數據和更新數據取當前最新的時間。這里定義兩個觸發器,一個是跟隨insert所進行觸發,一個根據update進行觸發。
 
 1 sqlite> create trigger t_users_audit_i
 2    ...> after insert
 3    ...> on users 4 ...> begin 5 ...> update users set last_modified_time=datetime('now','utc') 6 ...> where rowid=new.rowid; end; 7 Run Time: real 0.003 user 0.000000 sys 0.000000 8 9 sqlite> create trigger t_users_audit_u 10 ...> after update 11 ...> on users 12 ...> begin 13 ...> update users set last_modified=datetime('now','utc') 14 ...> where rowid=new.rowid; 15 ...> end; 16 17 Run Time: real 0.003 user 0.000000 sys 0.000000

 

上述觸發器創建的時候用到了datetime數據庫內置和日期相關的函數。具體內容可以查看http://www.runoob.com/sqlite/sqlite-date-time.html

 
現在我們插入一條數據來驗證我們剛剛創建的結構是否正確。
 
 1 sqlite> insert into users(name) values("liupengcheng");
 2  
 3 Run Time: real 0.003 user 0.000000 sys 0.004000
 4  
 5 sqlite> select * from users; 6 7 _id name last_modified_time 8 ---------- ------------ ------------------- 9 2 liupengcheng 2018-03-09 09:50:40 10 11 Run Time: real 0.000 user 0.000000 sys 0.000000

 

我們的User可能需要地址,比如客戶的收貨地址,所以我們創建一個地址表,

1 sqlite> create table addresses(
2    ...> _id integer primary key autoincrement, 3 ...> number integer not null, 4 ...> unit text, 5 ...> street text not null, 6 ...> city integer references cities); 7 8 Run Time: real 0.004 user 0.000000 sys 0.000000
 
如果我們引用完整性的支持已經開啟的話,這個表的定義肯定會導致一個錯誤,因為這里cities這張表是不存在的。不過在SQLite數據庫的默認配置當中,是沒有問題的,我們可以稍后去定義cities這張表。
 
現在我們的用戶存在了,地址表也存在了,這里分析得到,一個用戶可以有多個收貨地址,一個收貨地址可以被多個人多共有,所以這里的關系是多對多,一般的主外鍵支持一對多,如果是多對多的關系的話,我們就需要建立第三張表專門的讓上述兩張表進行對應了。
 
1 sqlite> create table users_addresses(
2    ...> users integer references users, 3 ...> address integer references addresses); 4 5 Run Time: real 0.004 user 0.000000 sys 0.004000
 
現在我們來我們創建的數據庫添加數據
 1 insert into users(name) values("wangyiqi"); 2 insert into users(name) values("wwangpeng"); 3 insert into users(name) values("liuna"); 4 insert into users(name) values("yangqiqi"); 5 sqlite> select * from users; 6 7 _id name last_modified_time 8 ---------- ------------ ------------------- 9 2 liupengcheng 2018-03-09 09:50:40 10 3 wangyiqi 2018-03-09 10:18:42 11 4 wwangpeng 2018-03-09 10:18:42 12 5 liuna 2018-03-09 10:18:42 13 6 yangqiqi 2018-03-09 10:19:01 14 Run Time: real 0.003 user 0.000000 sys 0.000000 15 16 17 insert into addresses(number,street) values(651, "北京昌平"); 18 insert into addresses(number,street) values(1600, "上海浦東"); 19 insert into addresses(number,street) values(259, "哈爾濱江北"); 20 insert into addresses(number,street) values(693, "甘肅天水"); 21 insert into addresses(number,street) values(1658, "北京朝陽"); 22 sqlite> select * from addresses; 23 24 _id number unit street city 25 ---------- ---------- ---------- ------------ ---------- 26 1 651 北京昌平 27 2 1600 上海浦東 28 3 259 哈爾濱江 29 4 693 甘肅天水 30 5 1658 北京朝陽 31 Run Time: real 0.000 user 0.000000 sys 0.000000 32 33 34 insert into users_addresses(users,address) values(2,1); 35 insert into users_addresses(users,address) values(3,2); 36 insert into users_addresses(users,address) values(4,3); 37 insert into users_addresses(users,address) values(5,4); 38 insert into users_addresses(users,address) values(6,5); 39 sqlite> select * from users_addresses; 40 41 users address 42 ---------- ---------- 43 2 1 44 3 2 45 4 3 46 5 4 47 6 5 48 Run Time: real 0.000 user 0.000000 sys 0.000000

現在數據也有了,關系也鍵全了,現在可以使用關聯關系進行查看我們之前所編輯的數據實體了。

 
 1 sqlite> select name,street,number from users,addresses,users_addresses where users._id = users_addresses.users and addresses._id = users_addresses.address;
 2  
 3 name          street        number    
 4 ------------  ------------  ----------
 5 liupengcheng  北京昌平  651       
 6 wangyiqi      上海浦東  1600      
 7 wwangpeng     哈爾濱江  259       
 8 liuna         甘肅天水  693       
 9 yangqiqi      北京朝陽  1658      
10  
11 Run Time: real 0.000 user 0.000000 sys 0.000000

如果我們想確定一個地址下面有多少用戶,我們可以使用count函數和group by字句來實現。

 
 1 select count(name),number,street
 2 from users,addresses,users_addresses 3 where users._id=users_addresses.users 4 and addresses._id=users_addresses.address 5 group by number,street; 6 7 count(name) number street 8 ----------- ---------- --------------- 9 1 259 哈爾濱江北 10 1 651 北京昌平 11 1 693 甘肅天水 12 1 1600 上海浦東 13 1 1658 北京朝陽 14 15 Run Time: real 0.000 user 0.000000 sys 0.000000
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM