Python折腾数据库(一)peewee
SQL数据库恐怕没几个人能够绕过去,自己折腾跑分析也有几个年头了回头想想之前就是吃了没有充分利用数据库来管理数据的亏。
二的话,有缘再见,最近想学一下SQLAlchemy
先推荐两个数据库的图形化查看软件
- dbeaver ***** 全平台 除了驱动不太好下,其他基本完美
- sqlite-browser *** 全平台 仅支持Sqlite,数据一多还会卡
- MySQL-wokbench **** 全平台 仅支持MySQL
关于peewe
官方文档 http://docs.peewee-orm.com/en/latest/
官方文档写的很完善,示例清晰,如果英语可以,还是直接看文档的好
之前我写过一点东西,简书地址如下,可以搭配着看,部分内容重合
http://www.jianshu.com/p/26648d192cd4
一、安装
- pypi:
pip install peewee
- anaconda or conda:
conda install -c conda-forge peewee
推荐使用anaconda管理包,尤其是Windows环境下,依赖问题貌似比Linux更加棘手(主要是不知道哪个包引起的,也不知道从那下载,最起码peewee我c++装遍了也没用) - 源码 github
git clone https://github.com/coleifer/peewee.git
cd peewee
python setup.py install
二、创建数据库和表单
peewee的使用异常简单,通过继承peewee提供的Model类,就可以一个类代表一个表,然后进行各种处理,方便快捷。
下面是一些简单示例,并没有详细追求表之间的逻辑性等等,因此看看就好,别细追究
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import os
from peewee import Model # peewee提供的基础类,一个Model就对应一个数据库
from peewee import SqliteDatabase # 我使用sqlite做展示,peewee支持常见的MySQL、PostgreSQL等等多种数据库
from peewee import BooleanField # 几种常见的数据类型,还有PrimaryField自己可以看看用法
from peewee import CharField
from peewee import FloatField
from peewee import ForeignKeyField
from peewee import IntegerField
from peewee import TextField
# 指定database的路径为同该.py文件目录下的test.db数据库
# 而且便于后边其他脚本的导入(主要是为了db.atomic()的使用)
dbpath = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'test.db'
)
# 初始化数据库
db = SqliteDatabase(dbpath)
class BaseModel(Model):
u"""
基础类
可以在此处制定一些大家都需要的列,
然后每个继承的子类(表)中都会有这么固定的几列
"""
class Meta:
u"""指定数据库."""
database = db
class Teacher(BaseModel):
u"""
教师类
继承自基础数据库,表明这些表都是该数据库里的
"""
name = CharField() # 教师姓名
page = TextField() # 教师主页,由于字符串比较长,因此用text
class_ = CharField() # 上什么课
u"""
教师表,没有指定表的名字,peewee会默认使用类的名称Teacher作为表的名称
"""
class Student(BaseModel):
u"""
一个数据库的简单示例
"""
student = CharField(default='zhang') # 学生姓名
student_ID = IntegerField(unique=True) # 学号,学号必须是独一份的,不允许重复
math = ForeignKeyField(null=True) # 数学老师,数学老师允许为空,也就是说,可以不上数学课
english = ForeignKeyField() # 英语老师,默认是由zhang来上
class Meta:
u"""
db_table指定表名,
order_by 指定表中数据的排序顺序,
indexes是为表中数据添加索引,加快后续的查询
其中我指定对学生姓名和学号之间建立索引,两个一块查会有速度优势,
后边的True表明,这两个数据组合必须是unique的
"""
db_table = 'student'
order_by = ('student_ID', 'student', )
indexes = (
(('student', 'student_ID'), True),
)
三、插入数据
首先,新建表
如果一个表存在,我们在新建该表的话peewee会报错,并且程序退出,为了避免该种错误,通通在新建和删除之前,进行一次该表是否存在的判断
u"""
建表和删除表
由于peewee的表封装成了类的形式,
因此可以通过传参进行传递,
便于写重复利用的function
"""
def create_table(table):
u"""
如果table不存在,新建table
"""
if not table.table_exists():
table.create_table()
def drop_table(table):
u"""
table 存在,就删除
"""
if table.table_exists():
table.drop_table()
# 分别建立Teacher和Student表
create_table(Teacher)
create_table(Student)
其次,插入数据
peewee会默认生成一定名为id的主键列,我们不用特地指定该列的值,peewee会默认从1递增,该id可以像普通列一样通过点号和转换成字典获取,也可以通过一下三种插入方式手动指定(必须为正整数,而且必须unique,即不得存在多个相同id)
插入数据主要有三种方式,除了用法上,主要会带来性能上的极大差异,如果数据量比较大,用最慢的方法会被慢死的,曾经50M左右的数据用最基础(也最慢)的插入,插了两天多,中档方法只需要4分钟左右,最快的只用了1分钟不到。
- 基础款,最慢
# 直接create,create之后还会将创建的这条数据赋值给你,方便进行其他操作
teacher = Teacher.create(name='zhang', page='http://xxx.xxxx.xxx', class_=''english")
# 比如,第二个表我们用到了外键,此时就需要上边的那条数据了,否则还需要自行查询获取结果
Student.create(student='no.1', student_ID='001', english=teacher)
- 中档,稍快
tem = {
'teacher': 'zhang',
'page': 'http://xxx.xxx.xxx',
'class_': 'math'
}
# 假设有一个长度为10k的列表里边的数据全部是同tem形式一致的字典
# db就是上边我们指定的数据库
# 还有另外一个关键词,和其他的atomic用法,但是最推荐这个,也只介绍这个,有需要自己查文档
with db.atomic():
for i in data:
Teacher.create(**i)
- 最快
接上边虚拟的data,继续插入
# 最快的插入方法,想在快估计只能换模块了
# peewee性能限制,无法一次插入太多条,因此根据自己的情况调整,一般不能比100更多,出现插入出错的情况,可以适当减少该数值
with db.atomic():
for i in range(0, len(data), 100):
Teacher.insert_many(data[i: i+100).execute()
加速的原理很简单,数据库方面慢就慢在它commit的过程中,第一个方法,每插入一条就commit一次,不满才怪
四、查询
插叙单条数据
# get的括号中给定条件,可以一次性多个条件
teacher = Teacher.get(Teacher.name == 'zhang', class_ == 'math')
查询多条数据,及一些简单的骚操作
# 只需要教师名字,只要上数学课的老师,老师的名字只保留一份,如果有两个zhang,结果中,只保留一个
teachers = Teacher.select(Teacher.name).where(Teacher.class_=='math').distinct()
# 获取教师姓名
for teacher in teachers:
print(teacher.name)
# 将peewee的类转化成元组构成的列表,每个元组中包含有该条查询的所有数据
teacher1 = teacher.tuples()
# 将peewee的类转化成字典构成的列表
teacher2 = teacher.dict()
# 在上边的结果上进一步查询
teacher3 = teachers.select().,where(teahcer.name == 'zhang')
外链查询
# 导入两个非常有用的函数
# playhouse是peewee的一个附加模块,提供了许多附加的小功能
from playhouse.shortcut import model_to_dict, dict_to_model
# 还记得学生类用了外链么
student = Student.select().join(Teacher)
# 即可查看字典形式的,学生和相应的任课老师的信息
print(model_to_dict(student))
查询就是通过一个简单的“.”来获取不同的功能,非常具有Python的风格,使用起来非常简便
,同时如果对SQL语句有了解的话,什么时候改用哪个功能,也很好决断;
五、更新数据
# 注意到,有个用了点好,有个没有用么
query = Teacher.update(name="gnahz").where(Teacher.class_="math")
query.execute()
六、其他,几个我常用的小功能
以官方示例来做一些展示,给出关键词和用法,可能暂时不了解用不上,不过不妨了解一下心里有数,万一哪天需要呢
# paginate,选取第2页,每页10条,按Tweet.id排序,该条命令实质上选取了排序之后的11~20,这10条结果
Tweet.select().order_by(Tweet.id).paginate(2, 10)
# count,统计数目,没啥好说的,只不过数量较大时,该命令比较慢,如果常用该数目,建议直接多建一个表,记录一下
Tweet.select().count()
Tweet.select().where(Tweet.id > 50).count()
u"""
比较特殊,表建立以后,
还可以进行添加列、删除列、更换表名等等操作,对表进行一下修饰
但是一般用不上
"""
migrator = SqliteMigrator(db) # 这里的db就是我们最开始初始化的数据库
# 下面给某个表,添加了两个列,删除一个列,修改完成后,千万别忘了把原始的class给改掉
title_field = CharField(default='')
migrate(
migrator.add_column('some_table', 'title', title_field), # 某个表,新的列名,列的数据形式
migrator.rename_column('story', 'mod_date', 'modified_date') # 某个表,改列名
migrator.drop_column('some_table', 'old_column'), # 删除某列
migrator.rename_table('story', 'stories_tbl') # 重命名表
migrator.add_index('story', ('pub_date',), False), #添加index
migrate(migrator.drop_index('story', 'story_pub_date_status')) # 删除index
)
# 其他用法,是否允许某个表中的某列拥有null值
# Note that when making a field not null that field must not have any
# NULL values present.
migrate(
# Make `pub_date` allow NULL values.
migrator.drop_not_null('story', 'pub_date'),
# Prevent `modified_date` from containing NULL values.
migrator.add_not_null('story', 'modified_date'),
)
几个查询符号
一些特殊的查询方式
官方示例:
# 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 << ['charlie', 'huey', 'mickey'])
Employee.select().where(Employee.salary.between(50000, 60000))
Employee.select().where(Employee.name.startswith('C'))
Blog.select().where(Blog.title.contains(search_string))
最后絮叨一句,peewee会在插入数据时默认生成一个名字叫id的primary key,这个id可以像其他列一样通过点号访问,我们做外键的时候其实直接赋值相应的id号给指定外键的列就可以,
因此,如果有时候需要插入大量含有外键的数据,查询过程严重拖慢插入速度时,我们可以通过提前获取对应外链的id号,通过直接指定外链的方式加快速度