08. 查詢
Pony 提供了一種非常方便的方法怖现,可以使用生成器表達(dá)式語法查詢數(shù)據(jù)庫屈嗤。
Pony允許程序員使用原生的Python語法來處理存儲在數(shù)據(jù)庫中的對象吊输,就像存儲在內(nèi)存中一樣。它使開發(fā)變得更加容易茫船。
在編寫查詢時扭屁,你可以使用Python生成器表達(dá)式或lambda匿名函數(shù)。
使用Python生成器表達(dá)式
Pony允許使用生成器表達(dá)式作為編寫數(shù)據(jù)庫查詢的一種非常自然的方式濒生。
Pony 提供了select()
函數(shù)幔欧,它接受 Python 生成器礁蔗,將其翻譯成 SQL,并從數(shù)據(jù)庫中返回對象晒骇,翻譯的過程在這個StackOverflow question中描述了。
下面是一個查詢的例子:
query = select(c for c in Customer
if sum(o.total_price for o in c.orders) > 1000)
或者徒坡,使用屬性提升 attribute lifting瘤缩。
query = select(c for c in Customer
if sum(c.orders.total_price) > 1000)
在查詢中剥啤,你可以應(yīng)用filter()函數(shù)
:
query2 = query.filter(lambda person: person.age > 18)
也可以根據(jù)另一個查詢建立新的查詢:
query3 = select(customer.name for customer in query2
if customer.country == 'Canada')
select()
函數(shù)返回一個Query
類的實例,然后可以調(diào)用Query
對象方法來獲取結(jié)果刻诊,例如:
customer_name = query3.first()
從查詢中可以返回實體牺丙、屬性或元組的任意表達(dá)式。
select((c, sum(c.orders.total_price))
for c in Customer if sum(c.orders.total_price) > 1000)
使用lambda函數(shù)
除了使用生成器是整,你也可以使用lambda函數(shù)來編寫查詢:
Customer.select(lambda c: sum(c.order.price) > 1000)
從將查詢轉(zhuǎn)換為SQL的角度來看浮入,如果你使用生成器或lambda羊异,沒有什么區(qū)別。
唯一的區(qū)別是易迹,使用lambda只能返回實體實例--沒有辦法返回具體的實體屬性列表或元組列表平道。
用于查詢數(shù)據(jù)庫的Pony ORM函數(shù)
詳情請參閱API參考資料中的查詢和函數(shù)部分一屋。
Pony查詢示例
為了演示Pony查詢,讓我們用Pony ORM發(fā)行版中的例子闸衫。
你可以在交互模式下自己嘗試這些查詢诽嘉,并查看生成的SQL弟翘。
為此稀余,我們可以這樣導(dǎo)入示例模塊:
>>> from pony.orm.examples.estore import *
本模塊提供了一個簡化的電子商務(wù)網(wǎng)店的數(shù)據(jù)模型酥筝,以下是數(shù)據(jù)模型的ER圖:
下面是實體的定義:
from decimal import Decimal
from datetime import datetime
from pony.converting import str2datetime
from pony.orm import *
db = Database()
class Customer(db.Entity):
email = Required(str, unique=True)
password = Required(str)
name = Required(str)
country = Required(str)
address = Required(str)
cart_items = Set('CartItem')
orders = Set('Order')
class Product(db.Entity):
id = PrimaryKey(int, auto=True)
name = Required(str)
categories = Set('Category')
description = Optional(str)
picture = Optional(buffer)
price = Required(Decimal)
quantity = Required(int)
cart_items = Set('CartItem')
order_items = Set('OrderItem')
class CartItem(db.Entity):
quantity = Required(int)
customer = Required(Customer)
product = Required(Product)
class OrderItem(db.Entity):
quantity = Required(int)
price = Required(Decimal)
order = Required('Order')
product = Required(Product)
PrimaryKey(order, product)
class Order(db.Entity):
id = PrimaryKey(int, auto=True)
state = Required(str)
date_created = Required(datetime)
date_shipped = Optional(datetime)
date_delivered = Optional(datetime)
total_price = Required(Decimal)
customer = Required(Customer)
items = Set(OrderItem)
class Category(db.Entity):
name = Required(str, unique=True)
products = Set(Product)
set_sql_debug(True)
db.bind('sqlite', 'estore.sqlite', create_db=True)
db.generate_mapping(create_tables=True)
當(dāng)你導(dǎo)入這個例子時,它將在文件'estore.sqlite'中創(chuàng)建SQLite數(shù)據(jù)庫茁影,并在其中填充一些測試數(shù)據(jù)募闲,下面你可以看到一些查詢的例子:
# 所有美國客戶
Customer.select(lambda c: c.country == 'USA')
# 每個國家的客戶數(shù)量
select((c.country, count(c)) for c in Customer)
# 最大產(chǎn)品價格
max(p.price for p in Product)
# 最大固態(tài)硬盤價格
max(p.price for p in Product)
for cat in p. categories if cat.name =='Solid State Drives')
#最貴的三款產(chǎn)品
Product.select().order_by(desc(Product.price))[:3]
# 缺貨產(chǎn)品
Product.select(lambda p: p.quantity ==0)
# 最受歡迎的產(chǎn)品
Product.select().order_by(lambda p: desc(sum(p.order_items.quantity))).first()
# 從未訂購過的產(chǎn)品
Product.select(lambda p: not p.order_items)
# 下過幾次訂單的客戶數(shù)
Customer.select(lambda c: count(c.order) > 1)
# 最有價值的三個客戶
Customer.select().order_by(lambda c: desc(sum(c.orders.total_price)))[:3]
# 訂單已發(fā)貨的客戶
Customer.select(lambda c: SHIPPED in c.order.state)
# 沒有訂單的客戶數(shù)
Customer.select(lambda c: not c.order)
# 同樣的查詢,用LEFT JOIN代替NOT EXISTS靴患。
left_join(c for c in Customer for o in c.order if o is None)
# 訂購不同藥片的客戶數(shù)
選擇(c for c in Customer
for p in c.order.projects.product.
if 'Tablets' in p.category.name and count(p) > 1)
你在pony.orm.examples.estore可以找到更多的查詢示例鸳君。
查詢對象方法
詳情請參閱API參考資料中的查詢結(jié)果部分患蹂。
在查詢中使用日期和時間
可以在查詢中用datetime
和timedelta
進(jìn)行算術(shù)運(yùn)算。
如果表達(dá)式可以用Python計算囱挑,Pony會把計算結(jié)果作為參數(shù)傳遞到查詢中:
select(o for o in Order if o.date_created >= datetime.now() - timedelta(days=3))[:]
SELECT "o". "id", "o". "state", "o". "date_created", "o". "date_shipped",
"o". "date_delivered", "o". "total_price", "o". "customer".
FROM "訂單" "o"
WHERE "o". "date_created">= ?
如果需要用該屬性進(jìn)行操作沼溜,我們無法事先計算系草,這就是為什么這樣的表達(dá)式會被翻譯成SQL的原因:
select(o for o in Order if o.date_created + timedelta(days=3) >= datetime.now())[:]
SELECT "o"."id", "o"."state", "o"."date_created", "o"."date_shipped",
"o"."date_delivered", "o"."total_price", "o"."customer"
FROM "Order" "o"
WHERE datetime("o"."date_created", '+3 days') >= ?
Pony生成的SQL會因數(shù)據(jù)庫的不同而不同,上面是SQLite的例子棠隐,下面是同樣的查詢檐嚣,翻譯成PostgreSQL:
SELECT "o". "id", "o". "state", "o". "date_created", "o". "date_shipped",
"o". "date_delivered", "o". "total_price", "o". "customer".
FROM "order" "o"
WHERE ( "o". "date_created" + INTERVAL '72:0:0:0' DAY TO SECOND) >= %(p1)s
如果你需要使用SQL函數(shù),可以使用raw_sql()
函數(shù)來包含這個SQL片段:
select(m for m in DBVoteMessage if m.date >= raw_sql("NOW() - '1 minute'::INTERVAL"))
使用Pony可以使用日期時間屬性嗡贺,如月诫睬、小時等,根據(jù)數(shù)據(jù)庫的不同续徽,會被翻譯成不同的SQL亲澡,提取這個屬性的值,在這個例子中客情,我們得到的是月份屬性:
select(o for o in Order if o.date_created.month ==12)
下面是SQLite的翻譯結(jié)果:
SELECT "o"."id", "o"."state", "o"."date_created", "o"."date_shipped",
"o"."date_delivered", "o"."total_price", "o"."customer"
FROM "Order" "o"
WHERE cast(substr("o"."date_created", 6, 2) as integer) = 12
而對于PostgreSQL則是:
SELECT "o"."id", "o"."state", "o"."date_created", "o"."date_shipped",
"o"."date_delivered", "o"."total_price", "o"."customer"
FROM "order" "o"
WHERE EXTRACT(MONTH FROM "o"."date_created") = 12
自動唯一
Pony試圖通過在必要的地方自動添加DISTINCT
SQL關(guān)鍵字來避免查詢結(jié)果中的重復(fù)膀斋,因為重復(fù)且有用的查詢結(jié)果很難遇到痹雅。
當(dāng)有人想要檢索具有特定條件的對象時,通常不會期望同一個對象會被多次返回惰匙。
同時铃将,避免重復(fù)的查詢結(jié)果也使查詢結(jié)果更具有可預(yù)測性:你不需要從查詢結(jié)果中篩選出重復(fù)的對象劲阎。
Pony只有在可能存在重復(fù)的情況下才會添加disctinct
關(guān)鍵字,讓我們考慮幾個例子:
1. 檢索有條件的對象
Person.select(lambda p: p.age > 20 and p.name == 'John')
在這個例子中龄毡,查詢不返回重復(fù)锡垄,因為結(jié)果中包含了Person的主鍵列,因為這里不可能出現(xiàn)重復(fù)路操,所以沒有必要添加DISTINCT關(guān)鍵字,Pony也不添加:
SELECT "p"."id", "p"."name", "p"."age"
FROM "Person" "p"
WHERE "p"."age" > 20
AND "p"."name" = 'John'
2. 檢索對象的屬性
select(p.name for p in Person)
這個查詢結(jié)果返回的不是對象搞坝,而是它的屬性魁袜,這個查詢結(jié)果可能包含重復(fù)峰弹,所以Pony會在這個查詢中加入DISTINCT。
SELECT DISTINCT "p". "name"
FROM "Person" "p"
這種查詢的結(jié)果通常用于下拉列表垫桂,在這里不會出現(xiàn)重復(fù)的情況粟按,當(dāng)你想在這里出現(xiàn)重復(fù)的情況時霹粥,想出一個真正的用例并不容易后控。
如果你需要統(tǒng)計同名的人,最好使用聚合查詢:
select((p.name, count(p)) for p in Person)
但如果絕對需要獲取所有的人名捌朴,包括重復(fù)的人名张抄,可以使用Query.without_distinct()
方法。
select(p.name for p in Person).without_distinct()
3. 使用聯(lián)接來檢索對象
select(p for p in Person for c in p.cars if c.make in ("Toyota", "Honda"))
這個查詢可能包含重復(fù)的內(nèi)容左驾,所以Pony使用DISTINCT來消除它們诡右。
SELECT DISTINCT "p"."id", "p"."name", "p"."age"
FROM "Person" "p", "Car" "c"
WHERE "c"."make" IN ('Toyota', 'Honda')
AND "p"."id" = "c"."owner"
如果不使用DISTINCT轻猖,就有可能出現(xiàn)重復(fù),因為查詢使用了兩個表(Person和Car)桅锄,但SELECT部分只使用了一個表。
上面的查詢只返回Person(而不是他們的車)翠肘,因此一般情況下辫秧,在結(jié)果中多次得到同一個人是不可取的盟戏。
我們認(rèn)為,如果沒有重復(fù)邮旷,結(jié)果看起來會更直觀蝇摸。
但如果因為某些原因不需要排除重復(fù)貌夕,你總是可以在查詢中添加without_distinct()
:
select(p for p in Person for c in p.cars
if c.make in ("Toyota", "Honda")).without_distinct()
如果查詢結(jié)果中包含每個人擁有的汽車,用戶可能希望看到Person對象重復(fù)险毁,在這種情況下们童,PONY查詢就不同了。
select((p, c) for p in Person for c in p.cars if c.make in ("Toyota", "Honda"))
并且在這種情況下跷跪,Pony不會在SQL查詢中添加DISTINCT
關(guān)鍵字完沪。
總結(jié)一下:
- "所有查詢默認(rèn)情況下不返回重復(fù)查詢 "這個原則很容易理解覆积,也不會導(dǎo)致意外。
- 在大多數(shù)情況下尉姨,這樣的行為是大多數(shù)用戶想要的吗冤。
- Pony在查詢不應(yīng)該有重復(fù)的情況下,不會添加DISTINCT覆致。
- 方法
without_distinct()
可以用來強(qiáng)制Pony不消除重復(fù)的查詢煌妈。
可以在查詢中使用的函數(shù)
下面是可以在生成器查詢中使用的函數(shù)列表:
- avg()
- abs()
- exists()
- len()
- max()
- min()
- count()
- concat()
- group_concat()
- random()
- raw_sql()
- select()
- sum()
- getattr()
例如:
select(avg(c.orders.total_price) for c in Customer)
SELECT AVG("order-1"."total_price")
FROM "Customer" "c"
LEFT JOIN "Order" "order-1"
ON "c"."id" = "order-1"."customer"
select(o for o in Order if o.customer in
select(c for c in Customer if c.name.startswith('A')))[:]
SELECT "o"."id", "o"."state", "o"."date_created", "o"."date_shipped",
"o"."date_delivered", "o"."total_price", "o"."customer"
FROM "Order" "o"
WHERE "o"."customer" IN (
SELECT "c"."id"
FROM "Customer" "c"
WHERE "c"."name" LIKE 'A%'
)
使用getattr()
getattr() 是一個內(nèi)置的Python函數(shù)璧诵,可以用來獲取屬性值:
例如:
attr_name = 'name'
param_value = 'John'
select(c for c in Customer if getattr(c, attr_name) === param_value)
使用原始SQL
Pony允許在查詢中使用原始SQL,關(guān)于如何使用原始SQL有兩種選擇族操。
- 使用
raw_sql()
函數(shù)來寫一個生成器或lambda查詢的一部分比被。 - 使用
Entity.select_by_sql()
或Entity.get_by_sql()
方法編寫一個完整的SQL查詢姐赡。
使用raw_sql()函數(shù)
讓我們來探討一下使用raw_sql()函數(shù)的例子柠掂,下面是方案及初始化數(shù)據(jù):
from datetime import date
from pony.orm import *
db = Database('sqlite', ':memory:')
class Person(db.Entity):
id = PrimaryKey(int)
name = Required(str)
age = Required(int)
dob = Required(date)
db.generate_mapping(create_tables=True)
with db_session:
Person(id=1, name='John', age=30, dob=date(1986, 1, 1))
Person(id=2, name='Mike', age=32, dob=date(1984, 5, 20))
Person(id=3, name='Mary', age=20, dob=date(1996, 2, 15))
raw_sql()
結(jié)果可以作為邏輯表達(dá)式處理:
select(p for p in Person if raw_sql('abs("p"."age") > 25'))
raw_sql()
結(jié)果可以用來進(jìn)行比較:
q = Person.select(lambda x: raw_sql('abs("x"."age")') > 25)
print(q.get_sql())
SELECT "x"."id", "x"."name", "x"."age", "x"."dob"
FROM "Person" "x"
WHERE abs("x"."age") > 25
另外涯贞,在上面的例子中,我們在一個lambda查詢中使用raw_sql()
州疾,并打印出結(jié)果的SQL皇拣。正如你所看到的,raw_sql()
部分變成了整個查詢的一部分颗胡。
raw_sql()
可以接受$parameters:
x = 25
select(p for p in Person if raw_sql('abs("p"."age") > $x'))
你可以動態(tài)地改變raw_sql()
函數(shù)的內(nèi)容毒姨,并且仍然可以使用里面的參數(shù):
x = 1
s = 'p.id > $x'
select(p for p in Person if raw_sql(s)
另一種使用動態(tài)原始SQL內(nèi)容的方式:
x = 1
cond = raw_sql('p.id > $x')
select(p for p in Person if cond)
你可以在原始SQL查詢中使用各種類型:
x = date(1990, 1, 1)
select(p for p in Person if raw_sql('p.dob < $x'))
原始SQL部分里面的參數(shù)可以組合:
x = 10
y = 15
select(p for p in Person if raw_sql('p.age > $(x + y)'))
你甚至可以在里面調(diào)用Python函數(shù):
select(p for p in Person if raw_sql('p.dob < $date.today()'))
raw_sql()
函數(shù)不僅可以用于條件部分弧呐,還可以用于返回查詢結(jié)果的部分。
names = select(raw_sql('UPPER(p.name)') for p in Person)[:]
print(names)
['John', 'Mike', 'Mary']
但是當(dāng)你使用raw_sql()
函數(shù)返回數(shù)據(jù)時腥沽,你可能需要指定結(jié)果的類型鸠蚪,因為Pony不知道結(jié)果的類型是什么:
dates = select(raw_sql('(p.dob)')) for p in Person)[:]
print(dates)
['1985-01-01', '1983-05-20', '1995-02-15']
如果你想以日期列表的形式得到結(jié)果邓嘹,你需要指定結(jié)果類型:
dates = select(raw_sql('(p.dob)', result_type=date) for p in Person)[:]
print(dates)
[datetime.date(1986, 1, 1), datetime.date(1984, 5, 20), datetime.date(1996, 2, 15)]
在Query.filter()
中也可以使用raw_sql()
函數(shù):
x = 25
select(p for p in Person).filter(lambda p: p.age > raw_sql('$x'))
它可以在Query.filter()
中使用,不需要lambda矿筝,在這種情況下棚贾,你必須使用實體名稱的首字母小寫作為別名:
x = 25
Person.select().filter(raw_sql('p.age > $x'))
你可以在一個查詢中使用多個raw_sql()
表達(dá)式:
x = '123'
y = 'John'
Person.select(lambda p: raw_sql("UPPER(p.name) || $x")
==raw_sql("UPPER($y || '123')")
同樣的參數(shù)名稱可以用不同的類型和值多次使用妙痹。
x = 10
y = 31
q = select(p for p in Person if p.age > x and p.age < raw_sql('$y'))
x = date(1980, 1, 1)
y = 'j'
q = q.filter(lambda p: p.dob > x and p.name.startwith(raw_sql('UPPER($y)')))
persons = q[:]
你可以在Query.order_by()
部分使用raw_sql()
。
x = 9
Person.select().order_by(lambda p: raw_sql('SUBSTR(p.dob, $x)'))
如果你在之前的過濾器中使用了相同的別名琳轿,則可以不使用lambda耿芹,在這種情況下吧秕,我們使用默認(rèn)的別名--實體名稱的第一個字母。
x = 9
Person.select().order_by(raw_sql('SUBSTR(p.dob, $x)'))
使用select_by_sql()和get_by_sql()方法
雖然Pony可以將幾乎所有用Python編寫的條件翻譯成SQL颠毙,但有時需要使用原始SQL砂碉,例如绽淘,為了調(diào)用存儲過程或使用特定數(shù)據(jù)庫系統(tǒng)的方言功能,需要使用原始SQL壮池。
在這種情況下,Pony允許用戶以原始SQL的形式寫一個查詢厅克,方法是把它放在函數(shù)Entity.select_by_sql()或Entity.get_by_sql()的內(nèi)部橙依,作為一個字符串。
Product.select_by_sql("SELECT * FROM Products")
與Entity.select()
方法不同女责,Entity.select_by_sql()
方法不返回查詢對象抵知,而是返回一個實體實例列表软族。
參數(shù)使用下面的語法傳遞,"(Python中的表達(dá)式)"掖疮,例如:
x = 1000
y = 500
Product.select_by_sql("SELECT * FROM Product WHERE price > $x OR price = $(y * 2)")
當(dāng)Pony在原始SQL查詢中遇到一個參數(shù)時浊闪,它會從當(dāng)前幀(來自globals和locals)或從可以作為參數(shù)傳遞的字典中獲取變量值吐葵。
Product.select_by_sql("SELECT * FROM Product WHERE price > $x OR price = $(y * 2)",
globals={'x': 100}, locals={'y': 200})
變量和更復(fù)雜的表達(dá)式在$符號后指定桥氏,將自動計算并作為參數(shù)傳輸?shù)讲樵冎凶种В@使得SQL注入成為不可能。
Pony會自動將查詢字符串中的$x用"?"揖庄、"%S "或其他參數(shù)樣式代替欠雌,在你的數(shù)據(jù)庫中使用富俄。
如果你需要在查詢中使用符號:$$们豌。