關系型數據庫的一些功能:
- 多用戶同時訪問數據;
- 用戶使用數據的保護;
- 高效地存儲和檢索數據;
- 數據被模式定義以及被約束限制;
- Joins 通過連接發現不同數據之間的關系;
- 聲明式(非命令式)查詢語言,SQL(Structured Query Language)
之所以被稱為關系型(relational)是因為數據庫展現了表單(table)形式的不同類型數據 之間的關系。
表單是一個具有行和列的二元組,和電子數據表類似。要創建一個表單,需要給它命名, 明確次序、每一項的名稱以及每一列的類型。每一行都會存在相同的列,即使允許缺失項 (也稱為 null)。
某一行或者某幾行通常作為表單的主鍵,在表單中主鍵的值是獨一無二的,防止重復增添 數據項。這些鍵在查詢時被快速索引,類似于圖書的索引,方便快速地找到指定行。
每一個表單都附屬于某數據庫,類似于一個文件都存在于某目錄下。兩層的層次結構便于 更好地組織和管理。
數據庫一詞有多種用法 ,用于指代服務器、表單容器以及存儲的數據。如 果你同時指代它們,可以稱其為數據庫服務器(database server)、數據庫 (database)和數據(data)。
如果我們想要通過非主鍵的列的值查找數據,可以定義一個二級索引,否則數據庫服務器需要掃描整個表單,暴力搜索每一行找到匹配列的值。
表單之間可以通過外鍵建立關系,列的值受這些鍵的約束。
SQL
SQL 既不是一個 API 也不是一種協議,而是一種聲明式語言,只需要告訴它做什么即可。 它是關系型數據庫的通用語言。SQL查詢是客戶端發送給數據庫服務器的文本字符串,指明需要操作的具體操作。
SQL 語言存在很多標準定義格式,但是所有的數據庫制造商都會增加它們自己的擴展,導 致產生許多 SQL 方言。如果你把數據存儲在關系型數據庫中,SQL 會帶來一定的可移植 性,但是方言和操作差異仍然會導致難以將數據移植到另一種類型的數據庫中。
SQL語句有兩種主要類型:
- DDL(數據定義語言)
處理用戶,數據庫以及表單的創建,刪除,約束和權限等。 - DML(數據操作語言)
處理數據插入,選擇,更新
基本的SQL DDL命令
操作 SQL模式 SQL示例
創建數據庫 CREATE DATABASE dbname CREATE DATABASE d
選擇當前數據庫 USE dbname USE d
刪除數據庫以及表單 DROP DATABASE dbname DROP DATABASE d
創建表單 CREATE TABLE tbname (coldefs) CREATE TABLE t(id INT, count INT)
刪除表單 DROP TABLE tbname DROP TABLE t
刪除表單中所有的行 TRUNCATE TABLE tbname TRUNCATE TABLE t
SQL是不區分大小寫的。
SQL關系型數據庫的主要DML操作可以縮略為CRUD。
- Create:使用 INSERT 語句創建
- Read:使用 SELECT 語句選擇
- Update:使用 UPDATE 語句更新
- Delete:使用 DELETE 語句刪除
基本的SQL DML命令
操作 SQL模式 SQL示例
增加行 INSERT INTO tbname VALUES(...) INSERT INTO t VALUES(7,40)
選擇全部行和全部列 SELECT * FROM tbname SELECT * FROM t
選擇全部行和部分列 SELECT cols FROM tbname SELECT id,count from t
選擇部分行部分列 SELECT cols FROM tbname WHERE condition SELECT id,count from t WHERE count > 5 AND id = 9
修改一列的部分行 UPDATE tbname SET col = value WHERE condition UPDATE t SET count=3 WHERE id=5
刪除部分行 DELETE FROM tbname WHERE condition DELETE FROM t WHERE count <= 10 OR id = 16
DB-API
應用程序編程接口(API)是訪問某些服務的函數集合。DB-API是Python中訪問關系數據庫的標準API。使用它可以編寫簡單的程 序來處理多種類型的關系型數據庫,不需要為每種數據庫編寫獨立的程序。
它的主要函數如下所示:
- connect()
連接數據庫,包含參數用戶名,密碼,服務器地址,等等 - cursor()
創建一個cursor()對象來慣例查詢 - execute()和executemany()
對數據庫執行一個或多個SQL命令 - fetchone(),fetchmany()和fetchall()
得到execute之后的結果
SQLite
SQLite(http://www.sqlite.org)是一種輕量級的、優秀的開源關系型數據庫。它是用 Python 的標準庫實現,并且存儲數據庫在普通文件中。這些文件在不同機器和操作系統之 間是可移植的,使得 SQLite 成為簡易關系型數據庫應用的可移植的解決方案。它不像功能 全面的 MySQL 或者 PostgreSQL,SQLite 僅僅支持原生 SQL 以及多用戶并發操作。瀏覽 器、智能手機和其他應用會把 SQLite 作為嵌入數據庫。
首先使用 connect() 函數連接本地的 SQLite 數據庫文件,這個文件和目錄型數據庫(管理 其他的表單)是等價的。字符串 ':memory:' 僅用于在內存中創建數據庫,有助于方便快速 地測試,但是程序結束或者計算機關閉時所有數據都會丟失。
下面的栗子會創建一個數據庫enterprise.db(自己先創建一個文件) 和表單 zoo 用以管理路邊繁華的寵物動物園 業務。表單的列如下所示。
? critter 可變長度的字符串,作為主鍵。
? count 某動物的總數的整數值。
? damages 人和動物的互動中損失的美元數目。
In [1]: import sqlite3
In [2]: conn = sqlite3.connect('enterprise.db')
In [3]: curs = conn.cursor()
In [4]: curs.execute('''CREATE TABLE zoo
...: (critter VARCHAR(20) PRIMARY KEY,
...: count INT,
...: damages FLOAT)''')
ERROR:root:An unexpected error occurred while tokenizing input
The following traceback may be corrupted or invalid
The error message is: ('EOF in multi-line string', (1, 14))
---------------------------------------------------------------------------
OperationalError Traceback (most recent call last)
<ipython-input-4-819479c0680a> in <module>()
2 (critter VARCHAR(20) PRIMARY KEY,
3 count INT,
----> 4 damages FLOAT)''')
OperationalError: near "KEY,": syntax error
In [5]: curs.execute('''CREATE TABLE zoo
...: (critter VARCHAR(20) PRIMARY KEY,
...: count INT,
...: damages FLOAT)''')
Out[5]: <sqlite3.Cursor at 0x7fc4d86e2f10>
Python 只有在創建長字符串時才會用到三引號('''),例如 SQL 查詢。
現在往動物園中新增一些動物:
In [6]: curs.execute('INSERT INTO zoo VALUES("duck", 5, 0.0)')
Out[6]: <sqlite3.Cursor at 0x7fc4d86e2f10>
In [7]: curs.execute('INSERT INTO zoo VALUES("bear", 2, 1000.0)')
Out[7]: <sqlite3.Cursor at 0x7fc4d86e2f10>
使用 placeholder 是一種更安全的、插入數據的方法:
In [8]: ins = 'INSERT INTO zoo (critter,count,damages) VALUES (?,?,?)'
In [9]: curs.execute(ins,('weasel',1,2000.0))
Out[9]: <sqlite3.Cursor at 0x7fc4d86e2f10>
在 SQL 中使用三個問號表示要插入三個值,并把它們作為一個列表傳入函數 execute()。 這些占位符用來處理一些冗余的細節,例如引用(quoting)。它們會防止 SQL 注入:一種 常見的 Web 外部攻擊方式,向系統插入惡意的 SQL 命令。
現在使用 SQL 獲取所有動物:
In [10]: curs.execute('SELECT * from zoo')
Out[10]: <sqlite3.Cursor at 0x7fc4d86e2f10>
In [11]: rows = curs.fetchall()
In [12]: print(rows)
[('duck', 5, 0.0), ('bear', 2, 1000.0), ('weasel', 1, 2000.0)]
需要按照降序得到它們:
In [13]: curs.execute('SELECT * from zoo ORDER BY count DESC')
Out[13]: <sqlite3.Cursor at 0x7fc4d86e2f10>
In [14]: curs.fetchall()
Out[14]: [('duck', 5, 0.0), ('bear', 2, 1000.0), ('weasel', 1, 2000.0)]
哪種類型的動物花費最多呢?
In [15]: curs.execute('''SELECT * FROM zoo WHERE
...: damages = (SELECT MAX(damages) FROM zoo)''')
Out[15]: <sqlite3.Cursor at 0x7fc4d86e2f10>
In [16]: curs.fetchall()
Out[16]: [('weasel', 1, 2000.0)]
我們打開了一個連接(connection)或者游標 (cursor),不需要時應該關掉它們:
In [17]: curs.close()
In [18]: conn.close()
SQLAlchemy
對于所有的關系型數據庫而言,SQL 是不完全相同的,并且 DB-API 僅僅實現共有的部 分。每一種數據庫實現的是包含自己特征的和哲學的方言。許多庫函數用于消除它們之間的差異,最著名的跨數據庫的 Python 庫是 SQLAlchemy。
它不在 Python 的標準庫,但被廣泛認可,使用者眾多。在Linux系統中使用下面 這條命令安裝它:
pip install sqlalchemy
我們可以在以下層級上是用SQLAlchemy:
- 底層負責處理數據庫連接池,執行SQL命令以及返回結果,這和DB-API相似;
- 再往上是SQL表達式語言,更像Python的SQL生成器。
- 較高級的是對象關系模型(ORM),使用SQK表達式語言,將應用程序代碼和關系型數據結構結合起來。
SQLAlchemy實現在前面幾節提到的數據庫驅動程序的基礎上。因此不需要導入驅動程序,初始化的連接字符串會作出分配,例如:
dialect + driver ://user:password@host:port/dbname
字符串中的值代表如下含義。
- dialect
數據庫類型 - driver
使用該數據庫的特定驅動程序 - user和password
數據庫認證字符串 - host和port
數據庫服務器的位置(只有特定情況下會使用端口號:port) - dbname
初始連接到服務器中的數據庫
SQLAlchemy連接
數據庫類型 驅動程序
sqlite pysqlite(可以忽略)
mysql mysqlconnector
mysql pymysql
mysql oursql
postgresql psycopg2
postgresql pypostgresql
1.引擎層
首先,我們試用一下 SQLAlchemy 的底層,它可以實現多于基本 DB-API 的功能。
以內置于 Python 的 SQLite 為例,連接字符串忽略 host、port、user 和 password。dbname 表示存儲 SQLite 數據庫的文件,如果省去dbname,SQLite會在內存中創建數據庫。如果 dbname 以反斜線(/)開頭,那么它是文件所在的絕對路徑(Linux 和 OS X 是反斜線,而在 Windows 是例如 C:\ 的路徑名)。否則它是當前目錄下的相對路徑。
下面是一個小栗子:
導入庫函數,并起別名 sa
In [22]: import sqlalchemy as sa
連接到數據庫,并在內存中存儲它(參數字符串 'sqlite:///:memory:' 也是可行的):
In [23]: conn = sa.create_engine('sqlite://')
創建包含三列的數據庫表單 zoo:
In [24]: conn.execute('''CREATE TABLE zoo
...: (critter VARCHAR(20) PRIMARY KEY,
...: count INT,
...: damages FLOAT)''')
Out[24]: <sqlalchemy.engine.result.ResultProxy at 0x7fc4d2e6de48>
運行函數 conn.execute() 返回到一個 SQLAlchemy 的對象 ResultProxy
現在向空表單里插入三組數據:
In [25]: ins ='INSERT INTO zoo (critter, count, damages) VALUES (?, ?, ?)'
In [26]: conn.execute(ins, 'duck', 10, 0.0)
Out[26]: <sqlalchemy.engine.result.ResultProxy at 0x7fc4d2eb7fd0>
In [27]: conn.execute(ins, 'bear', 2, 1000.0)
Out[27]: <sqlalchemy.engine.result.ResultProxy at 0x7fc4d2efc518>
In [28]: conn.execute(ins, 'weasel', 1, 2000.0)
Out[28]: <sqlalchemy.engine.result.ResultProxy at 0x7fc4d2eadc50>
In [29]: rows = conn.execute('SELECT * FROM zoo)
File "<ipython-input-29-337b763205d0>", line 1
rows = conn.execute('SELECT * FROM zoo)
^
SyntaxError: EOL while scanning string literal
查詢所有放進去的數據:
In [30]: rows = conn.execute('SELECT * FROM zoo')
在SQLAlchemy中,row不是一個列表,不能直接輸出:
In [31]: print(rows)
<sqlalchemy.engine.result.ResultProxy object at 0x7fc4d2e6d9e8>
但它可以像列表一樣迭代,每次可以得到其中的一行:
In [32]: for row in rows:
...: print(row)
...:
('duck', 10, 0.0)
('bear', 2, 1000.0)
('weasel', 1, 2000.0)
這個例子幾乎和 SQLite DB-API 提到的示例是一樣的。一個優勢是在程序開始時不需要導入數據庫驅動程序,SQLAlchemy 從連接字符串(connection string)已經指定了。改變連 接字符串就可以使得代碼可移植到另一種數據庫。
2.SQL表達式語言
再往上一層是SQLAlchemy的SQL表達式語言。它介紹了創建多種SQL操作的函數。相比引擎層,他能處理更多SQL方言的差異,對于關系型數據庫應用是一種方便的中間層解 決方案。
下面介紹如何創建和管理數據表 zoo。
In [33]: import sqlalchemy as sa
In [34]: conn = sa.create_engine('sqlite://')
在定義表單 zoo 時,開始使用一些表達式語言代替 SQL:
In [35]: meta = sa.MetaData()
In [36]: zpptb = sa.Table('zoo',meta,
...: sa.Column('critter', sa.String, primary_key=True),
...: sa.Column('count',sa.Integer),
...: sa.Column('damages',sa.Float)
...: )
下面的的代碼創建了數據表
In [37]: meta.create_all(conn)
注意多行調用時的圓括號。Table() 方法的調用結構和表單的結構相一致,此表單中包含 三列,在 Table() 方法調用時括號內部也調用三次 Column()。
同時,zpptb是連接 SQL 數據庫和 Python 數據結構的一個對象。
使用表達式語言的更多函數插入數據:
In [38]: conn.execute(zpptb.insert(('bear',2,1000.0)))
Out[38]: <sqlalchemy.engine.result.ResultProxy at 0x7fc4d2e6d0b8>
In [39]: conn.execute(zoo.insert(('weasel', 1, 2000.0)))
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-39-1f816288e04e> in <module>()
----> 1 conn.execute(zoo.insert(('weasel', 1, 2000.0)))
NameError: name 'zoo' is not defined
In [40]: conn.execute(zpptb.insert(('weasel', 1, 2000.0)))
Out[40]: <sqlalchemy.engine.result.ResultProxy at 0x7fc4d2e64278>
In [41]: conn.execute(zpptb.insert(('duck', 10, 0)))
Out[41]: <sqlalchemy.engine.result.ResultProxy at 0x7fc4d2e4a550>
接下來創建 SELECT 語句(zpptb.select() 會選擇出 zpptb 對象表單的所有項,和 SELECT * FROM zoo 在普通 SQL 做的相同)
In [42]: result = conn.execute(zpptb.select())
最終得到的結果
In [43]: rows = result.fetchall()
In [44]: print(rows)
[('bear', 2, 1000.0), ('weasel', 1, 2000.0), ('duck', 10, 0.0)]
3.對象關系映射(Object Relation Mapping)
在上一節中,對象 zpptb 是 SQL 和 Python 之間的中間層連接。在 SQLAlchemy 的頂層,對象關系映射(ORM)使用 SQL 表達式語言,但盡量隱藏實際數據庫的機制。我們自己定義 類,ORM 負責處理如何讀寫數據庫的數據。在 ORM 這個復雜短語背后,最基本的觀點 是:同樣使用一個關系型數據庫,但操作數據的方式仍然和Python保持接近。
我們定義一個類Zoo,并把它掛接到ORM。這一次我們使用SQLite的zoo.db文件以便驗證ORM是否有效。
初始的import 還是一樣,這一次需要導入新的東西:
In [1]: import sqlalchemy as sa
In [2]: from sqlalchemy.ext.declarative import declarative_base
連接數據庫:
In [3]: conn = sa.create_engine('sqlite:///zoo.db')
現在進入 SQLAlchemy 的 ORM,定義類 Zoo,并關聯它的屬性和表單中的列:
In [4]: Base = declarative_base()
In [5]: class Zoo(Base) :
...: __tablename__ = 'zoo'
...: critter = sa.Column('critter', sa.String, primary_key=True)
...: count = sa.Column('count', sa.Integer)
...: damages = sa.Column('damages', sa.Float)
...: def __init__(self, critter, count, damages):
...: self.critter = critter
...: self.count = count
...: self.damages = damages
...: def __repr__(self):
...: return "<Zoo({}, {}, {})>".format(self.critter, self.count, self.damages)
...:
下面這行代碼創建數據庫和表單:
In [6]: Base.metadata.create_all(conn)
然后通過創建 Python 對象插入數據,ORM 內部會管理這些:
In [7]: first = Zoo('duck',10,0.0)
In [8]: second = Zoo('bear', 2, 1000.0)
In [9]: third = Zoo('weasel', 1, 2000.0)
In [10]: first
Out[10]: <Zoo(duck, 10, 0.0)>
接下來,利用 ORM 接觸 SQL,創建連接到數據庫的會話(session):
In [11]: from sqlalchemy.orm import sessionmaker
In [12]: Session = sessionmaker(bind=conn)
In [13]: session = Session()
借助會話,把創建的三個對象寫入數據庫。add() 函數增加一個對象,而 add_all() 增加一 個列表
In [14]: session.add(first)
In [15]: session.add_all([second,third])
最后使整個過程完整:
In [16]: session.commit()
我們現在在當前目錄下創建了文件zoo.db,可以使用命令行的 SQLite3 程序驗證 一下:
[root@wangerxiao ~]# sqlite3 zoo.db
SQLite version 3.5.6
Enter ".help" for instructions
sqlite> .tables
zoo
sqlite> select * from zoo;
duck|10|0.0
bear|2|1000.0
weasel|1|2000.0
本節的目的是介紹 ORM 和它在頂層的實現過程。
四個層級按照需求選擇:
- 普通 DB-API
- SQLAlchemy 引擎層
- SQLAlchemy 表達式語言
- SQLAlchemy ORM
MySQL
MySQL(http://www.mysql.com)是一款非常流行的開源關系型數據庫。不同于 SQLite, 它是真正的數據庫服務器,因此客戶端可以通過網絡從不同的設備連接它。
MySQL的驅動程序:Connector,PYMySQL,oursql
PostgreSQL
PostgreSQL(http://www.postgresql.org/) 是一款功能全面的開源關系型數據庫,在很多方 面超過 MySQL.
名稱 鏈接 Pypi包 導入 注意
psycopg2 http://initd.org/psycopg/ psycopg2 psycopg2 需要來自 PostgreSQL 客戶端工具 的 pg_con?g
最流行的驅動程序是 psycopg2,但是它的安裝依賴 PostgreSQL 客戶端的相關庫。
注:本文內容來自《Python語言及其應用》歡迎購買原書閱讀