查詢運算子

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_(value)

IN 查詢(與 << 相同)。

.not_in(value)

NOT IN 查詢。

.is_null(is_null)

IS NULL 或 IS NOT NULL。接受布林參數。

.contains(substr)

針對子字串的萬用字元搜尋。

.startswith(prefix)

搜尋以 prefix 開頭的值。

.endswith(suffix)

搜尋以 suffix 結尾的值。

.between(low, high)

搜尋 low <= value <= high 的地方。

.regexp(exp)

正規表示式比對(區分大小寫)。

.iregexp(exp)

正規表示式比對(不區分大小寫)。

.bin_and(value)

二進制 AND。

.bin_or(value)

二進制 OR。

.concat(other)

使用 || 連接兩個字串或物件。

.distinct()

標記欄位以進行 DISTINCT 選取。

.collate(collation)

使用給定的定序指定欄位。

.cast(type)

將欄位的值轉換為給定的類型。

若要使用邏輯運算子組合子句,請使用

運算子

含義

範例

&

AND

(User.is_active == True) & (User.is_admin == True)

| (管道)

OR

(User.is_admin) | (User.is_superuser)

~

NOT (一元否定)

~(User.username.contains('admin'))

以下說明如何使用其中一些查詢運算子

# 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 的 inandorisnot 運算子,但這些將無法運作in 表達式的傳回值一律會強制轉換為布林值。同樣地,andornot 都會將其引數視為布林值,而無法多載。

所以請記住

  • 使用 .in_().not_in() 而非 innot in

  • 使用 & 而非 and

  • 使用 | 而非 or

  • 使用 ~ 而非 not

  • 使用 .is_null() 而非 is None== None

  • 使用 ==!= 來與 TrueFalse 比較,或者您可以使用表達式的隱含值。

  • 使用邏輯運算子時,別忘了將比較包在括號中。

如需更多範例,請參閱表達式章節。

注意

SQLite 的 LIKE 和 ILIKE

因為 SQLite 的 LIKE 操作預設不區分大小寫,所以 peewee 會使用 SQLite GLOB 操作來進行區分大小寫的搜尋。glob 操作會使用星號作為萬用字元,而不是常用的百分比符號。如果您正在使用 SQLite 並且想要區分大小寫的部分字串比對,請記得使用星號作為萬用字元。

三值邏輯

因為 SQL 處理 NULL 的方式,有一些特殊的操作可用於表示

  • IS NULL

  • IS NOT NULL

  • IN

  • NOT IN

雖然可以使用 IS NULLIN 運算子以及否定運算子 (~),但有時為了獲得正確的語意,您需要明確地使用 IS NOT NULLNOT IN

使用 IS NULLIN 的最簡單方式是使用運算子多載

# 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 NOTNOT 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 查詢。本節將快速概述一些常見的表達式類型。

有兩種主要類型的物件可以組合以建立表達式

  • Field 執行個體

  • 使用 fn 的 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

可以使用位元andor 組合比較。運算子優先順序由 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 函式,例如 LOWERSUBSTR。若要使用任意 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 語句:

  1. 使用 Database.execute_sql() 來執行任何類型的查詢。

  2. 使用 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 來找到此參數。