SQL Expression Language對原生SQL語言進行了簡單的封裝
兩大模組SQLAlchemy Core and ORM:
-
Core:提供執行SQL Expression Language的介面
-
ORM
安裝:SQLAlchemy及相關資料庫驅動pip install sqlalchemy pymysql
連線到資料庫
資料庫連線字串格式:請參考這裡
mysql://username:password@hostname/database
postgresql://username:password@hostname/database
sqlite:////absolute/path/to/database
oracle://scott:tiger@127.0.0.1:1521/orcl
比如SQLite如下:
from sqlalchemy import create_engine
engine = create_engine(`sqlite:///cookies.db`)
engine2 = create_engine(`sqlite:///:memory:`)
engine3 = create_engine(`sqlite:////home/cookiemonster/cookies.db`)
engine4 = create_engine(`sqlite:///c:\Users\cookiemonster\cookies.db`)
注意:create_engine函式返回以一個engine例項,但是不會立即獲取資料庫連線,直到在engine上進行操作如查詢時才會去獲取connection
關於MySQL空閒連線8小時自動關閉的解決方案:傳入 pool_recycle=3600引數
from sqlalchemy import create_engine
engine = create_engine(`mysql+pymysql://cookiemonster:chocolatechip@mysql01.monster.internal/cookies`, pool_recycle=3600)
create_engine其餘的一些引數:
-
echo:是否log列印執行的sql語句及其引數。預設為False
-
encoding:預設utf-8
-
isolation_level:隔離級別
-
pool_recycle:指定連線回收間隔,這對於MySQL連線的8小時機制特別重要。預設-1
獲取連線
from sqlalchemy import create_engine
engine = create_engine(`mysql+pymysql://cookiemonster:chocolatechip`
`@mysql01.monster.internal/cookies`, pool_recycle=3600)
connection = engine.connect()
Schema and Types
四種型別集合:
• Generic
• SQL standard
• Vendor specific
• User defined
SQLAlchemy定義了很多generic types以相容不同資料庫。這些型別都定義在sqlalchemy.types
模組中,為了方便也可以從sqlalchemy直接匯入這些型別。
型別對應表如下:
SQLAlchemy | Python | SQL |
---|---|---|
BigInteger | int | BIGINT |
Boolean | bool | BOOLEAN or SMALLINT |
Date | datetime.date | DATE (SQLite: STRING) |
DateTime | datetime.datetime | DATETIME (SQLite: STRING) |
Enum | str | ENUM or VARCHAR |
Float | float or Decimal | FLOAT or REAL |
Integer | int | INTEGER |
Interval | datetime.timedelta | INTERVAL or DATE from epoch |
LargeBinary | byte | BLOB or BYTEA |
Numeric | decimal.Decimal | NUMERIC or DECIMAL |
Unicode | unicode | UNICODE or VARCHAR |
Text | str | CLOB or TEXT |
Time | datetime.time | DATETIME |
如果這些型別不能滿足你,比如有些資料庫支援json型別,那麼你需要用到sqlalchemy.dialects
模組中對應資料庫的型別。比如from sqlalchemy.dialects.postgresql import JSON
Metadata & Table & Column
Metadata為了快速訪問資料庫。可以看作是很多Table物件的集合,還有一些關於engin,connection的資訊。可以通過MetaData.tables
訪問這些表物件字典
定義表物件之前需要先例項化Metadata:
from sqlalchemy import MetaData
metadata = MetaData()
Table物件構建如下:第一個引數為名稱,第二個引數為Metadata物件,後續引數為Column物件. Column物件引數為,名稱,型別,及其餘等
from sqlalchemy import Table, Column, Integer, Numeric, String, ForeignKey
cookies = Table(`cookies`, metadata,
Column(`cookie_id`, Integer(), primary_key=True),
Column(`cookie_name`, String(50), index=True),
Column(`cookie_recipe_url`, String(255)),
Column(`cookie_sku`, String(55)),
Column(`quantity`, Integer()),
Column(`unit_cost`, Numeric(12, 2))
)
from datetime import datetime
from sqlalchemy import DateTime
users = Table(`users`, metadata,
Column(`user_id`, Integer(), primary_key=True),
Column(`username`, String(15), nullable=False, unique=True),
Column(`email_address`, String(255), nullable=False),
Column(`phone`, String(20), nullable=False),
Column(`password`, String(25), nullable=False),
Column(`created_on`, DateTime(), default=datetime.now),
Column(`updated_on`, DateTime(), default=datetime.now, onupdate=datetime.now)
注意:這裡default,onupdate屬性是一個callable物件而不是直接值,比如datetime.now(),因為這樣的話,就永遠是這個值,而不是每個例項例項化、更新時的時間了。
比較有用的就是onupdate
,每次更新時都會呼叫該方法或函式。
鍵和約束(Keys and Constraints)
鍵和約束既可以像上面那樣通過kwargs定義在Column中,也可以在之後通過物件新增。相關類定義在基礎的 sqlalchemy模組中,比如最常用的三個:from sqlalchemy import PrimaryKeyConstraint, UniqueConstraint, CheckConstraint
PrimaryKeyConstraint(`user_id`, name=`user_pk`),它也支援同時定義多個形成聯合主鍵。
UniqueConstraint(`username`, name=`uix_username`)
CheckConstraint(`unit_cost >= 0.00`, name=`unit_cost_positive`)
索引(Index)
from sqlalchemy import Index
Index(`ix_cookies_cookie_name`, `cookie_name`)
這個定義需要放置在Table構造器中。也可以在之後定義,比如Index(`ix_test`, mytable.c.cookie_sku, mytable.c.cookie_name))
關聯關係和外來鍵約束(Relationships and ForeignKeyConstraints)
from sqlalchemy import ForeignKey
orders = Table(`orders`, metadata,
Column(`order_id`, Integer(), primary_key=True),
Column(`user_id`, ForeignKey(`users.user_id`)),
Column(`shipped`, Boolean(), default=False)
)
line_items = Table(`line_items`, metadata,
Column(`line_items_id`, Integer(), primary_key=True),
Column(`order_id`, ForeignKey(`orders.order_id`)),
Column(`cookie_id`, ForeignKey(`cookies.cookie_id`)),
Column(`quantity`, Integer()),
Column(`extended_cost`, Numeric(12, 2))
)
注意:這裡ForeignKey用的是字串引數(這些字串對應的是資料庫中的表名.列名),而非引用。這樣隔離了模組間相互依賴
我們也可以使用:ForeignKeyConstraint([`order_id`], [`orders.order_id`])
建立或持久化表模式(Persisting the Tables)
通過示例程式碼我們知道所有的Table定義,以及額外的模式定義都會與一個metadata物件關聯。我們可以通過這個metadata物件來建立表:
metadata.create_all(engine)
注意:預設情況下create_all不會
重新建立已有表,所以它可以安全地多次呼叫,而且也非常友好地與資料庫遷移庫如Ablembic整合而不需要你進行額外手動編碼。
本節程式碼完整如下:
from datetime import datetime
from sqlalchemy import (MetaData, Table, Column, Integer, Numeric, String,
DateTime, ForeignKey, create_engine)
metadata = MetaData()
cookies = Table(`cookies`, metadata,
Column(`cookie_id`, Integer(), primary_key=True),
Column(`cookie_name`, String(50), index=True),
Column(`cookie_recipe_url`, String(255)),
Column(`cookie_sku`, String(55)),
Column(`quantity`, Integer()),
Column(`unit_cost`, Numeric(12, 2))
)
users = Table(`users`, metadata,
Column(`user_id`, Integer(), primary_key=True),
Column(`customer_number`, Integer(), autoincrement=True),
Column(`username`, String(15), nullable=False, unique=True),
Column(`email_address`, String(255), nullable=False),
Column(`phone`, String(20), nullable=False),
Column(`password`, String(25), nullable=False),
Column(`created_on`, DateTime(), default=datetime.now),
Column(`updated_on`, DateTime(), default=datetime.now, onupdate=datetime.now)
)
orders = Table(`orders`, metadata,
Column(`order_id`, Integer(), primary_key=True),
Column(`user_id`, ForeignKey(`users.user_id`))
)
line_items = Table(`line_items`, metadata,
Column(`line_items_id`, Integer(), primary_key=True),
Column(`order_id`, ForeignKey(`orders.order_id`)),
Column(`cookie_id`, ForeignKey(`cookies.cookie_id`)),
Column(`quantity`, Integer()),
Column(`extended_cost`, Numeric(12, 2))
)
engine = create_engine(`sqlite:///:memory:`)
metadata.create_all(engine)
SQLAlchemy-Core模組
插入資料:
ins = cookies.insert().values(
cookie_name="chocolate chip",
cookie_recipe_url="http://some.aweso.me/cookie/recipe.html",
cookie_sku="CC01",
quantity="12",
unit_cost="0.50"
)
print(str(ins))
當然你也可以這麼做:
from sqlalchemy import insert
ins = insert(cookies).values(
cookie_name="chocolate chip",
cookie_recipe_url="http://some.aweso.me/cookie/recipe.html",
cookie_sku="CC01",
quantity="12",
unit_cost="0.50"
)
上述編譯成預編譯語句如下:
INSERT INTO cookies
(cookie_name, cookie_recipe_url, cookie_sku, quantity, unit_cost)
VALUES
(:cookie_name, :cookie_recipe_url, :cookie_sku, :quantity, :unit_cost)
實際過程會是如下ins物件內部會呼叫compile()方法編譯成上述語句,然後將引數儲存到ins.compile().params字典中。
接下來我們通過前面獲取的connection物件執行statement:
result = connection.execute(ins)
當然你也可以這麼查詢:
ins = cookies.insert()
result = connection.execute(
ins,
cookie_name=`dark chocolate chip`,
cookie_recipe_url=`http://some.aweso.me/cookie/recipe_dark.html`,
cookie_sku=`CC02`,
quantity=`1`,
unit_cost=`0.75`
)
result.inserted_primary_key
批量插入:
inventory_list = [
{
`cookie_name`: `peanut butter`,
`cookie_recipe_url`: `http://some.aweso.me/cookie/peanut.html`,
`cookie_sku`: `PB01`,
`quantity`: `24`,
`unit_cost`: `0.25`
},
{
`cookie_name`: `oatmeal raisin`,
`cookie_recipe_url`: `http://some.okay.me/cookie/raisin.html`,
`cookie_sku`: `EWW01`,
`quantity`: `100`,
`unit_cost`: `1.00`
}
]
result = connection.execute(ins, inventory_list)
注意:一定要確保所有字典引數擁有相同的keys
查詢
from sqlalchemy.sql import select
s = select([cookies])
rp = connection.execute(s)
results = rp.fetchall()
當然我們也可以使用字串來代替:
s = select("""SELECT cookies.cookie_id, cookies.cookie_name,
cookies.cookie_recipe_url, cookies.cookie_sku, cookies.quantity,
cookies.unit_cost FROM cookies""")
connection.execute返回的rp變數是一個ResultProxy
物件(它是DBAPI中cursor物件的封裝)。
我們也可以這樣寫:
from sqlalchemy.sql import select
s = cookies.select()
rp = connection.execute(s)
results = rp.fetchall()
ResultProxy使得查詢結果可以通過index,name,or Column object訪問列資料。例如:
first_row = results[0]
first_row[1] #遊標列索引從1開始,by index
first_row.cookie_name # by name
first_row[cookies.c.cookie_name] #by Column object.
你也可以迭代ResultProxy,如下:
rp = connection.execute(s)
for record in rp:
print(record.cookie_name)
ResultProxy其餘可用來獲取結果集的方法
-
first()
-
fetchone()
-
fetchall()
-
scalar():Returns a single value if a query results in a single record with one column.
-
keys() 獲取列名
關於選擇ResultProxy上述的方法的建議:
1、使用first()而不是fetchone()來獲取單條記錄,因為fetchone()呼叫之後仍然保留著開啟的connections共後續使用,如果不小心的話很容易引起問題。
2、使用迭代方式獲取所有結果,而不是fetchall(),更加省記憶體。
3、使用scalar()獲取單行單列結果時需要注意,如果返回多於一行,它會丟擲異常。
控制返回列的數目
s = select([cookies.c.cookie_name, cookies.c.quantity])
rp = connection.execute(s)
print(rp.keys())
result = rp.first()
排序
s = select([cookies.c.cookie_name, cookies.c.quantity])
s = s.order_by(cookies.c.quantity)
rp = connection.execute(s)
for cookie in rp:
print(`{} - {}`.format(cookie.quantity, cookie.cookie_name))
#倒序desc
from sqlalchemy import desc
s = select([cookies.c.cookie_name, cookies.c.quantity])
s = s.order_by(desc(cookies.c.quantity))
限制返回結果集的條數
s = select([cookies.c.cookie_name, cookies.c.quantity])
s = s.order_by(cookies.c.quantity)
s = s.limit(2)
rp = connection.execute(s)
print([result.cookie_name for result in rp])
內建SQL函式
在sqlalchemy.sql.func模組中
#sum
from sqlalchemy.sql import func
s = select([func.sum(cookies.c.quantity)])
rp = connection.execute(s)
print(rp.scalar())
#count
s = select([func.count(cookies.c.cookie_name)])
rp = connection.execute(s)
record = rp.first()
print(record.keys())
print(record.count_1) #欄位名是自動生成的,<func_name>_<position>,可以設定別名的,看下面
#設定別名
s = select([func.count(cookies.c.cookie_name).label(`inventory_count`)])
rp = connection.execute(s)
record = rp.first()
print(record.keys())
print(record.inventory_count)
過濾
#where
s = select([cookies]).where(cookies.c.cookie_name == `chocolate chip`)
rp = connection.execute(s)
record = rp.first()
print(record.items()) #呼叫row物件的items()方法。
#like
s = select([cookies]).where(cookies.c.cookie_name.like(`%chocolate%`))
rp = connection.execute(s)
for record in rp.fetchall():
print(record.cookie_name)
可以在where中使用的子句元素
-
between(cleft, cright)
-
concat(column_two) Concatenate column with column_two
-
distinct()
-
in_([list])
-
is_(None) Find where the column is None (commonly used for Null checks with None)
-
contains(string) Find where the column has string in it (case-sensitive)
-
endswith(string) Find where the column ends with string (case-sensitive)
-
like(string) Find where the column is like string (case-sensitive)
-
startswith(string) Find where the column begins with string (case-sensitive)
-
ilike(string) Find where the column is like string (this is not case-sensitive)
當然還包括一系列的notxxx方法,比如notin_(),唯一的例外是isnot()
操作符
-
+,-,*,/,%
-
==,!=,<,>,<=,>=
-
AND,OR,NOT,由於python關鍵字的原因,使用and_(),or_(),not_()來代替
+號還可以用於字串拼接:
s = select([cookies.c.cookie_name, `SKU-` + cookies.c.cookie_sku])
for row in connection.execute(s):
print(row)
from sqlalchemy import cast
s = select([cookies.c.cookie_name,
cast((cookies.c.quantity * cookies.c.unit_cost),
Numeric(12,2)).label(`inv_cost`)])
for row in connection.execute(s):
print(`{} - {}`.format(row.cookie_name, row.inv_cost))
注意:cast是另外一個函式,允許我們進行型別轉換,上述轉換是將數字轉換為貨幣形式,和
print(`{} – {:.2f}`.format(row.cookie_name, row.inv_cost)).這個行為一致。
from sqlalchemy import and_, or_, not_
s = select([cookies]).where(
and_(
cookies.c.quantity > 23,
cookies.c.unit_cost < 0.40
)
)
for row in connection.execute(s):
print(row.cookie_name)
from sqlalchemy import and_, or_, not_
s = select([cookies]).where(
or_(
cookies.c.quantity.between(10, 50),
cookies.c.cookie_name.contains(`chip`)
)
)
for row in connection.execute(s):
print(row.cookie_name)
update
from sqlalchemy import update
u = update(cookies).where(cookies.c.cookie_name == "chocolate chip")
u = u.values(quantity=(cookies.c.quantity + 120))
result = connection.execute(u)
print(result.rowcount)
s = select([cookies]).where(cookies.c.cookie_name == "chocolate chip")
result = connection.execute(s).first()
for key in result.keys():
print(`{:>20}: {}`.format(key, result[key]))
delete
from sqlalchemy import delete
u = delete(cookies).where(cookies.c.cookie_name == "dark chocolate chip")
result = connection.execute(u)
print(result.rowcount)
s = select([cookies]).where(cookies.c.cookie_name == "dark chocolate chip")
result = connection.execute(s).fetchall()
print(len(result))
joins
join(),outerjoin()函式,select_from()函式
columns = [orders.c.order_id, users.c.username, users.c.phone,
cookies.c.cookie_name, line_items.c.quantity,
line_items.c.extended_cost]
cookiemon_orders = select(columns)
cookiemon_orders = cookiemon_orders.select_from(orders.join(users).join(
line_items).join(cookies)).where(users.c.username ==
`cookiemon`)
result = connection.execute(cookiemon_orders).fetchall()
for row in result:
print(row)
最終產生的SQL語句如下:
SELECT orders.order_id, users.username, users.phone, cookies.cookie_name,
line_items.quantity, line_items.extended_cost FROM users JOIN orders ON
users.user_id = orders.user_id JOIN line_items ON orders.order_id =
line_items.order_id JOIN cookies ON cookies.cookie_id = line_items.cookie_id
WHERE users.username = :username_1
outerjoin
columns = [users.c.username, func.count(orders.c.order_id)]
all_orders = select(columns)
all_orders = all_orders.select_from(users.outerjoin(orders))
all_orders = all_orders.group_by(users.c.username)
result = connection.execute(all_orders).fetchall()
for row in result:
print(row)
表別名函式alias()
>>> manager = employee_table.alias(`mgr`)
>>> stmt = select([employee_table.c.name],
... and_(employee_table.c.manager_id==manager.c.id,
... manager.c.name==`Fred`))
>>> print(stmt)
SELECT employee.name
FROM employee, employee AS mgr
WHERE employee.manager_id = mgr.id AND mgr.name = ?
分組
columns = [users.c.username, func.count(orders.c.order_id)]
all_orders = select(columns)
all_orders = all_orders.select_from(users.outerjoin(orders))
all_orders = all_orders.group_by(users.c.username)
result = connection.execute(all_orders).fetchall()
for row in result:
print(row)
chaining
def get_orders_by_customer(cust_name, shipped=None, details=False):
columns = [orders.c.order_id, users.c.username, users.c.phone]
joins = users.join(orders)
if details:
columns.extend([cookies.c.cookie_name, line_items.c.quantity,
line_items.c.extended_cost])
joins = joins.join(line_items).join(cookies)
cust_orders = select(columns)
cust_orders = cust_orders.select_from(joins)
cust_orders = cust_orders.where(users.c.username == cust_name)
if shipped is not None:
cust_orders = cust_orders.where(orders.c.shipped == shipped)
result = connection.execute(cust_orders).fetchall()
return result
執行原生SQL
返回的還是ResultProxy物件
1、完全採用原始SQL
result = connection.execute("select * from orders").fetchall()
print(result)
2、部分採用原始SQL,text()函式
from sqlalchemy import text
stmt = select([users]).where(text("username=`cookiemon`"))
print(connection.execute(stmt).fetchall())
異常
SQLALchemy定義了很多異常。我們通過關心:AttributeErrors,IntegrityErrors.等
為了進行相關試驗與說明,請先執行下面這些語句
from datetime import datetime
from sqlalchemy import (MetaData, Table, Column, Integer, Numeric, String,
DateTime, ForeignKey, Boolean, create_engine,
CheckConstraint)
metadata = MetaData()
cookies = Table(`cookies`, metadata,
Column(`cookie_id`, Integer(), primary_key=True),
37
Column(`cookie_name`, String(50), index=True),
Column(`cookie_recipe_url`, String(255)),
Column(`cookie_sku`, String(55)),
Column(`quantity`, Integer()),
Column(`unit_cost`, Numeric(12, 2)),
CheckConstraint(`quantity > 0`, name=`quantity_positive`)
)
users = Table(`users`, metadata,
Column(`user_id`, Integer(), primary_key=True),
Column(`username`, String(15), nullable=False, unique=True),
Column(`email_address`, String(255), nullable=False),
Column(`phone`, String(20), nullable=False),
Column(`password`, String(25), nullable=False),
Column(`created_on`, DateTime(), default=datetime.now),
Column(`updated_on`, DateTime(),
default=datetime.now, onupdate=datetime.now)
)
orders = Table(`orders`, metadata,
Column(`order_id`, Integer()),
Column(`user_id`, ForeignKey(`users.user_id`)),
Column(`shipped`, Boolean(), default=False)
)
line_items = Table(`line_items`, metadata,
Column(`line_items_id`, Integer(), primary_key=True),
Column(`order_id`, ForeignKey(`orders.order_id`)),
Column(`cookie_id`, ForeignKey(`cookies.cookie_id`)),
Column(`quantity`, Integer()),
Column(`extended_cost`, Numeric(12, 2))
)
engine = create_engine(`sqlite:///:memory:`)
metadata.create_all(engine)
connection = engine.connect()
from sqlalchemy import select, insert
ins = insert(users).values(
username="cookiemon",
email_address="mon@cookie.com",
phone="111-111-1111",
password="password"
)
result = connection.execute(ins)
s = select([users.c.username])
results = connection.execute(s)
for result in results:
print(result.username)
print(result.password) #此處包AttributeError異常
在違反約束的情況下會出現IntegrityError異常。比如違反唯一性約束等。
s = select([users.c.username])
connection.execute(s).fetchall()
[(u`cookiemon`,)]
ins = insert(users).values(
username="cookiemon",
email_address="damon@cookie.com",
phone="111-111-1111",
password="password"
)
result = connection.execute(ins) #此處報IntegrityError, UNIQUE constraint failed: users.username
#異常處理
try:
result = connection.execute(ins)
except IntegrityError as error:
print(error.orig.message, error.params)
所有的SQLAlchemy異常處理方式都是上面那種思路,通過[SQLAlchemyError](http://docs.sqlal
chemy.org/en/latest/core/exceptions.html)可以獲取到的資訊由如下:
-
orig :The DBAPI exception object.
-
params:The parameter list being used when this exception occurred.
-
statement :The string SQL statement being invoked when this exception occurred.
事務Transactions
from sqlalchemy.exc import IntegrityError
def ship_it(order_id):
s = select([line_items.c.cookie_id, line_items.c.quantity])
s = s.where(line_items.c.order_id == order_id)
transaction = connection.begin() #開啟事務
cookies_to_ship = connection.execute(s).fetchall()
try:
for cookie in cookies_to_ship:
u = update(cookies).where(cookies.c.cookie_id == cookie.cookie_id)
u = u.values(quantity=cookies.c.quantity - cookie.quantity)
result = connection.execute(u)
u = update(orders).where(orders.c.order_id == order_id)
u = u.values(shipped=True)
result = connection.execute(u)
print("Shipped order ID: {}".format(order_id))
transaction.commit() #提交事務
except IntegrityError as error:
transaction.rollback() #事務回滾
print(error)