查詢運算子
peewee 支援以下類型的比較
比較 |
含義 |
---|---|
|
x 等於 y |
|
x 小於 y |
|
x 小於或等於 y |
|
x 大於 y |
|
x 大於或等於 y |
|
x 不等於 y |
|
x IN y,其中 y 是列表或查詢 |
|
x IS y,其中 y 是 None/NULL |
|
x LIKE y,其中 y 可以包含萬用字元 |
|
x ILIKE y,其中 y 可以包含萬用字元 |
|
x XOR y |
|
一元否定(例如,NOT x) |
因為我沒有更多的運算子可以覆寫,所以有一些額外的查詢操作可作為方法使用
方法 |
含義 |
---|---|
|
IN 查詢(與 |
|
NOT IN 查詢。 |
|
IS NULL 或 IS NOT NULL。接受布林參數。 |
|
針對子字串的萬用字元搜尋。 |
|
搜尋以 |
|
搜尋以 |
|
搜尋 |
|
正規表示式比對(區分大小寫)。 |
|
正規表示式比對(不區分大小寫)。 |
|
二進制 AND。 |
|
二進制 OR。 |
|
使用 |
|
標記欄位以進行 DISTINCT 選取。 |
|
使用給定的定序指定欄位。 |
|
將欄位的值轉換為給定的類型。 |
若要使用邏輯運算子組合子句,請使用
運算子 |
含義 |
範例 |
---|---|---|
|
AND |
|
|
OR |
|
|
NOT (一元否定) |
|
以下說明如何使用其中一些查詢運算子
# Find the user whose username is "charlie".
User.select().where(User.username == 'charlie')
# Find the users whose username is in [charlie, huey, mickey]
User.select().where(User.username.in_(['charlie', 'huey', 'mickey']))
# Find users whose salary is between 50k and 60k (inclusive).
Employee.select().where(Employee.salary.between(50000, 60000))
Employee.select().where(Employee.name.startswith('C'))
Blog.select().where(Blog.title.contains(search_string))
以下說明如何組合表達式。比較可以任意複雜。
注意
請注意,實際的比較都包在括號中。Python 的運算子優先順序要求比較必須包在括號中。
# Find any users who are active administrations.
User.select().where(
(User.is_admin == True) &
(User.is_active == True))
# Find any users who are either administrators or super-users.
User.select().where(
(User.is_admin == True) |
(User.is_superuser == True))
# Alternatively, use the boolean values directly. Here we query users who
# are admins and NOT superusers.
User.select().where(User.is_admin & ~User.is_superuser)
# Find any Tweets by users who are not admins (NOT IN).
admins = User.select().where(User.is_admin == True)
non_admin_tweets = Tweet.select().where(Tweet.user.not_in(admins))
# Find any users who are not my friends (strangers).
friends = User.select().where(User.username.in_(['charlie', 'huey', 'mickey']))
strangers = User.select().where(User.id.not_in(friends))
警告
雖然您可能會想在查詢表達式中使用 Python 的 in
、and
、or
、is
和 not
運算子,但這些將無法運作。in
表達式的傳回值一律會強制轉換為布林值。同樣地,and
、or
和 not
都會將其引數視為布林值,而無法多載。
所以請記住
使用
.in_()
和.not_in()
而非in
和not in
使用
&
而非and
使用
|
而非or
使用
~
而非not
使用
.is_null()
而非is None
或== None
。使用
==
和!=
來與True
和False
比較,或者您可以使用表達式的隱含值。使用邏輯運算子時,別忘了將比較包在括號中。
如需更多範例,請參閱表達式章節。
注意
SQLite 的 LIKE 和 ILIKE
因為 SQLite 的 LIKE
操作預設不區分大小寫,所以 peewee 會使用 SQLite GLOB
操作來進行區分大小寫的搜尋。glob 操作會使用星號作為萬用字元,而不是常用的百分比符號。如果您正在使用 SQLite 並且想要區分大小寫的部分字串比對,請記得使用星號作為萬用字元。
三值邏輯
因為 SQL 處理 NULL
的方式,有一些特殊的操作可用於表示
IS NULL
IS NOT NULL
IN
NOT IN
雖然可以使用 IS NULL
和 IN
運算子以及否定運算子 (~
),但有時為了獲得正確的語意,您需要明確地使用 IS NOT NULL
和 NOT IN
。
使用 IS NULL
和 IN
的最簡單方式是使用運算子多載
# Get all User objects whose last login is NULL.
User.select().where(User.last_login >> None)
# Get users whose username is in the given list.
usernames = ['charlie', 'huey', 'mickey']
User.select().where(User.username << usernames)
如果您不喜歡運算子多載,您可以改為呼叫欄位方法
# Get all User objects whose last login is NULL.
User.select().where(User.last_login.is_null(True))
# Get users whose username is in the given list.
usernames = ['charlie', 'huey', 'mickey']
User.select().where(User.username.in_(usernames))
若要否定上述查詢,您可以使用一元否定,但為了獲得正確的語意,您可能需要使用特殊的 IS NOT
和 NOT IN
運算子
# Get all User objects whose last login is *NOT* NULL.
User.select().where(User.last_login.is_null(False))
# Using unary negation instead.
User.select().where(~(User.last_login >> None))
# Get users whose username is *NOT* in the given list.
usernames = ['charlie', 'huey', 'mickey']
User.select().where(User.username.not_in(usernames))
# Using unary negation instead.
usernames = ['charlie', 'huey', 'mickey']
User.select().where(~(User.username << usernames))
新增使用者定義的運算子
因為我沒有更多的 Python 運算子可以多載,所以 peewee 中有一些遺失的運算子,例如 modulo
。如果您發現您需要支援上方表格中沒有的運算子,您可以很輕鬆地新增自己的運算子。
以下說明如何新增對 SQLite 中 modulo
的支援
from peewee import *
from peewee import Expression # The building block for expressions.
def mod(lhs, rhs):
# Note: this works with Sqlite, but some drivers may use string-
# formatting before sending the query to the database, so you may
# need to use '%%' instead here.
return Expression(lhs, '%', rhs)
現在您可以使用這些自訂運算子來建構更豐富的查詢
# Users with even ids.
User.select().where(mod(User.id, 2) == 0)
如需更多範例,請查看 playhouse.postgresql_ext
模組的原始碼,因為其中包含許多特定於 postgresql 的 hstore 的運算子。
表達式
Peewee 的設計目的是提供一種簡單、具表達力且 Python 式的方式來建構 SQL 查詢。本節將快速概述一些常見的表達式類型。
有兩種主要類型的物件可以組合以建立表達式
我們將假設一個簡單的「使用者」模型,其中包含使用者名稱和其他項目的欄位。它看起來像這樣
class User(Model):
username = CharField()
is_admin = BooleanField()
is_active = BooleanField()
last_login = DateTimeField()
login_count = IntegerField()
failed_logins = IntegerField()
比較會使用查詢運算子
# username is equal to 'charlie'
User.username == 'charlie'
# user has logged in less than 5 times
User.login_count < 5
可以使用位元and 和 or 組合比較。運算子優先順序由 Python 控制,比較可以巢狀到任意深度
# User is both and admin and has logged in today
(User.is_admin == True) & (User.last_login >= today)
# User's username is either charlie or charles
(User.username == 'charlie') | (User.username == 'charles')
# User is active and not a superuser.
(User.is_active & ~User.is_superuser)
比較也可以與函數一起使用
# user's username starts with a 'g' or a 'G':
fn.Lower(fn.Substr(User.username, 1, 1)) == 'g'
我們可以做一些相當有趣的事情,因為表達式可以與其他表達式比較。表達式也支援算術運算
# users who entered the incorrect more than half the time and have logged
# in at least 10 times
(User.failed_logins > (User.login_count * .5)) & (User.login_count > 10)
表達式允許我們執行原子更新
# when a user logs in we want to increment their login count:
User.update(login_count=User.login_count + 1).where(User.id == user_id)
表達式可以用於查詢的所有部分,因此請嘗試看看!
列值
許多資料庫都支援列值,這與 Python tuple 物件類似。在 Peewee 中,可以使用 Tuple
在表達式中使用列值。例如,
# If for some reason your schema stores dates in separate columns ("year",
# "month" and "day"), you can use row-values to find all rows that happened
# in a given month:
Tuple(Event.year, Event.month) == (2019, 1)
列值更常見的用途是與單一表達式中子查詢的多個欄位比較。還有其他方法可以表達這些類型的查詢,但列值可能提供簡潔易讀的方法。
例如,假設我們有一個表格「EventLog」,其中包含事件類型、事件來源和一些中繼資料。我們還有一個「IncidentLog」,其中包含事件類型、事件來源和中繼資料欄位。我們可以使用列值將事件與特定事件相關聯
class EventLog(Model):
event_type = TextField()
source = TextField()
data = TextField()
timestamp = TimestampField()
class IncidentLog(Model):
incident_type = TextField()
source = TextField()
traceback = TextField()
timestamp = TimestampField()
# Get a list of all the incident types and sources that have occured today.
incidents = (IncidentLog
.select(IncidentLog.incident_type, IncidentLog.source)
.where(IncidentLog.timestamp >= datetime.date.today()))
# Find all events that correlate with the type and source of the
# incidents that occured today.
events = (EventLog
.select()
.where(Tuple(EventLog.event_type, EventLog.source).in_(incidents))
.order_by(EventLog.timestamp))
表達此類型查詢的其他方式是使用 聯結 或 聯結子查詢。上面的範例只是為了讓您了解如何使用 Tuple
。
當新資料衍生自子查詢時,您也可以使用列值來更新表格中的多個欄位。如需範例,請參閱此處。
SQL 函數
可以使用 fn()
輔助程式來表示 SQL 函數,例如 COUNT()
或 SUM()
# Get all users and the number of tweets they've authored. Sort the
# results from most tweets -> fewest tweets.
query = (User
.select(User, fn.COUNT(Tweet.id).alias('tweet_count'))
.join(Tweet, JOIN.LEFT_OUTER)
.group_by(User)
.order_by(fn.COUNT(Tweet.id).desc()))
for user in query:
print('%s -- %s tweets' % (user.username, user.tweet_count))
fn
輔助程式會將任何 SQL 函數公開為如同方法一般。參數可以是欄位、值、子查詢,甚至是巢狀函數。
巢狀函數呼叫
假設您需要取得所有使用者名稱以a開頭的使用者列表。有很多方法可以做到這一點,但其中一種方法可能是使用一些 SQL 函式,例如 LOWER 和 SUBSTR。若要使用任意 SQL 函式,請使用特殊的 fn()
物件來建構查詢。
# Select the user's id, username and the first letter of their username, lower-cased
first_letter = fn.LOWER(fn.SUBSTR(User.username, 1, 1))
query = User.select(User, first_letter.alias('first_letter'))
# Alternatively we could select only users whose username begins with 'a'
a_users = User.select().where(first_letter == 'a')
>>> for user in a_users:
... print(user.username)
SQL 輔助工具
有時候您可能只想直接傳入一些任意的 SQL。您可以使用特殊的 SQL
類別來做到這一點。其中一個使用案例是當您引用別名時。
# We'll query the user table and annotate it with a count of tweets for
# the given user
query = (User
.select(User, fn.Count(Tweet.id).alias('ct'))
.join(Tweet)
.group_by(User))
# Now we will order by the count, which was aliased to "ct"
query = query.order_by(SQL('ct'))
# You could, of course, also write this as:
query = query.order_by(fn.COUNT(Tweet.id))
有兩種方法可以使用 peewee 執行手動撰寫的 SQL 語句:
使用
Database.execute_sql()
來執行任何類型的查詢。使用
RawQuery
來執行SELECT
查詢並傳回模型實例。
安全性與 SQL 注入
預設情況下,peewee 會參數化查詢,因此使用者傳入的任何參數都會被跳脫。此規則的唯一例外是當您撰寫原始 SQL 查詢或傳入可能包含不受信任資料的 SQL
物件時。為了減輕這種情況,請確保任何使用者定義的資料都作為查詢參數傳入,而不是實際 SQL 查詢的一部分。
# Bad! DO NOT DO THIS!
query = MyModel.raw('SELECT * FROM my_table WHERE data = %s' % (user_data,))
# Good. `user_data` will be treated as a parameter to the query.
query = MyModel.raw('SELECT * FROM my_table WHERE data = %s', user_data)
# Bad! DO NOT DO THIS!
query = MyModel.select().where(SQL('Some SQL expression %s' % user_data))
# Good. `user_data` will be treated as a parameter.
query = MyModel.select().where(SQL('Some SQL expression %s', user_data))
注意
MySQL 和 Postgresql 使用 '%s'
來表示參數。另一方面,SQLite 使用 '?'
。請務必使用適合您資料庫的字元。您也可以透過檢查 Database.param
來找到此參數。