peewee用法考察

[TOC]


peewee

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

閱讀原文體驗更佳

主要參考內容:
peewee官方文檔

考察目標

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

  • Create:單條插入、批量插入几苍、批量塊插入翻屈、獲取自增pk
  • Update:數據更新
  • Retrieve:單條查詢、多條查詢妻坝、指定字段單條/多條查詢伸眶、關聯(lián)查詢(join、union)刽宪、聚合查詢
  • Delete:單條刪除厘贼、批量刪除
  • 其他功能:事務、原生sql查詢

前置準備

在mysql中新建表

本文使用數據庫名為peewee纠屋,三張表分別是user涂臣、balance和integral,結構如下:

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分別提供根據模型生成表根據表生成模型的功能。但peewee等orm要考慮兼容多種數據庫族铆,因此模型生成表的過程中只能使用所有數據庫公有的字段類型岩四,不能使用mysql的tinyint、char等類型哥攘,所以不考慮根據模型生成表剖煌。使用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

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

批量添加方法

使用insert_many批量添加數據:在插入多條數據時蛋欣,根據文檔描述航徙,create()方法每次都會開啟事務(如果存儲引擎支持),雖然可以遍歷方式用上述方法逐條插入陷虎,但數據量一大就會導致效率低下到踏,所以有必要進行批量插入優(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():
    """批量添加數據"""
    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)

控制臺輸出返回批量插入過程中第一條數據的自增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

批量塊添加方法

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

def create():
    """批量塊添加數據"""
    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的實質:

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)

讀取單條數據

直接調用get()方法庄萎,可以添加過濾(filter)條件踪少。查詢結果是一個User對象,直接打印得到pk糠涛,通過調用成員變量可以獲得對應字段的值援奢。特別的,當篩選條件為pk時忍捡,可以使用get_by_id()方法直接查詢:

def retrieve():
    """讀取一條數據"""
    # result = User.get_by_id(10000)
    result = User.get(User.uname == 'Jack')
    print('直接打蛹:%s' % result)
    """查看結構"""
    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])
直接打忧星:10001
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()方法查詢出來的數據包含了所有的字段。那么具篇,當我們的需求是查詢單條數據中的限定字段(fields)時纬霞,應該怎么查詢呢?閱讀get()方法的源碼可以得知驱显,其調用了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()

因此险领,我們可以直接調用select()方法,然后調用get()方法秒紧,就可以實現需求。

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

事實上挨下,很多時候工作中遇到的單一查詢都是只需要其中幾個字段的數據熔恢,因此比起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

讀取多條數據

讀取多條數據時臭笆,就要用到剛才提到的select()方法叙淌,這也是查詢使用的主要方法。查閱select()方法源碼可知愁铺,其返回一個ModelSelect類鹰霍,然后我們再調用這個類的各種方法(where()、order_by()茵乱、limit()茂洒、offset())來實現查詢的條件限定。如果你有特殊的需求瓶竭,可以在鏈式操作的最后調用dicts()方法督勺,將結果轉化為一個字典。例如斤贰,我們要查詢性別為男性智哀、按注冊日期降序(升序寫字段,降序在字段前面再加個負號)排列的第二和第三位用戶的編號和名稱

def retrieve():
    """讀取多條數據"""
    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))
    """轉換為字典形式"""
    print('轉為字典形式:')
    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
轉為字典形式:
{'uname': 'Micheal', 'uid': 10002}
{'uname': 'Jack', 'uid': 10001}

Process finished with exit code 0

查詢表達式

根據文檔荧恍,peewee的查詢操作符由以下組成:

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

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

def retrieve():
    """模糊查詢用戶名包含J的用戶"""
    # 包含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()
    # 包含J開頭的查詢條件
    query = User.select(User.uname, User.uid).where(User.uname % 'J%').dicts()
    for row in query:
        print(row)

我們應該知道送巡,模糊查詢的時候使用右模糊查詢可以有效地利用索引加快查詢速度摹菠。

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在數組y中
.not_in(value x不在數組y中
.is_null(is_null) 判斷是否為空,接受布爾值
.contains(substr) 包含字符串淮腾,忽略大小寫
.startswith(prefix) 以字符串開頭糟需,忽略大小寫
.endswith(suffix) 以字符串結尾屉佳,忽略大小寫
.between(low, high) 在兩個值的范圍內
.regexp(exp) 匹配正則表達式(大小寫敏感)
.iregexp(exp) 匹配正則表達式,忽略大小寫
.bin_and(value) 二進制的和
.bin_or(value) 二進制的或
.concat(other) 將兩個字符串或對象臨時合并
.distinct() 指定字段過濾重復記錄
.collate(collation) 對列進行指定規(guī)則排序
.cast(type) 將列的值按照指定類型轉變

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

# 包含J結尾的查詢條件
# 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時杈帐,調用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)越的功能:事務操作。peewee提供了兩種事務開啟的方法站叼,這里只用第二種娃兽。
首先,我們可以在models.py這個文件的BaseModel類中添加一個trans()方法尽楔,便于后面開啟事務:

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

    class Meta:
        database = database

開啟事務的方法

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

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

應用情景

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

def transaction():
    user = User()
    result = user.select(User.uid, User.balance).where(User.uname == 'Jack').get()  # 查詢用戶的現有余額以及用戶編號
    """開啟事務"""
    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('更新成功')

如果其中有一步操作失敗,則整個事務自動回滾怔球。

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]

關于union的操作姿勢,官方文檔也提供了多種方式担汤,包括操作符涎跨、方法等等。

操作符 意義
| union
+ union all

當我們需要把余額操作記錄和積分操作記錄并作操作記錄統(tǒng)計時間降序查看第二和第三條數據(盡管這意義不大崭歧,且實際上我本來是想用訂單統(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)率碾。如果對此感到不習慣叔营,也可以用方法union_all()來代替屋彪。值得注意的是,在方法order_by()中绒尊,不能直接寫入'addtime desc'這樣的字符串畜挥,因為peewee不會將其解析為sql語句。你需要使用SQL類將其轉化為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)計用戶個數"""
    count = User.select().count()
    print('用戶數量:%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

刪除調用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)

返回值為成功刪除的數據條數客叉。

原生sql支持

使用pwiz生成models.py文件中,儲存著一個類MySQLDatabase景描,包含了數據庫連接的信息。這個類有一個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)  # 返回插入條數
    user.commit()  # 需要手動提交事務插入才能成功
    cursor.close()

總結

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末笼才,一起剝皮案震驚了整個濱河市漱受,隨后出現的幾起案子,更是在濱河造成了極大的恐慌骡送,老刑警劉巖昂羡,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件絮记,死亡現場離奇詭異,居然都是意外死亡紧憾,警方通過查閱死者的電腦和手機到千,發(fā)現死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赴穗,“玉大人憔四,你說我怎么就攤上這事“忝迹” “怎么了了赵?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長甸赃。 經常有香客問我柿汛,道長,這世上最難降的妖魔是什么埠对? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任络断,我火速辦了婚禮,結果婚禮上项玛,老公的妹妹穿的比我還像新娘貌笨。我一直安慰自己,他們只是感情好襟沮,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布锥惋。 她就那樣靜靜地躺著,像睡著了一般开伏。 火紅的嫁衣襯著肌膚如雪膀跌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天固灵,我揣著相機與錄音捅伤,去河邊找鬼。 笑死巫玻,一個胖子當著我的面吹牛暑认,可吹牛的內容都是我干的。 我是一名探鬼主播大审,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蘸际,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了徒扶?” 一聲冷哼從身側響起粮彤,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后导坟,有當地人在樹林里發(fā)現了一具尸體屿良,經...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年惫周,在試婚紗的時候發(fā)現自己被綠了尘惧。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡递递,死狀恐怖喷橙,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情登舞,我是刑警寧澤贰逾,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站菠秒,受9級特大地震影響疙剑,放射性物質發(fā)生泄漏。R本人自食惡果不足惜践叠,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一言缤、第九天 我趴在偏房一處隱蔽的房頂上張望荆忍。 院中可真熱鬧岔帽,春花似錦、人聲如沸嗡载。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拳芙。三九已至察藐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間舟扎,已是汗流浹背分飞。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留睹限,地道東北人譬猫。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像羡疗,于是被迫代替她去往敵國和親染服。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內容

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