peewee用法考察

[TOC]


peewee

最近嘗試使用tornado。由于tornado組件較為簡潔,還需引入一些其他類庫結(jié)合使用。在ORM方面,我選擇了輕量小巧的peewee。本文即對peewee的用法進行初步考察,記錄自用。

閱讀原文體驗更佳

主要參考內(nèi)容:
peewee官方文檔

考察目標

peewee支持多種數(shù)據(jù)庫,而本文的考察環(huán)境是mysql,存儲引擎為InnoDB。
常用的sql操作可簡單分為CURD。我平常工作使用的功能細分如下:

  • Create:單條插入、批量插入、批量塊插入、獲取自增pk
  • Update:數(shù)據(jù)更新
  • Retrieve:單條查詢、多條查詢、指定字段單條/多條查詢、關(guān)聯(lián)查詢(join、union)、聚合查詢
  • Delete:單條刪除、批量刪除
  • 其他功能:事務(wù)、原生sql查詢

前置準備

在mysql中新建表

本文使用數(shù)據(jù)庫名為peewee,三張表分別是user、balance和integral,結(jié)構(gòu)如下:

CREATE TABLE `user` (
  `uid` int(11) NOT NULL AUTO_INCREMENT COMMENT '用戶編號',
  `avartar` varchar(200) NOT NULL COMMENT '頭像',
  `uname` varchar(50) NOT NULL COMMENT '用戶名',
  `gender` char(1) NOT NULL DEFAULT '0' COMMENT '性別 0-保密|1-男|2-女',
  `password` varchar(200) NOT NULL COMMENT '密碼',
  `balance` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '余額',
  `integral` int(11) NOT NULL DEFAULT '0' COMMENT '積分',
  `logintime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '最后登錄時間',
  `addtime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '注冊時間',
  PRIMARY KEY (`uid`),
  UNIQUE KEY `uname` (`uname`) COMMENT '用戶名唯一'
) ENGINE=InnoDB AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8 COMMENT='用戶信息'
CREATE TABLE `balance` (
  `b_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '操作編號',
  `uid` int(11) NOT NULL DEFAULT '0' COMMENT '用戶編號',
  `change` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '變動金額',
  `total` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '剩余金額',
  `addtime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作時間',
  PRIMARY KEY (`b_id`),
  KEY `uid` (`uid`) COMMENT '用戶編號'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='余額操作記錄表'
CREATE TABLE `integral` (
  `i_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '操作編號',
  `uid` int(11) NOT NULL DEFAULT '0' COMMENT '用戶編號',
  `change` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '變動積分',
  `total` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '剩余積分',
  `addtime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作時間',
  PRIMARY KEY (`i_id`),
  KEY `uid` (`uid`) COMMENT '用戶編號'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='積分操作記錄表'

python連接mysql,需要pymysql或mysql-connector-python。

pip install pymysql peewee

生成映射模型

peewee分別提供根據(jù)模型生成表根據(jù)表生成模型的功能。但peewee等orm要考慮兼容多種數(shù)據(jù)庫,因此模型生成表的過程中只能使用所有數(shù)據(jù)庫公有的字段類型,不能使用mysql的tinyint、char等類型,所以不考慮根據(jù)模型生成表。使用pwiz命令生成模型:

python -m pwiz -e mysql -H localhost -p3306 -uroot -P peewee > models.py

獲得以下model文件:

# -*- coding: utf-8 -*-
# models.py

from peewee import *

database = MySQLDatabase('peewee', **{'charset': 'utf8', 'use_unicode': True, 'host': 'localhost', 'port': 3306, 'user': 'root', 'password': ''})


class UnknownField(object):
    def __init__(self, *_, **__): pass


class BaseModel(Model):
    class Meta:
        database = database


class Balance(BaseModel):
    addtime = DateTimeField(constraints=[SQL("DEFAULT CURRENT_TIMESTAMP")])
    b = AutoField(column_name='b_id')
    change = DecimalField(constraints=[SQL("DEFAULT 0.00")])
    total = DecimalField(constraints=[SQL("DEFAULT 0.00")])
    uid = IntegerField(constraints=[SQL("DEFAULT 0")], index=True)

    class Meta:
        table_name = 'balance'


class Integral(BaseModel):
    addtime = DateTimeField(constraints=[SQL("DEFAULT CURRENT_TIMESTAMP")])
    change = DecimalField(constraints=[SQL("DEFAULT 0.00")])
    i = AutoField(column_name='i_id')
    total = DecimalField(constraints=[SQL("DEFAULT 0.00")])
    uid = IntegerField(constraints=[SQL("DEFAULT 0")], index=True)

    class Meta:
        table_name = 'integral'


class User(BaseModel):
    addtime = DateTimeField(constraints=[SQL("DEFAULT CURRENT_TIMESTAMP")])
    avartar = CharField()
    balance = DecimalField(constraints=[SQL("DEFAULT 0.00")])
    gender = CharField(constraints=[SQL("DEFAULT '0'")])
    integral = IntegerField(constraints=[SQL("DEFAULT 0")])
    logintime = DateTimeField(constraints=[SQL("DEFAULT CURRENT_TIMESTAMP")])
    password = CharField()
    uid = AutoField()
    uname = CharField(unique=True)

    class Meta:
        table_name = 'user'

Create

新建一個peewee_test.py文件,引入models,使用logging類庫觀察執(zhí)行的sql語句。

第一種添加方法

# -*- coding: utf-8 -*-
# peewee_test.py

from models import *
import sys, logging, hashlib

# 使用日志觀察執(zhí)行的sql語句
logger = logging.getLogger('peewee')
logger.setLevel(logging.DEBUG)
logger.addHandler(logging.StreamHandler())


def create():
    """md5加密密碼"""
    m = hashlib.md5()
    m.update('123456'.encode())
    pwd = m.hexdigest()
    """頭像"""
    avartar = 'https://avatars2.githubusercontent.com/u/25029451'
    """第一種添加方法"""
    user = User(
        avartar=avartar, 
        uname='John', gender='1', password=pwd
    )
    uid = user.save()
    print('uid=%d' % uid)


if __name__ == '__main__':
    sys.exit(create())

運行peewee_test.py,查看控制臺輸出信息,我們得知第一種添加方法不會返回插入的自增pk(此時自增pk為uid=10000),而是成功返回1,失敗返回0;

python peewee_test.py
('INSERT INTO `user` (`avartar`, `gender`, `password`, `uname`) VALUES (%s, %s, %s, %s)', ['https://avatars2.githubusercontent.com/u/25029451', '1', 'e10adc3949ba59abbe56e057f20f883e', 'John'])
uid=1

Process finished with exit code 0

第二種添加方法

def create():
    """第二種添加方法"""
    uid = User.create(
        avartar=avartar,
        uname='Jack', gender='1', password=pwd
    )
    print('uid=%s' % uid)

注意第二種方法中我打印uid使用格式化操作符是%s而不是%d,因為返回值是一個User對象
查看控制臺輸出信息,可知第二種方法會返回自增pk;

python peewee_test.py
('INSERT INTO `user` (`avartar`, `gender`, `password`, `uname`) VALUES (%s, %s, %s, %s)', ['https://avatars2.githubusercontent.com/u/25029451', '1', 'e10adc3949ba59abbe56e057f20f883e', 'Jack'])
uid=10001

Process finished with exit code 0

第三種添加方法

def create():
    """第三種添加方法"""
    uid = User.insert(
        avartar=avartar,
        uname='Micheal', gender='1', password=pwd
    ).execute()
    print('uid=%d' % uid)

查看控制臺輸出信息,第三種方法也會返回自增pk。

python peewee_test.py
('INSERT INTO `user` (`avartar`, `gender`, `password`, `uname`) VALUES (%s, %s, %s, %s)', ['https://avatars2.githubusercontent.com/u/25029451', '1', 'e10adc3949ba59abbe56e057f20f883e', 'Micheal'])
uid=10002

Process finished with exit code 0

第一種需要調(diào)用save()方法才能插入數(shù)據(jù),而第二種不需要調(diào)用其他方法,第三種則調(diào)用execute()方法進行插入。第二種方法返回的是對象,第三種方法返回的是整型。工作中我經(jīng)常需要在后續(xù)代碼中使用到自增pk,因此我將采用insert()方法進行添加操作。

批量添加方法

使用insert_many批量添加數(shù)據(jù):在插入多條數(shù)據(jù)時,根據(jù)文檔描述,create()方法每次都會開啟事務(wù)(如果存儲引擎支持),雖然可以遍歷方式用上述方法逐條插入,但數(shù)據(jù)量一大就會導(dǎo)致效率低下,所以有必要進行批量插入優(yōu)化。

If you are not wrapping the loop in a transaction then each call to create() happens in its own transaction. That is going to be really slow!

def create():
    """批量添加數(shù)據(jù)"""
    data_source = [
        (avartar, 'Catherine', '2', pwd),
        (avartar, 'Jane', '2', pwd),
        (avartar 'Mary', '2', pwd),
    ]
    field = [User.avartar, User.uname, User.gender, User.password]
    uid = User.insert_many(data_source, field).execute()
    print('uid=%d' % uid)

控制臺輸出返回批量插入過程中第一條數(shù)據(jù)的自增pk;

python peewee_test.py
('INSERT INTO `user` (`avartar`, `uname`, `gender`, `password`) VALUES (%s, %s, %s, %s), (%s, %s, %s, %s), (%s, %s, %s, %s)', ['https://avatars2.githubusercontent.com/u/25029451', 'Catherine', '2', 'e10adc3949ba59abbe56e057f20f883e', 'https://avatars2.githubusercontent.com/u/25029451', 'Jane', '2', 'e10adc3949ba59abbe56e057f20f883e', 'https://avatars2.githubusercontent.com/u/25029451', 'Mary', '2', 'e10adc3949ba59abbe56e057f20f883e'])
uid=10003

Process finished with exit code 0

批量塊添加方法

如果要插入的數(shù)據(jù)量非常大,比如有千萬條。一次性批量插入將會導(dǎo)致卡死,逐條插入也會導(dǎo)致耗時過長。此時可以折中尋求一個分批次批量插入的方案:使用peewee的chunked(it, n)方法將數(shù)據(jù)分割成小塊數(shù)據(jù)(比如每次1000條,chunked(data_source, 1000))分批次批量插入:

def create():
    """批量塊添加數(shù)據(jù)"""
    data_source = [
        (avartar, 'Zoe', '2', pwd),
        (avartar, 'Lucy', '2', pwd),
        (avartar, 'Kara', '2', pwd),
        (avartar, 'Rex', '1', pwd),
    ]
    field = [User.avartar, User.uname, User.gender, User.password]
    for data_chunk in chunked(data_source, 2):
        User.insert_many(data_chunk, field).execute()

控制臺輸出的信息表明了chunked的實質(zhì):

python peewee_test.py
('INSERT INTO `user` (`avartar`, `uname`, `gender`, `password`) VALUES (%s, %s, %s, %s), (%s, %s, %s, %s)', ['https://avatars2.githubusercontent.com/u/25029451', 'Zoe', '2', 'e10adc3949ba59abbe56e057f20f883e', 'https://avatars2.githubusercontent.com/u/25029451', 'Lucy', '2', 'e10adc3949ba59abbe56e057f20f883e'])
('INSERT INTO `user` (`avartar`, `uname`, `gender`, `password`) VALUES (%s, %s, %s, %s), (%s, %s, %s, %s)', ['https://avatars2.githubusercontent.com/u/25029451', 'Kara', '2', 'e10adc3949ba59abbe56e057f20f883e', 'https://avatars2.githubusercontent.com/u/25029451', 'Rex', '1', 'e10adc3949ba59abbe56e057f20f883e'])

Process finished with exit code 0

Retrieve(1)

讀取單條數(shù)據(jù)

直接調(diào)用get()方法,可以添加過濾(filter)條件。查詢結(jié)果是一個User對象,直接打印得到pk,通過調(diào)用成員變量可以獲得對應(yīng)字段的值。特別的,當篩選條件為pk時,可以使用get_by_id()方法直接查詢:

def retrieve():
    """讀取一條數(shù)據(jù)"""
    # result = User.get_by_id(10000)
    result = User.get(User.uname == 'Jack')
    print('直接打印:%s' % result)
    """查看結(jié)構(gòu)"""
    print('\n'.join(['%s:%s' % item for item in result.__data__.items()]))
    print('用戶名為:%s' % result.uname)

控制臺輸出:

python peewee_test.py
('SELECT `t1`.`uid`, `t1`.`addtime`, `t1`.`avartar`, `t1`.`balance`, `t1`.`gender`, `t1`.`integral`, `t1`.`logintime`, `t1`.`password`, `t1`.`uname` FROM `user` AS `t1` WHERE (`t1`.`uname` = %s) LIMIT %s OFFSET %s', ['Jack', 1, 0])
直接打?。?0001
uid:10001
addtime:2018-12-12 10:22:53
avartar:https://avatars2.githubusercontent.com/u/25029451
balance:0.00
gender:1
integral:0
logintime:2018-12-12 10:22:53
password:e10adc3949ba59abbe56e057f20f883e
uname:Jack
用戶名為:Jack

Process finished with exit code 0

使用get()方法查詢出來的數(shù)據(jù)包含了所有的字段。那么,當我們的需求是查詢單條數(shù)據(jù)中的限定字段(fields)時,應(yīng)該怎么查詢呢?閱讀get()方法的源碼可以得知,其調(diào)用了select()方法返回的ModelSelect類中的get()方法:

# peewee.py
@classmethod
def get(cls, *query, **filters):
    sq = cls.select()
    if query:
        sq = sq.where(*query)
    if filters:
        sq = sq.filter(**filters)
    return sq.get()

因此,我們可以直接調(diào)用select()方法,然后調(diào)用get()方法,就可以實現(xiàn)需求。

def retrieve():
    """查詢一條數(shù)據(jù)中的限定字段(uname)"""
    result = User.select(User.uname).where(User.uid == 10001).get()
    """查看結(jié)構(gòu)"""
    print('\n'.join(['%s:%s' % item for item in result.__data__.items()]))

事實上,很多時候工作中遇到的單一查詢都是只需要其中幾個字段的數(shù)據(jù),因此比起get()我用的更多的將會是select().get()。

python peewee_test.py
('SELECT `t1`.`uname` FROM `user` AS `t1` WHERE (`t1`.`uid` = %s) LIMIT %s OFFSET %s', [10001, 1, 0])
uname:Jack

Process finished with exit code 0

讀取多條數(shù)據(jù)

讀取多條數(shù)據(jù)時,就要用到剛才提到的select()方法,這也是查詢使用的主要方法。查閱select()方法源碼可知,其返回一個ModelSelect類,然后我們再調(diào)用這個類的各種方法(where()、order_by()、limit()、offset())來實現(xiàn)查詢的條件限定。如果你有特殊的需求,可以在鏈式操作的最后調(diào)用dicts()方法,將結(jié)果轉(zhuǎn)化為一個字典。例如,我們要查詢性別為男性、按注冊日期降序(升序?qū)懽侄危敌蛟谧侄吻懊嬖偌觽€負號)排列的第二和第三位用戶的編號和名稱

def retrieve():
    """讀取多條數(shù)據(jù)"""
    query = User.select(User.uname, User.uid).order_by(-User.addtime).limit(2).offset(1).where(User.gender == '1')
    for row in query:
        print('%d:%s' % (row.uid, row.uname))
    """轉(zhuǎn)換為字典形式"""
    print('轉(zhuǎn)為字典形式:')
    query = User.select(User.uname, User.uid).order_by(-User.addtime).limit(2).offset(1).where(User.gender == '1').dicts()
    for row in query:
        print(row)
python peewee_test.py
('SELECT `t1`.`uname`, `t1`.`uid` FROM `user` AS `t1` WHERE (`t1`.`gender` = %s) ORDER BY `t1`.`addtime` DESC LIMIT %s OFFSET %s', ['1', 2, 1])
('SELECT `t1`.`uname`, `t1`.`uid` FROM `user` AS `t1` WHERE (`t1`.`gender` = %s) ORDER BY `t1`.`addtime` DESC LIMIT %s OFFSET %s', ['1', 2, 1])
10002:Micheal
10001:Jack
轉(zhuǎn)為字典形式:
{'uname': 'Micheal', 'uid': 10002}
{'uname': 'Jack', 'uid': 10001}

Process finished with exit code 0

查詢表達式

根據(jù)文檔,peewee的查詢操作符由以下組成:

比較符號 意義
== x等于y
< x小于y
<= x小于等于y
> x大于y
>= x大于等于
!= x不等于y
<< x在數(shù)組y中
>> x是y,這個y是None或者Null
% x包含在y中
** 忽略大小寫的x包含在y中
^ x異或y
~ 非x

舉例,模糊查詢用戶名包含J的用戶:

def retrieve():
    """模糊查詢用戶名包含J的用戶"""
    # 包含J結(jié)尾的查詢條件
    # query = User.select(User.uname, User.uid).where(User.uname % '%J').dicts()
    # 包含J的查詢條件
    # query = User.select(User.uname, User.uid).where(User.uname % '%J%').dicts()
    # 包含J開頭的查詢條件
    query = User.select(User.uname, User.uid).where(User.uname % 'J%').dicts()
    for row in query:
        print(row)

我們應(yīng)該知道,模糊查詢的時候使用右模糊查詢可以有效地利用索引加快查詢速度。

python peewee_test.py
('SELECT `t1`.`uname`, `t1`.`uid` FROM `user` AS `t1` WHERE (`t1`.`uname` LIKE BINARY %s)', ['J%'])
{'uname': 'Jack', 'uid': 10001}
{'uname': 'Jane', 'uid': 10004}
{'uname': 'John', 'uid': 10000}

Process finished with exit code 0

當然,因為操作符有限,無法表達全部功能,所以peewee同時也提供了方法用于代替操作符:

方法 意義
.in_(value) x在數(shù)組y中
.not_in(value x不在數(shù)組y中
.is_null(is_null) 判斷是否為空,接受布爾值
.contains(substr) 包含字符串,忽略大小寫
.startswith(prefix) 以字符串開頭,忽略大小寫
.endswith(suffix) 以字符串結(jié)尾,忽略大小寫
.between(low, high) 在兩個值的范圍內(nèi)
.regexp(exp) 匹配正則表達式(大小寫敏感)
.iregexp(exp) 匹配正則表達式,忽略大小寫
.bin_and(value) 二進制的和
.bin_or(value) 二進制的或
.concat(other) 將兩個字符串或?qū)ο笈R時合并
.distinct() 指定字段過濾重復(fù)記錄
.collate(collation) 對列進行指定規(guī)則排序
.cast(type) 將列的值按照指定類型轉(zhuǎn)變

還是上面的查詢例子,等效用方法進行查詢可改為:

# 包含J結(jié)尾的查詢條件
# query = User.select(User.uname, User.uid).where(User.uname.endswith('j')).dicts()
# 包含J的查詢條件
# query = User.select(User.uname, User.uid).where(User.uname.contains('j')).dicts()
# 包含J開頭的查詢條件
query = User.select(User.uname, User.uid).where(User.uname.startswith('j')).dicts()

以及連接多個條件的邏輯運算符:

符號 意義
&
|
~

Update

第一種更新方法

和添加操作一樣,更新也有多種方法。當條件中包含了pk時,調(diào)用save()方法就是更新:

def update():
    """第一種更新方法"""
    user = User(gender='1')
    user.uid = 1006
    user.save()
python peewee_test.py
('UPDATE `user` SET `gender` = %s WHERE (`uid` = %s)', ['1', 1006])

Process finished with exit code 0

第二種更新方法

同insert(),改成update()就可以了,比第一種方法優(yōu)越在于可以使用其他字段進行條件限定:

def update():
    """第二種更新方法"""
    User.update(
        gender='2'
    ).where(User.uname == 'Zoe').execute()
python peewee_test.py
('UPDATE `user` SET `gender` = %s WHERE (`uname` = %s)', ['2', 'Zoe'])

Process finished with exit code 0

Transaction

掌握了最基本的CUR操作后,我們終于可以進行一些更為高級的操作。比如很重要很常用并且是InnoDB比MyISAM更為優(yōu)越的功能:事務(wù)操作。peewee提供了兩種事務(wù)開啟的方法,這里只用第二種。
首先,我們可以在models.py這個文件的BaseModel類中添加一個trans()方法,便于后面開啟事務(wù):

# models.py
class BaseModel(Model):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.trans = database.atomic

    class Meta:
        database = database

開啟事務(wù)的方法

def transaction():
    user = User()
    """開啟事務(wù)"""
    with user.trans():
        pass

在python中使用with開啟的好處在于如果原子操作執(zhí)行成功它可以自動提交、失敗會自動回滾,不需要程序員手寫提交和回滾代碼,除此之外,視覺上更為優(yōu)雅(pythonic)。當然如果你更習(xí)慣于完全手寫也可以使用database.manual_commit()的方法來開啟手動提交回滾操作。

應(yīng)用情景

名為Jack的用戶添加100.00余額,同時需要在余額操作記錄表上插入一條記錄。兩個操作必須全部成功,否則全部失敗。

def transaction():
    user = User()
    result = user.select(User.uid, User.balance).where(User.uname == 'Jack').get()  # 查詢用戶的現(xiàn)有余額以及用戶編號
    """開啟事務(wù)"""
    with user.trans():
        try:
            user.update(
                balance=result.balance+100
            ).where(User.uid == result.uid).execute()
            Balance.insert(
                uid=result.uid,
                change=100,
                total=result.balance+100
            ).execute()
        except Exception:
            print('更新失敗')
        else:
            print('更新成功')

如果其中有一步操作失敗,則整個事務(wù)自動回滾。

python peewee_test.py
('SELECT `t1`.`uid`, `t1`.`balance` FROM `user` AS `t1` WHERE (`t1`.`uname` = %s) LIMIT %s OFFSET %s', ['Jack', 1, 0])
('UPDATE `user` SET `balance` = %s WHERE (`uid` = %s)', [Decimal('100.00'), 10001])
('INSERT INTO `balance` (`change`, `total`, `uid`) VALUES (%s, %s, %s)', [100, Decimal('100.00'), 10001])
更新成功

Process finished with exit code 0

Retrieve(2)

join

當我們查看操作記錄的時候,往往需要用戶信息和操作記錄組合起來,也就是mysql的join操作。例如,我們要時間降序查詢用戶編號為10001的用戶的余額變更記錄

def retrieve2():
    """join查詢"""
    query = Balance.select(
        User.uname, Balance.change,
        Balance.total, Balance.addtime
    ).join(
        User, JOIN.RIGHT_OUTER,
        on=(Balance.uid == User.uid)
    ).where(
        Balance.uid == 10001
    ).order_by(-Balance.addtime)
    for row in query:
        print('%s:%.2f:%.2f:%s' % (
            row.user.uname, row.change, row.total, row.addtime
        ))
python peewee_test.py
('SELECT `t1`.`uname`, `t2`.`change`, `t2`.`total`, `t2`.`addtime` FROM `balance` AS `t2` RIGHT OUTER JOIN `user` AS `t1` ON (`t1`.`uid` = `t2`.`uid`) WHERE (`t2`.`uid` = %s) ORDER BY `t2`.`addtime` DESC', [10001])
Jack:100.00:200.00:2018-12-12 17:24:32
Jack:100.00:100.00:2018-12-12 16:31:15

Process finished with exit code 0

union[ all]

關(guān)于union的操作姿勢,官方文檔也提供了多種方式,包括操作符、方法等等。

操作符 意義
| union
+ union all

當我們需要把余額操作記錄和積分操作記錄并作操作記錄統(tǒng)計時間降序查看第二和第三條數(shù)據(jù)(盡管這意義不大,且實際上我本來是想用訂單統(tǒng)計來作為示例)的時候,union|union all就可以派上用場:

def retrieve2():
    """union_all查詢"""
    b_query = (Balance.select(
        Balance.b.alias('id'), Balance.change,
        Balance.total, Balance.addtime
    ))
    i_query = (Integral.select(
        Integral.i.alias('id'), Integral.change,
        Integral.total, Integral.addtime
    ))
    query = (b_query + i_query).order_by(SQL('addtime desc')).limit(2).offset(1)
    # query = b_query.union_all(i_query).order_by(SQL('addtime desc')).limit(2).offset(1)
    for row in query:
        print('%d:%.2f:%.2f:%s' % (row.id, row.change, row.total, row.addtime))

沒錯,union_all的操作看起來仿佛將兩個元組連接一樣(實際上源碼 __add__ = union_all)。如果對此感到不習(xí)慣,也可以用方法union_all()來代替。值得注意的是,在方法order_by()中,不能直接寫入'addtime desc'這樣的字符串,因為peewee不會將其解析為sql語句。你需要使用SQL類將其轉(zhuǎn)化為sql語句才能使排序生效(參考自StackOverflow)。

python peewee_test.py
('SELECT `t1`.`b_id` AS `id`, `t1`.`change`, `t1`.`total`, `t1`.`addtime` FROM `balance` AS `t1` UNION ALL SELECT `t2`.`i_id` AS `id`, `t2`.`change`, `t2`.`total`, `t2`.`addtime` FROM `integral` AS `t2` ORDER BY addtime desc LIMIT %s OFFSET %s', [2, 1])
1:300.00:300.00:2018-12-13 10:16:27
4:100.00:200.00:2018-12-12 17:24:32

Process finished with exit code 0

聚合查詢

聚合查詢包括count、max、min、avg、sum。

count

def count():
    """統(tǒng)計用戶個數(shù)"""
    count = User.select().count()
    print('用戶數(shù)量:%d' % count)
    """fn.COUNT用法"""
    fn_count = User.select(fn.COUNT(User.uid)).scalar()
    print('fn.COUNT:%d' % fn_count)

max

def max():
    fn_max = User.select(fn.MAX(User.balance)).scalar()

min

def min():
    fn_min = User.select(fn.MIN(User.balance)).scalar()

avg

def avg():
    fn_avg = User.select(fn.AVG(User.balance)).scalar()

sum

def sum():
    fn_sum = Balance.select(fn.SUM(Balance.change)).where(Balance.uid == 10001).scalar()

Delete

刪除調(diào)用delete()方法,使用execute()執(zhí)行

def delete():
    result = User.delete().where(User.uid == 10009).execute()
    print(result)
    result2 = User.delete().where(User.uid << (10006, 10007, 10008, 10009)).execute()
    print(result2)

返回值為成功刪除的數(shù)據(jù)條數(shù)。

原生sql支持

使用pwiz生成models.py文件中,儲存著一個類MySQLDatabase,包含了數(shù)據(jù)庫連接的信息。這個類有一個cursor()方法,可以用來執(zhí)行原生sql語句(如果你的peewee版本低于3.0,這個方法名是get_cursor())。
在models.py中的BaseModel添加self.cursor = database.cursor
簡單查詢示例

def sql():
    user = User()
    cursor = user.cursor()
    cursor.execute('SELECT `uid`, `uname`, `gender` FROM `user` WHERE `uid`=10001')
    for (uid, uname, gender) in cursor:
        print('%d:%s:%s' % (uid, uname, gender))

批量插入使用executemany(),參考自CSDN。
在models.py中的BaseModel添加self.commit = database.commit

def sql():
    """執(zhí)行批量插入"""
    user = User()
    cursor = user.cursor()
    """md5密碼"""
    m = hashlib.md5()
    m.update('123456'.encode())
    pwd = m.hexdigest()
    """頭像"""
    avartar = 'https://avatars2.githubusercontent.com/u/25029451'
    data_source = [
        (avartar, 'Rem', pwd),
        (avartar, 'Cosmos', pwd)
    ]
    result = cursor.executemany('insert into `user`(`avartar`, `uname`, `password`) values (%s, %s, %s) ', data_source)
    print(result)  # 返回插入條數(shù)
    user.commit()  # 需要手動提交事務(wù)插入才能成功
    cursor.close()

總結(jié)

文中記錄了我目前能想起來的工作中使用頻率較高的一些操作。其他的操作由于沒想起來便沒作記錄,也許日后會更新加入。除此之外,鎖、隔離級別、分布式支持在peewee中的體現(xiàn)我暫時沒有什么頭緒,待google看看。關(guān)于鎖,官方文檔中提到基本上都是sqlite相關(guān)的,還有一個樂觀鎖的例子,但我粗略看了一下需要在表中添加version字段,所以沒有采用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 32,279評論 2 89
  • 原文:https://my.oschina.net/liuyuantao/blog/751438 查詢集API 參...
    陽光小鎮(zhèn)少爺閱讀 3,951評論 0 8
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,626評論 1 32
  • 一直很疑惑:為什么看到山就想起家?直到現(xiàn)在我才恍然明白:我家就在山的那邊。 小時候我和哥哥在山下村...
    莫寄閱讀 734評論 2 5
  • 冬天了,我在的城市下雪了,是的,夢中的江南水鄉(xiāng)在下雪,多少讓我有些接受不了。臨近放假了,大雪飄飛,冷冽的風(fēng)席卷著家...
    塔城女王閱讀 321評論 1 2

友情鏈接更多精彩內(nèi)容