# https://www.sqlite.org/index.html
與一般的資料庫如 MySQL 等不同的是, SQLite 並非 Client-Server 架構, 因此沒有獨立之伺服器程序, 而是嵌入整合在用戶程式中. SQLite 的資料庫與微軟 ACCESS 一樣是單一檔案型態 (即使用單一文件儲存整個資料庫, 副檔名為 .sqlite 或 .db), 不需要進行任何組態設定, 只要連接資料庫檔案就可以直接使用. SQLite 的特性整理如下 :
- SQLite 屬於羽量級基於磁碟之資料庫管理系統
- 不需要安裝與設定伺服器
- 支援大部分 SQL91 標準, 但不支援外鍵限制
- 最大支援 140TB 單一資料庫檔案, 備份方便簡易
- 資料以 B+ 樹狀結構儲存
# 12.6. sqlite3 — DB-API 2.0 interface for SQLite databases
# https://www.tutorialspoint.com/sqlite/index.htm
# https://www.quackit.com/sqlite/tutorial/about_sqlite.cfm
# How to Create, Open, Backup Database in SQLite
可以從 SQLite 官網下載 "Precompiled Binaries for Windows" 安裝檔, 安裝後可由命令列輸入 sqlite3 進入 SQLite3 介面進行資料庫操作 :
# https://www.sqlite.org/download.html
sqlite3 模組定義了 sqlite3.Connection 與 sqlite3.Cursor 這兩個類別來連線與操作資料庫. SQLite 資料庫的 CRUD 操作主要是利用 Cursor 物件之 execute() 方法來執行 SQL 指令. 一般資料庫操作之程序如下 :
- 呼叫 sqlite3.connect() 連接資料庫
- 呼叫 conn.cursor() 建立 Cursor 物件
- 呼叫 cursor.execute() 執行 CRUD 操作
- 呼叫 conn.close() 關閉資料庫
- 呼叫 sqlite3.connect() 連接資料庫
- 呼叫 conn..execute() 執行 CRUD 操作
- 呼叫 conn.close() 關閉資料庫
sqlite3 方法 | 說明 |
conn=sqlite3.connect("db.sqlite") | 連接資料庫檔案 db.sqlite |
conn.commit() | 將之前的操作變更至資料庫中 |
conn.rollback() | 取消最近之 commit() 變更, 回復至之前狀態 |
conn.close() | 關閉資料庫連線 |
cursor=conn.execute(SQL) | 執行 SQL 指令 (字串), 傳回 Cursor 物件 |
cursor=conn.cursor() | 傳回 Cursor 物件 |
cursor.execute(SQL) | 執行 SQL 指令 (字串) |
cursor.fetchall() | 讀取全部剩餘之紀錄以串列傳回, 若無紀錄傳回空串列 |
cursor.fetchone() | 讀取目前 Cursor 物件所指之下一筆紀錄, 若無傳回 None |
SQLite 資料庫操作的 SQL 語法參考官網或 TutorialsPoint :
# SQL As Understood By SQLite
# TutorialsPoint : SQLite Tutorial
# Python Library Reference
SQLite 的 Schema (表格結構) 相當簡潔, 其資料欄位只有如下 5 種資料型態 :
sqlite3 資料類型 | 說明 |
NULL | 無值或值為 NULL |
INTEGER | 有號整數 |
REAL | 8 Bytes 的 IEEE 浮點數 |
TEXT | UTF-8, UTF-16BE 或 UTF-16LE 編碼之字串 |
BLOB | 原始輸入類型 |
SQLite 並無布林型態欄位, 但可用 INTEGER 的 0 (False) 與 1 (True) 代用. 另外 SQLite 也沒有 DATE/TIME 型態欄位, 但可以透過 Date And Time Functions 日期時間函數的協助利用 REAL, INTEGER, 或 TEXT 型態來儲存日期時間資料. 參考 :
# Datatypes In SQLite Version 3
除了資料型態外, 欄位還可以加上一些修飾詞, 例如欄位必須有值要用 NOT NULL; 值不可重複用 UNIQUE; 主鍵欄位使用 PRIMARY KEY, 整數自動增量 AUTOINCREMENT, 此常用來作為當作紀錄的索引. 可自動增量的 id 欄位, 其欄位型態可定義為 :
INTEGER PRIMARY KEY AUTOINCREMENT
注意, AUTOINCREMENT 只能用在 INTEGER, 而且若與 PRIMARY KEY 同時存在時必須放在 PRIMARY KEY 後面.
在測試之前還要安裝一個 FireFox 瀏覽器上好用的 SQLite 圖形化資料庫管理工具, 它是 FireFox 的一個附加元件, 必須經過安裝才能使用. 首先開啟 FireFox, 搜尋 "SQLite Manager", 點擊超連結 :
# https://addons.mozilla.org/zh-TW/firefox/addon/sqlite-manager-webext/?src=search
按 "+ 新增至 FireFox" 鈕再按彈出視窗之 "安裝" 鈕, 完成後點 FireFox 右上角之設定鈕, 在彈出視窗中按 "+自訂" 鈕開啟 "其他工具與功能" 頁面 :
將其中的 "SQLite Manager" 拖曳到設定視窗中, 按 "結束自訂模式" 即完成設定 :
再次按設定頁籤開啟 SQLite Manager 頁面, 介面如下 :
以下是 sqlite3 模組之 CRUD 操作測試紀錄, 本測試參考了下列書籍 :
- Python 程式設計實務 (博碩, 何敏煌)
- Python 初學特訓班 (碁峰, 文淵閣工作室)
- Python 入門邁向高手之路-王者歸來 (深石, 洪錦魁)
- Python Pocket Reference (Oreilly, Mark Lutz)
- Python Cookbook(Oreilly, David Beazly)
- Learning Python (Oreilly, Mark Lutz)
# Python 學習筆記 : 安裝執行環境與 IDLE 基本操作
# Python 學習筆記 : 檔案處理
# Python 學習筆記 : 日誌 (logging) 模組測試
1. 連接資料庫 :
使用 import sqlite3 匯入模組後即可呼叫 sqlite3.connect() 方法連接資料庫檔案, 它會傳回一個 sqlite3.Connection 物件, 呼叫 Connection 物件之 cursor() 則會傳回一個 sqlite3.Cursor 物件, 這個 Cursor 物件便是操作資料庫的主要工具 :
D:\Python\test>python
Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 18:41:36) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sqlite3 #匯入 sqlite3 模組
>>> conn=sqlite3.connect("db.sqlite") #連接資料庫檔案
>>> type(conn) #傳回 Connection 物件
<class 'sqlite3.Connection'>
>>> cursor=conn.cursor() #建立 Cursor 物件
>>> type(cursor)
<class 'sqlite3.Cursor'>
注意, 若檔案不存在, SQLite 會自動建立空白的資料庫檔案. 關於 Cursor 物件, 參考 :
# https://docs.python.org/2.5/lib/sqlite3-Cursor-Objects.html
2. 新增資料表 :
新增資料表使用 CREATE TABLE 指令, 若要避免重複新增相同名稱資料表造成錯誤, 可用 CREATE TABLE IF NOT EXISTS, 這樣當同名資料表已經存在時就不會執行此 CREATE TABLE 指令了. SQLite 說明文件參考 :
# https://www.sqlite.org/lang_createtable.html
在下面的測試中, 我參考了之前測試 Java 連接 ACCESS 資料庫的範例 :
# Java 資料庫存取 : 使用 ACCESS
改寫如下 :
>>> import sqlite3
>>> conn=sqlite3.connect("db.sqlite")
>>> SQL='CREATE TABLE IF NOT EXISTS users(id INTEGER \
... PRIMARY KEY AUTOINCREMENT NOT NULL,user_name TEXT, \
... age NUMBER, gender TEXT,email TEXT, password TEXT)'
>>> cursor=conn.execute(SQL) //傳回 Cursor 物件
>>> type(cursor)
<class 'sqlite3.Cursor'>
這樣便在資料庫 db.sqlite 中建立了一個 users 資料表, 裡面含有 id, user_name, age, gender, email, password 六個欄位. 注意, 有加上 NOT NULL 限制的欄位在新增紀錄時必須要給值, 否則會出現 "NOT NULL constraint failed" 錯誤訊息. 欄位 id 因為有 AUTOINCREMENT 屬性會自動給值, 因此加 NOT NULL 是多此一舉,
在 FireFox 的 SQLite manager 中執行 Database/Connect Database, 點選目前目錄下的 db.sqlite 檔案開啟資料庫, 再打開 users 資料表即可看到其欄位結構 :
>>> SQL='CREATE TABLE users(id INTEGER \
... PRIMARY KEY AUTOINCREMENT NOT NULL,user_name TEXT, \
... age NUMBER, gender TEXT,email TEXT,password TEXT)'
>>> cursor=conn.execute(SQL)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
sqlite3.OperationalError: table users already exists #資料表已存在
所以在使用 CREATE TABLE 時最好伴隨 IF NOT EXISTS.
另外, SQL 指令中的自訂名稱, 例如資料表名稱與欄位名稱亦可用引號括起來, 但要注意雙引號與單引號交錯出現原則, 即若名稱用雙引號, 則 SQL 語句就用單引號, 例如 :
SQL='CREATE TABLE IF NOT EXISTS "users" ("id" INTEGER \
PRIMARY KEY AUTOINCREMENT NOT NULL, "user_name" TEXT, "age" NUMBER, \
"gender" TEXT, "email" TEXT, "password" TEXT)'
若名稱用單引號, 則 SQL 語句就用雙引號, 例如 :
SQL="CREATE TABLE IF NOT EXISTS 'users' ('id' INTEGER \
PRIMARY KEY AUTOINCREMENT NOT NULL, 'user_name' TEXT, 'age' NUMBER, \
'gender' TEXT, 'email' TEXT, 'password' TEXT)"
3. 新增與查詢紀錄 :
新增紀錄之 SQL 指令格式 :
INSERT INTO table_name(field1,field2,...,fieldn) VALUES(val1,val2,...,valn)
注意, 欄位名稱必須與 VALUES 中列舉之值一一對應, 數目若不一致將導致執行錯誤. 這裡的欄位名稱不需列舉該資料表之全部欄位, 缺漏的欄位將被填入 NULL 值, 但若缺漏的欄位型態為 NOT NULL 將產生執行錯誤. 說明文件參考 :
# https://www.tutorialspoint.com/sqlite/sqlite_insert_query.htm
查詢紀錄之 SQL 指令格式 :
SELECT * FROM table [WHERE field='value' [AND field='value']]
利用 cursor.execute() 或 conn.execute() 執行 SELECT 語句後會傳回 Cursor 物件, 此物件有兩個擷取符合查詢條件之紀錄的方法 :
Cursor 物件方法 | 說明 |
fetchone() | 以 tuple 傳回符合查詢條件之下一筆紀錄, 若無傳回 None |
fetchall() | 以 list 傳回符合查詢條件之全部紀錄, 若無傳回 None |
其中 fetchone() 傳回的是表示紀錄的 tuple; 而 fetchall() 傳回的是 tuple 組成之 list (表示多筆紀錄), fetchall() 即使只查詢到一筆紀錄也是傳回 list.
說明文件參考 :
# https://www.tutorialspoint.com/sqlite/sqlite_select_query.htm
首先新增第一筆紀錄到上面建立的 users 資料表內, 然後用 SELECT 指令查詢單筆紀錄 :
>>> SQL="INSERT INTO users(user_name,age,gender,email,password) \
... VALUES('愛咪','12','女','amy@gmail.com','123')"
>>> conn.execute(SQL) #新增第一筆紀錄
>>> SQL="SELECT * FROM users" #查詢所有紀錄
>>> cursor=conn.execute(SQL)
>>> print(cursor.fetchone()) #fetchone() 傳回 tuple
(1, '愛咪', 12, '女', 'amy@gmail.com', '123')
>>> SQL="SELECT * FROM users" #查詢所有紀錄
>>> cursor=conn.execute(SQL)
>>> print(cursor.fetchall()) #fetchall() 傳回 list
[(1, '愛咪', 12, '女', 'amy@gmail.com', '123')]
可見 fetchone() 傳回的是一個表示紀錄的 tuple; 而 fetchall() 傳回的則是可表示多筆紀錄的 list (事實上是 tuple's list), 即使只有一筆紀錄也是傳回 list. 注意, 雖然可查詢到這筆紀錄, 但事實上它還放在記憶體中並未寫回資料庫裡, 因為還沒有呼叫 conn.commit(). 這時到 FireFox 的 SQLite Manager 裡面是看不到這筆紀錄的.
接著寫入第二筆與第三筆紀錄 :
>>> SQL="INSERT INTO users(user_name,age,gender,email,password) \
... VALUES('彼得',14,'男','peter@gmail.com','456')"
>>> conn.execute(SQL) #新增第二筆紀錄
>>> SQL="INSERT INTO users(user_name,age,gender,email,password) \
... VALUES('凱莉',16,'女','kelly@gmail.com','789')"
>>> conn.execute(SQL) #新增第三筆紀錄
>>> SQL="SELECT * FROM users" #查詢所有紀錄
>>> cursor=conn.execute(SQL)
>>> print(cursor.fetchone()) #擷取下一筆
(1, '愛咪', 12, '女', 'amy@gmail.com', '123')
>>> print(cursor.fetchone()) #擷取下一筆
(2, '彼得', 14, '男', 'peter@gmail.com', '456')
>>> print(cursor.fetchone())
(3, '凱莉', 16, '女', 'kelly@gmail.com', '789') #擷取下一筆
>>> print(cursor.fetchone())
None
>>> print(cursor.fetchall()) #擷取全部
[]
可見 Cursor 物件的作用如同指向查詢所得紀錄集的指標, 呼叫 fetchone() 就由頭指向下一筆紀錄, 到尾時就傳回 None, 此時呼叫 fetchall() 就傳回空串列了.
以上新增的紀錄每一筆都有完整的欄位資料, 事實上新增時可以只填入部分欄位資料 (但欄位定義中有 NOT NULL 者必須填入), 未填欄位會被填入 None, 例如下面填入第四筆紀錄時只填入 user_name 欄位, 其餘欄位未填, 最後並呼叫 conn.commit() 將目前已寫入記憶體中的紀錄寫回資料庫檔案中 :
>>> SQL="INSERT INTO users(user_name) VALUES('東尼')"
>>> cursor=conn.execute(SQL) #新增第四筆紀錄
>>> SQL="SELECT * FROM users"
>>> cursor=conn.execute(SQL)
>>> print(cursor.fetchall()) #擷取全部
[(1, '愛咪', 12, '女', 'amy@gmail.com', '123'), (2, '彼得', 14, '男', 'peter@gmail.com', '456'), (3, '凱莉', 16, '女', 'kelly@gmail.com', '789'), (4, '東尼', None, None, None, None)]
>>> conn.commit() #寫回資料庫
可見 fetchall() 傳回含有四個 tuple 的串列. 呼叫 commit() 之後再去 SQLite Manager 按 Refresh 鈕即可看到這四筆紀錄 :
可見值為 None 的欄位在 SQLite Manager 中顯示為空白.
4. 更新與刪除紀錄 :
更新紀錄的 SQL 指令格式如下 :
UPDATE table SET field1='value1' [, field2='value2', ... fieldn='valuen'] [WHERE field=value]
以上面新增的第四筆紀錄為例, 由於只填入了 user_name 欄位, 此處可用 UPDATE 指令來補足其餘欄位資料, 例如 :
>>> SQL="UPDATE users SET age='48',gender='男',email='tony@gmail.co', \
... password='abc' WHERE user_name='東尼'"
>>> cursor=conn.execute(SQL) #更新紀錄
>>> SQL="SELECT * FROM users" #查詢全部紀錄
>>> cursor=conn.execute(SQL)
>>> print(cursor.fetchall()) #擷取全部紀錄集
[(1, '愛咪', 12, '女', 'amy@gmail.com', '123'), (2, '彼得', 14, '男', 'peter@gmail.com', '456'), (3, '凱莉', 16, '女', 'kelly@gmail.com', '789'), (4, '東尼', 48, '男', 'tony@gmail.co', 'abc')]
>>> conn.commit() #寫回資料庫
可見執行 UPDATE 指令後, 第四筆紀錄缺漏的欄位資料都補足了. 寫回資料庫後將 SQLite Manager 按 Refresh 鈕更新即可看到更新的結果. 注意, 如果沒有設定 WHERE 限制條件, 則每一筆紀錄都會被 UPDATE 操作, 使得被更新的欄位值都變成相同.
SQL 指令也可以用格式化指令 format() 搭配 {} 運算子來對應填值, 上面的 UPDATE 指令可用下列指令取代 :
SQL="UPDATE users SET age='{}',gender='{}',email='{}', \
password='{}' WHERE user_name='東尼'".format('48',\
'男','tony@gmail.co','abc')
刪除紀錄的 SQL 指令格式如下 :
DELETE FROM table [WHERE field='value']
注意, 刪除紀錄若沒有加上 WHERE 條件的話會刪除整個資料表內的全部記錄.
以刪除上面第四筆紀錄為例 :
>>> SQL="DELETE FROM users WHERE id='4'" #刪除第四筆資料
>>> cursor=conn.execute(SQL)
>>> SQL="SELECT * FROM users" #查詢全部紀錄
>>> cursor=conn.execute(SQL)
>>> print(cursor.fetchall()) #擷取全部紀錄集
[(1, '愛咪', 12, '女', 'amy@gmail.com', '123'), (2, '彼得', 14, '男', 'peter@gmail.com', '456'), (3, '凱莉', 16, '女', 'kelly@gmail.com', '789')]
>>> conn.commit() #寫回資料庫
可見只剩下三筆資料, 第四筆已經被刪除. 注意, 此處 id 用 4 或 '4' 均可, SQLite 會自動轉態. WHERE 限制條件可以使用 AND 或 OR 來組合多重條件, 例如若要刪除女性紀錄, 但年紀小於 15 歲者, 其 SQL 指令如下 :
>>> SQL="DELETE FROM users WHERE gender='女' AND age < 15"
>>> cursor=conn.execute(SQL) #刪除第一筆紀錄
>>> SQL="SELECT * FROM users"
>>> cursor=conn.execute(SQL)
>>> print(cursor.fetchall())
[(2, '彼得', 14, '男', 'peter@gmail.com', '456'), (3, '凱莉', 16, '女', 'kelly@gmail.com', '789')]
可見符合女性且年齡小於 15 者只有第一筆紀錄被刪除. 下面測試
5. 更改資料欄位 :
更改資料欄位的 SQL 指令為 ALTER TABLE, 能用的只有新增欄位以及變更資料表名稱兩個功能, 新增欄位指令格式如下 :
ALTER TABLE table ADD field type
SQLite 一次只能新增一個欄位, 且資料格式不能用上面的簡約格式如 TEXT, INTEGER 等, 而是要用如 CHAR(), VARCHAR(), INT(20) 等詳盡格式, 例如 :
>>> SQL="ALTER TABLE users ADD telephone CHAR(20)" #新增 telephone 欄位
>>> cursor=conn.execute(SQL)
>>> SQL="ALTER TABLE users ADD city CHAR(20)" #新增 city 欄位
>>> cursor=conn.execute(SQL)
>>> conn.commit()
>>> SQL="INSERT INTO users(user_name) VALUES('潔西卡')"
>>> cursor=conn.execute(SQL)
>>> SQL="SELECT * FROM users"
>>> cursor=conn.execute(SQL)
>>> print(cursor.fetchall())
[(2, '彼得', 14, '男', 'peter@gmail.com', '456', None, None), (3, '凱莉', 16, ' 女', 'kelly@gmail.com', '789', None, None), (5, '潔西卡', None, None, None, None, None, None)]
>>> conn.commit()
可見每一筆紀錄後面都新增了兩個欄位, 更新 SQLite Manager 後檢視資料表結構可知已加入 telephone 與 city 兩欄位 :
變更資料表名稱指令格式如下 :
ALTER TABLE table RENAME TO new_table
其中 new_table 為資料表的新名稱 :
>>> SQL="ALTER TABLE users RENAME TO users_new"
>>> cursor=conn.execute(SQL)
>>> SQL="SELECT * FROM users"
>>> cursor=conn.execute(SQL)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
sqlite3.OperationalError: no such table: users
可見更名為 users_new 之後再去查詢 users 就會出現 "no such table" 的錯誤, 應該改為查詢 users_new 資料表才對 :
>>> SQL="SELECT * FROM users_new"
>>> cursor=conn.execute(SQL)
>>> print(cursor.fetchall())
[(2, '彼得', 14, '男', 'peter@gmail.com', '456', None, None), (3, '凱莉', 16, ' 女', 'kelly@gmail.com', '789', None, None), (5, '潔西卡', None, None, None, None, None, None)]
6. 刪除資料表 :
刪除資料表指令格式 :
DROP TABLE IF EXISTS table
要將上面建立的 users 資料表刪除之指令為 DROP TABLE users, 但在刪除之前可先用 SQLite Manager 將資料表匯出儲存, 以便刪除後若後悔還可從匯出檔再匯入 :
點選左邊欄位之資料表 users, 按右欄中的 "EXPORT" 鈕 :
勾選 "Include CREATE TABLE statement" :
按底下的 "OK" 鈕即可將資料表 users 存為 users.sql 檔, 內容如下 :
DROP TABLE IF EXISTS "users";
CREATE TABLE users(id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,user_name TEXT,age NUMBER, gender TEXT,email TEXT NOT NULL,password TEXT);
INSERT INTO "users" VALUES(1,'愛咪','12','女','amy@gmail.com','123');
INSERT INTO "users" VALUES(2,'彼得',14,'男','peter@gmail.com','456');
INSERT INTO "users" VALUES(3,'凱莉',16,'女','kelly@gmail.com','789');
INSERT INTO "users" VALUES(4,'東尼',48,'男','tony@gmail.co','789');
這樣就可以放心刪除資料表 users 了 :
>>> SQL="DROP TABLE IF EXISTS users"
>>> cursor=conn.execute(SQL)
>>> SQL="SELECT * FROM users"
>>> cursor=conn.execute(SQL)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
sqlite3.OperationalError: no such table: users
可見 users 資料表已被刪除, 查詢紀錄顯示 "no such table" 錯誤.
沒有留言 :
張貼留言