python数据库
# 一、MariaDB
# 驱动
与MySQL通信就是典型的CS模式。Server就是服务器端,使用客户端先建立连接,数据库编程时,这个客户端变成了程序。 MySQL基于TCP协议之上开发,传输的数据必须遵循MySQL的协议。 封装好MySQL协议的包,习惯上称为驱动程序。 MysQL的驱动
- MysQLdb 最有名的库。对MySQL的C Client封装实现,支持Python 2,不更新了,不支持Python3
- mysqlclient 。 在MySQLdb的基础上,增加了对Python 3的支持
- MySQL官方Connector Mysql官网 https://dev.mysgl.com/downloads/connector/
- pymysql 。 语法兼容MySQLdb,使用纯Python写的MySQL客户端库,支持Python 3;CPython 2.7 、3.4+;MySQL 5.5+、MariaDB 5.5+
# 二、Pymysql
# 安装
pip install pymysql, simplejson
# 连接Connect
Connection.ping(False)
方法,测试数据库服务器是否活着。有一个参数reconnect表示断开与服务器连接是否重连。连接关闭抛出异常。
import pymysql, simplejson
from pymysql import Connection
from pathlib import Path
path = Path.joinpath(Path.cwd(), "config.json")
with open( path, mode="r") as f: #读取配置文件
config = simplejson.load(f)
conn:Connection = None;
cursor:Cursor = None;
try:
#conn:Connection = pymysql.connect(user="root",password="123456",host="10.10.10.207",database="test2",port=3306,);
conn = pymysql.connect(**config);
conn.ping(False); # 测试数据库服务器是否活着
print(conn);
cursor = conn.cursor(); #游标
sql ="""
insert into person( name, age) values ("jim", 20);
"""
i:int = cursor.execute(sql); #执行sql
print(i);
conn.commit(); # 提交事务
except Exception as e:
conn.rollback(); #回滚事务
print(e);
finally:
if cursor:
cursor.close();
if conn:
conn.close();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
参数 | 描述 |
---|---|
host | 数据库服务器地址,默认 localhost |
user | 用户名,默认为当前程序运行用户 |
password | 登录密码,默认为空字符串 |
database | 默认操作的数据库 |
port | 数据库端口,默认为 3306 |
bind_address | 当客户端有多个网络接口时,指定连接到主机的接口。参数可以是主机名或IP地址。 |
unix_socket | unix 套接字地址,区别于 host 连接 |
read_timeout | 读取数据超时时间,单位秒,默认无限制 |
write_timeout | 写入数据超时时间,单位秒,默认无限制 |
charset | 数据库编码 |
sql_mode | 指定默认的 SQL_MODE |
read_default_file | Specifies my.cnf file to read these parameters from under the [client] section. |
conv | Conversion dictionary to use instead of the default one. This is used to provide custom marshalling and unmarshaling of types. |
use_unicode | Whether or not to default to unicode strings. This option defaults to true for Py3k. |
client_flag | Custom flags to send to MySQL. Find potential values in constants.CLIENT. |
cursorclass | 设置默认的游标类型 |
init_command | 当连接建立完成之后执行的初始化 SQL 语句 |
connect_timeout | 连接超时时间,默认 10,最小 1,最大 31536000 |
ssl | A dict of arguments similar to mysql_ssl_set()'s parameters. For now the capath and cipher arguments are not supported. |
read_default_group | Group to read from in the configuration file. |
compress | Not supported |
named_pipe | Not supported |
autocommit | 是否自动提交,默认不自动提交,参数值为 None 表示以服务器为准 |
local_infile | Boolean to enable the use of LOAD DATA LOCAL command. (default: False) |
max_allowed_packet | 发送给服务器的最大数据量,默认为 16MB |
defer_connect | 是否惰性连接,默认为立即连接 |
auth_plugin_map | A dict of plugin names to a class that processes that plugin. The class will take the Connection object as the argument to the constructor. The class needs an authenticate method taking an authentication packet as an argument. For the dialog plugin, a prompt(echo, prompt) method can be used (if no authenticate method) for returning a string from the user. (experimental) |
server_public_key | SHA256 authenticaiton plugin public key value. (default: None) |
db | 参数 database 的别名 |
passwd | 参数 password 的别名 |
binary_prefix | Add _binary prefix on bytes and bytearray. (default: False) |
# 游标Cursor
操作数据库,必须使用游标,需要先获取一个游标对象Connection.cursor(cursor=None) 方法返回一个新的游标对象。连接没有关闭前,游标对象可以反复使用。 cursor参数,可以指定一个Cursor类。如果为None,则使用默认Cursor类。Cursor类的实例,使用execute() 方法,执行SQL语句,成功返回影响的行数
# 事务管理
提交和回滚, 默认connect关闭事务,需要自己提交
# 一般流程
- 建立连接
- 获取游标
- 执行SQL
- 提交事务
- 释放资源
# 查询
# 带列名查询
cursor = conn.cursor(DictCursor); #游标
with conn.cursor(DictCursor) as cursor:
try:
sqlSelect = "SELECT * FROM buyer WHERE cid=%s";
result:int = cursor.execute(sqlSelect,(self.cid) ) #参数为对象或者元组
if result>0: #有数据,则更新,主要更新业务相关的数据, 数据有变化才会更新
oldBuyer:dict = cursor.fetchone();
sqlUpdate = """\
UPDATE buyer SET nickName=%s,tag=%s,description=%s,visitDate=%s,openid=%s,name=%s,phone=%s,employee=%s,inviteEmp=%s,inviteCid=%s,hmKaHao=%s WHERE cid=%s
"""
paramsUpdate = (self.nickName if self.nickName else oldBuyer["nickName"],
self.tag if self.tag else oldBuyer["tag"],
self.description if self.description else oldBuyer["description"],
self.visitDate if self.visitDate else oldBuyer["visitDate"],
self.openid if self.openid else oldBuyer["openid"],
self.name if self.name else oldBuyer["name"],
self.phone if self.phone else oldBuyer["phone"],
self.employee if self.employee else oldBuyer["employee"],
self.inviteEmp if self.inviteEmp else oldBuyer["inviteEmp"],
self.inviteCid if self.inviteCid else oldBuyer["inviteCid"],
self.hmKaHao if self.hmKaHao else oldBuyer["hmKaHao"],
self.cid);
#sqlUpdate = sqlUpdate.strip()
#print(sqlUpdate, paramsUpdate);
result: int = cursor.execute(sqlUpdate, paramsUpdate)
print("更新结果:" + str(result));
else:
sqlInsert = """\
insert into buyer(cid, nickName,tag,description,visitDate,openid, name,phone,hmKaHao, employee, inviteEmp,inviteCid)
values(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s);
"""
paramsInsert = (self.cid, self.nickName, self.tag, self.description,self.visitDate,
self.openid,self.name, self.phone, self.hmKaHao, self.employee, self.inviteEmp,self.inviteCid)
result: int = cursor.execute(sqlInsert, paramsInsert)
print("保存或更新买家saveOrUpdate:" ,result);
return result;
except Exception as e:
print(e);
raise e;
return 0;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# sql注入攻击
参数化查询,可以有效防止注入攻击,并提高查询的效率, 解决办法:在sql语句写入占位符(%s),注意不要加引号 Cursor.execute(query, args=None) query查询字符串使用c printf风格。args,必须是元组、列表或字典。如果查询字符串使用%(name)s,就必须使用字典。
sq1 = "select * from student where id-%s"
userid ='2 or 1=1'
rows = cursor.execute(sgl, userid) # (userid,)
sql = "select * from student where name like %(name)s and age > %(age)s" #仅测试用,不用like
cursor.execute(sql,{'name': 'tom%','age': 253})
print(cursor.fetcha11())
# like写法
sql = "select * from user where username like %s;"
user = '42b'
format_str = f"%%{user}%%"
cdn_tuple = (format_str)
curs.execute(sql, cdn_tuple)
#日期写法
conn = pymysql.connect(host="127.0.0.1", user='root', password="12345" charset='utf8' database='test')
curs = conn.cursor()
sql = "select * from user where date_format(addtime, "%%Y-%%m-%%d")>=str_to_date(%s, "%%Y-%%m-%%d");"
addtime = '2022-07-12'
cdn_tuple = (addtime)
curs.execute(sql, cdn_tuple)
res = curs.fetchone()
conn.close()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 上下文支持
Connection、 Cursor里面有__enter__
__exit__
方法,因此支持上下文with管理
# 三、mysqlclient
pip install mysqlclient
用法与pymysql相似
https://mysqlclient.readthedocs.io/
# 四、元编程
元编程概念来自LISP和smalltalk。 我们写程序是直接写代码,是否能够用代码来生成未来我们需要的代码吗? 这就是元编程,例如,我们写一个类class A,能否用代码生成一个类出来? 用来生成代码的程序称为元程序metaprogram,编写这种程序就称为元编程metaprogramming。 Python语言能够通过反射实现元编程。
Python中所有非object类都继承自object类;所有类的类型包括type类都是type;type类继承自object类,object类的类型也是type类
# type类
# type构建类
C = type("CC",(),{}); # 用type元类构造了类C
print( type(C), C.__bases__) # type, object 类的类型为type
2
class ModelMeta(type): # type类的子类,用来构造其它类的类
def __new__(cls, *args, **kwargs):
print(cls); # ModelMeta类本身,这时A还没有构造出来呢
print(args); # name, bases继承, dict属性字典
print(kwargs); # 没用
return super().__new__(cls, *args, **kwargs);
class A(metaclass=ModelMeta): # 继承关系没变,还是object,但是构造方式由type更改成了MetaClass
pass
print(type(A));
class ModelMeta(type): # type类的子类,用来构造其它类
def __new__(cls, name, bases, attrs):
print(cls);
print( name, bases, attrs);
return super().__new__(cls, name, bases, attrs)
class A(metaclass=ModelMeta): # 继承关系没变,还是object,但是构造方式由type更改成了MetaClass
pass
class B(A): pass
print(type(A), A.__bases__); # ModelMeta object
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
父类的元类metaclass
更改了,子子孙孙的元类也相应更改
# 元类的应用:简易orm
class Field:
def __init__(self, fieldname = None, pk = False, nullable= True):
self.fieldname = fieldname;
self.pk = pk;
self.nullable = nullable;
def __str__(self):
return "<Field: {} {} {}>".format(self.fieldname, self.pk, self.nullable )
__repr__ = __str__
class ModelMeta(type): # type类的子类,用来构造其它类
def __new__(cls, name, bases, attrs):
"""
:param name: 类名
:param bases: 继承
:param attrs: 属性字典对象
"""
print(cls);
print( name, bases, attrs);
# 添加属性
primaryKeys = []; # 主键
if "db_table" not in attrs:
attrs["db_table"] = name.lower();
for key, value in attrs.items():
if isinstance(value, Field): # 设置字段
if not value.fieldname or value.fieldname.strip()=='':
value.fieldname = key;
if value.pk:
primaryKeys.append(key)
attrs["primaryKeys"] = primaryKeys
return super().__new__(cls, name, bases, attrs)
class Model(metaclass=ModelMeta): # 继承关系没变,还是object,但是构造方式由type更改成了MetaClass
pass
print(type(Model), Model.__bases__); # ModelMeta, object
class Student(Model): #
id = Field(pk=True);
name = Field(fieldname= "username", nullable=False);
age = Field( nullable=True);
print(Student.__dict__);
s = Student();
print(s.db_table);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# 元编程总结
元类是制造类的工厂,是用来构造类的类构造好元类,就可以在类定义时,使用关键字参数metaclass指定元类,可以使用最原始的metatype(namebases,dict)的方式构造一个类。元类的__new__0 方法中,可以获取元类信息、当前类、基类、类属性字典。 元编程一般用于框架开发中, Django、sqlalchemy (opens new window)使用了元类,让我们使用起来很方便。
pip install SQLAlchemy
# 五、ORM和Django配置
ORM : 全称 object relational mapping, 对象关系映射, 如sqlalchemy(适合企业级项目的开发)、peewee、sqlobject 功能 : 通过orm实现使用操作对象的方式来操作数据库中的数据 目的 : 就是为了能够让不懂SQL语句的人通过python面向对象的知识点也能够轻松自如的操作数据库 缺陷 : sql 封装死了, 有时候查询速度很慢
https://blog.csdn.net/Linxiaokang_1212/article/details/127912847
# orm常用框架
pip install sqlalchemy
pip install sqlobject
2
# 1、SQLObject
优点:采用了易懂的ActiveRecord 模式 ; 一个相对较小的代码库
缺点:方法和类的命名遵循了Java 的小驼峰风格 ;不支持数据库session隔离工作单元
# 2、Storm
优点:清爽轻量的API,短学习曲线和长期可维护性 ;不需要特殊的类构造函数,也没有必要的基类
缺点:迫使程序员手工写表格创建的DDL语句,而不是从模型类自动派生 ;Storm的贡献者必须把他们的贡献的版权给Canonical公司
# 3、Django's ORM
优点:易用,学习曲线短 ;和Django紧密集合,用Django时使用约定俗成的方法去操作数据库
缺点:不好处理复杂的查询,强制开发者回到原生SQL ;紧密和Django集成,使得在Django环境外很难使用
# 4、peewee
优点:Django式的API,使其易用 ;轻量实现,很容易和任意web框架集成
缺点:不支持自动化 schema 迁移 ;多对多查询写起来不直观
# 5、SQLAlchemy
优点:企业级 API,使得代码有健壮性和适应性 ;灵活的设计,使得能轻松写复杂查询
缺点:工作单元概念不常见 ;重量级 API,导致长学习曲线
pip uninstall SQLAlchemy
sqlalchemy.__version__
2
3
# 安装指定版本
pip uninstall django #卸载
pip install Django==2.2.28
pip install Django==4.2
django-admin startproject salary . #项目准备, 创建项目,会出现 manager.py和salary目录
django-admin startapp employee #创建应用, 会创建employee目录
2
3
4
5
6
django主要版本: 2.2.28、3.2.18、4.2
Django2.2,必须mysql5.6版本以上;django3.2支持MySQL 5.7 及以上版本;Django4及更高版本需要MySQL 8以上,否则会报错:django.db.utils.NotSupportedError: MySQL 8 or later is required (found 5.6.22)
SELECT @@version #查看数据库版本 5.6.22
# 创建项目和应用
# manage.py
# 修改项目目录下文件
# settings.py
项目的配置文件,需要添加上应用INSTALLED_APPS
、配置数据库DATABASES
(默认为sqlite3)、时区TIME_ZONE
、 日志
https://docs.djangoproject.com/en/4.2/ref/settings/#databases
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'employee', #自己创建的应用 django-admin startapp employee
]
DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': BASE_DIR / 'db.sqlite3',
# }
"default": {
"ENGINE": "django.db.backends.mysql",
"NAME": "test2", #库
"USER": "root",
"PASSWORD": "123456",
"HOST": "10.10.10.207",
"PORT": "3306",
}
}
TIME_ZONE = 'Asia/Shanghai'
# https://docs.djangoproject.com/en/4.2/topics/logging/#configuring-logging
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"handlers": {
"console": {
"class": "logging.StreamHandler",
},
},
"loggers": {
"django.db.backends": {
"handlers": ["console"],
"level": "DEBUG",
},
},
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# urls.py
# 编写测试文件test.py
在项目下新建test.py, 与manage.py同级, 仿照wsgi.py (opens new window)写如下代码:
"""
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
"""
import os
import django
from django.core.wsgi import get_wsgi_application
from django.db.models import Manager
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salary.settings')
application = get_wsgi_application()
# 上面的加载django配置文件,初始化django环境后,再写自己的代码
from employee.models import Employee
manager:Manager = Employee.objects;
emps:QuerySet = manager.all(); # 懒加载 管理器对象objects
for x in emps:
print(type(x), x)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 模型model
# 字段类型
- AutoField: 一个自动增加的整数类型字段。通常你不需要自己编写它,Django会自动帮你添加字段:id = models.AutoField(primary_key=True),这是一个自增字段,从1开始计数。如果你非要自己设置主键,那么请务必将字段设置为primary_key=True。Django在一个模型中只允许有一个自增字段,并且该字段必须为主键!
- BigAutoField😦.10新增)64位整数类型自增字段,数字范围更大,从1到9223372036854775807
- BigIntegerField: 64位整数字段(看清楚,非自增),类似IntegerField ,- 到9223372036854775807。在Django的模板表单里体现为一个textinput标签。
- BinaryField: 二进制数据类型。使用受限,少用。
- BooleanField:布尔值类型。默认值是None。在HTML表单中体现为CheckboxInput标签。如果要接收null值,请使用NullBooleanField。
- CharField:字符串类型。必须接收一个max_length参数,表示字符串长度不能超过该值。默认的表单标签是input text。最常用的filed,没有之一!
- CommaSeparatedIntegerField: 逗号分隔的整数类型。必须接收一个max_length参数。常用于表示较大的金额数目,例如1,,000元。
- DateField: class DateField(auto_now=False, auto_now_add=False, **options)日期类型。一个Python中的datetime.date的实例。在HTML中表现为TextInput标签。在admin后台中,Django会帮你自动添加一个JS的日历表和一个“Today”快捷方式,以及附加的日期合法性验证。两个重要参数:(参数互斥,不能共存) 。auto_now:每当对象被保存时将字段设为当前日期,常用于保存最后修改时间。auto_now_add:每当对象被创建时,设为当前日期,常用于保存创建日期(注意, 它是不可修改的)。设置上面两个参数就相当于给field添加了editable=False和blank=True属性。如果想具有修改属性,请用default参数。 例子:pub_time = models.DateField(auto_now_add=True),自动添加发布时间。
- DateTimeField: 日期时间类型。Python的datetime.datetime的实例。与DateField相比就是多了小时、分和秒的显示,其它功能、参数、用法、默认值等等都一样。
- DecimalField: 固定精度的十进制小数。相当于Python的Decimal实例,必须提供两个指定的参数!参数max_digits:最大的位数,必须大于或等于小数点位数 。 decimal_places:小数点位数,精度。 当localize=False时,它在HTML表现为NumberInput标签,否则是text类型。 例子:储存最大不超过999,带有2位小数位精度的数,定义如下:models.DecimalField(..., max_digits=, decimal_places=)。
- DurationField: 持续时间类型。存储一定期间的时间长度。类似Python中的timedelta。在不同的数据库实现中有不同的表示方法。常用于进行时间之间的加减运算。但是小心了,这里有坑,PostgreSQL等数据库之间有兼容性问题!
- EmailField: 邮箱类型,默认max_length最大长度254位。使用这个字段的好处是,可以使用DJango内置的EmailValidator进行邮箱地址合法性验证。
- FileField: class FileField(upload_to=None, max_length=, **options)上传文件类型。
- FilePathField: 文件路径类型。
- FloatField: 浮点数类型,参考整数类型。
- ImageField: 图像类型。
- IntegerField: 整数类型,最常用的字段之一。取值范围-2147483648到2147483647。在HTML中表现为NumberInput标签。
- GenericIPAddressField:class GenericIPAddressField(protocol='both', unpack_ipv4=False, **options)[source],IPV4或者IPV6地址,字符串形式, 例如192.0.2.30或者2a02:42fe::4在HTML中表现为TextInput标签。参数protocol默认值为‘both’,可选‘IPv4’或者‘IPv6’,表示你的IP地址类型。
- NullBooleanField: 类似布尔字段,只不过额外允许NULL作为选项之一。
- PositiveIntegerField: 正整数字段,包含0,最大2147483647。
- PositiveSmallIntegerField: 较小的正整数字段,从0到32767。
- SlugField: slug是一个新闻行业的术语。一个slug就是一个某种东西的简短标签,包含字母、数字、下划线或者连接线,通常用于URLs中。可以设置max_length参数,默认为50。
- SmallIntegerField: 小整数,包含-32768到32767。
- TextField: 大量文本内容,在HTML中表现为Textarea标签,最常用的字段类型之一!如果你为它设置一个max_length参数,那么在前端页面中会受到输入字符数量限制,然而在模型和数据库层面却不受影响。只有CharField才能同时作用于两者。
- TimeField: 时间字段,Python中datetime.time的实例。接收同DateField一样的参数,只作用于小时、分和秒。
- URLField: 一个用于保存URL地址的字符串类型,默认最大长度200。
- UUIDField: 用于保存通用唯一识别码(Universally Unique Identifier)的字段。使用Python的UUID类。在PostgreSQL数据库中保存为uuid类型,其它数据库中为char()。这个字段是自增主键的最佳替代品,后面有例子展示。
# 缺省主键
缺省情况下,Django的每一个Model都有一个名为id的AutoField字段,如:id = models .AutoField(primary_key=True) 如果显式定义了主键,这种缺省主键就不会被创建了。Python之禅中说“显式优于隐式”,所以,尽量使用自己定义的主键,哪怕该字段名就是id,也是一种不错的选择
# 字段选项
一对多
class Employee(Model):
class Meta:
db_table = 'employee';
id = BigIntegerField(primary_key=True, db_column="emp_no")
name = CharField(null=False, max_length=16)
gender = models.SmallIntegerField(null=False)
birth_date = models.DateField(null=False);
hire_date = models.DateField(null=False);
def __str__(self):
return "<Employee {} {} {}>".format(self.id, self.name, self.gender);
__repr__ = __str__;
2
3
4
5
6
7
8
9
10
11
12
13
# Model类
基类: django.db.models.Model
, 表名不指定默认使用model_name, 可以使用Meta
类修改表名
# 管理器对象objects
Django会为模型类提供一个objects对象,它是django.db.models.manager.Manager
类型,用于与数据库交互。当定义模型类的时候没有指定管理器,则Django会为模型类提供一个objects的管理器。如果在模型类中手动指定管理器后,Django不再提供默认的objects的管理器了。见ModelBase
元类源码
管理器是Django的模型进行数据库查询操作的接口,Django应用的每个模型都至少拥有一个管理器.
用户也可以自定义管理器类,继承自django.db.models.manager.Manager,实现表级别控制。
class Salary(models.Model):
class Meta:
db_table = 'salary';
id = models.AutoField(primary_key=True)
emp_no = models.ForeignKey("Employee", on_delete=models.CASCADE, db_column="emp_no"); #填对象名 多对一 外键,会自动添加一个字段emp_no_id
salary= DecimalField(null=False)
from_date= DateField(null=False)
to_date= DateField(null=False)
def __str__(self):
return "<Salary {} {} {}>".format(self.id, self.emp_no_id, self.salary); # self.emp_no为Employee对象,
#插入数据
for i in range(50):
mngSalary.create(emp_no_id=random.randint(1,21), salary=random.randrange(15000,19000),from_date = radar.random_date(), to_date = radar.random_date() );
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查询
# 限制查询集(切片)
分页功能实现,使用限制查询集。 查询集对象可以直接使用索引下标的方式 (不支持负索引),相当于SOL语句中的limit和offset子句注意使用索引返回的新的结果集,依然是惰性求值,不会立即查询。
emps:QuerySet = manager.all()[1:3]; # 懒加载 分页,前包后不包
# 结果集方法
所谓惰性机制:Publisher.objects.all()或者.filter()等都只是返回了一个QuerySet(查询结果集对象),它并不会马上执行sql,而是当调用QuerySet的时候才执行。
QuerySet特点:可迭代的 ; 可切片;惰性计算和缓存机制
manager:Manager = Employee.objects; #管理器对象objects
manager.order_by("pk","birthday") # 降序就用负号
# emps:QuerySet = manager.all().values(); # 懒加载 分页,前包后不包
emps:QuerySet = manager.filter(pk=122).values("pk","name"); # 懒加载 分页,前包后不包, pk指的是主键,不用关心主键字段名是啥
2
3
4
get严格要求返回一个对象,立即查询,多或少一个都不行,抛DoesNotExist异常或 MultipleObjectsReturned异常;
first 通过limit 1实现,返回一条数据,找不到返回None,last倒排取limit 1,找不到返回None
count 计数,返回单值,当前查询的总条数, exist 是否有数据
# 字段查询 (Field Lookup) 表达式
字段查询表达式可以作为filter()、exclude()、get()的参数,实现where子句。语法: 属性名称_比较运算符=值 注意:属性名和运算符之间使用双下划线比较运算符如下
models.Tb1.objects.filter(id__lt=10, id__gt=1) # 获取id大于1 且 小于10的值
models.Tb1.objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据
models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in
models.Tb1.objects.filter(name__contains="ven") # 获取name字段包含"ven"的
models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感
models.Tb1.objects.filter(id__range=[1, 3]) # id范围是1到3的,等价于SQL的bettwen and
类似的还有:startswith,istartswith, endswith, iendswith
date字段还可以:
models.Class.objects.filter(first_day__year=2017)
2
3
4
5
6
7
8
9
10
11
12
13
14
django-orm常用字段和查询 (opens new window)
# Q对象
对查询条件进行与、或、非操作, 在类似 filter()
(opens new window) 中,查询使用的关键字参数是通过 "AND" 连接起来的。如果你要执行更复杂的查询(例如,由 OR
语句连接的查询),你可以使用 Q 对象
(opens new window)。
一个 Q 对象
(opens new window) (django.db.models.Q
) 用于压缩关键字参数集合。这些关键字参数由前文 "Field lookups" 指定。
Poll.objects.get(
Q(question__startswith='Who'),
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)
#相当于sql:
SELECT * from polls WHERE question LIKE 'Who%'
AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
2
3
4
5
6
7
8
# 聚合分组
annotate: 返回查询集,元素是对象。用聚合函数,聚合函数会分组,没有指定分组使用PK分组,行行分组
from django.db.models import Manager, QuerySet, F, Q, Max, Min, Count, Avg, Sum
emps2:QuerySet = manager.filter( Q(pk__gt = 125) ).aggregate( Count("pk")); # 聚合,返回对象,默认key为 pk__count
2
3
# 六、一对多和多对多
CREATE TABLE `employee` (
`emp_no` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(16) NOT NULL,
`birth_date` date NOT NULL,
`gender` smallint(6) NOT NULL DEFAULT '1' COMMENT 'M=1 F=2',
`hire_date` date NOT NULL,
PRIMARY KEY (`emp_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `salary` (
`id` int(11) NOT NULL,
`emp_no` int(11) NOT NULL,
`salary` decimal(10,2) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`id`),
KEY `fk_2` (`emp_no`),
CONSTRAINT `fk_2` FOREIGN KEY (`emp_no`) REFERENCES `employee` (`emp_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 一对多
# 联合主键问题
SQLAIchemy提供了联合主键支持,但是Django至今都没有支持Django只支持单一主键,这也是我提倡的。但对于本次基于Django测试的表就只能增加一个单一主键
# 特殊属性
如果增加了外键后,Django会对一端和多端增加一些新的类属性
外键: XXX_id; _state; XXX_set
查员工的工资, 可以从员工-->工资salary_set查出来,也可以直接查工资表(不推荐,因为会把关联的员工对象查出来)
emps:QuerySet = eMng.filter(Q(pk = 5));
for r in emps:
print(type(r), r.salary_set.all()); # 从员工表查工资
emps2:QuerySet = sMng.filter(Q(emp_no = 5));
for r in emps2:
print(type(r), r); # 从工资表查
2
3
4
5
6
7
# distinct
# raw
如果查询非常复杂,django可以使用原生sql
# 多对多
# 七、迁移
制作迁移文件
python manage.py makemigrations
迁移: 修改迁移存储的目标数据库
python manage.py migrate 为django中所有要迁移的模型类生成表
python manage.py migrate employee 迁移单个模型,见数据库里面的迁移数据
# 八、SqlAlchemy
pip install SQLAlchemy # pip list sqlalchemy.__version__
https://www.sqlalchemy.org/
# 1、配置数据库连接
from datetime import datetime
import sqlalchemy
from sqlalchemy import create_engine, Column, SmallInteger, Text, String, DateTime
from sqlalchemy.engine import Engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import DeclarativeMeta
# 使用pymysql驱动连接到mysql mysql+pymysql://数据库用户:数据库密码@数据库地址:3306/数据库名?charset=utf8
DB= "mysql";
HOST = "127.0.0.1"
DATABASE="test";
USER="root";
PASSWORD="123456";
PORT=3306;
CHARSET = "utf8mb4";
mysqlDriver = f'{DB}+pymysql://{USER}:{PASSWORD}@{HOST}/{DATABASE}?charset={CHARSET}' # 10.10.10.207
sqliteDriver = "sqlite:///testdb.db";
def getDB(database:str=DATABASE):
# 使用pymysql获取连接对象 getDB()() ==> 获取Connection
def getConn():
connect: Connection = pymysql.connect(
host=HOST,
user=USER,
password=PASSWORD,
db=database,
port=PORT,
charset=CHARSET);
return connect;
return getConn;
engine:Engine = create_engine( f"{DB}+pymysql://", creator=getDB(),echo=True)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
url 即数据库url, 后面可以带编码参数
echo参数为True时, 将会将engine的SQL记录到日志中 ( 默认输出到标准输出)
echo_pool 为True时,会将连接池的记录信息输出
future 使用2.0样式Engine 和 Connection API
参考:https://blog.csdn.net/u013190417/article/details/122402879
# 2、建立连接
SQLAlchemy 操作数据库,需要引入另外一个对象 Session。Session 建立与数据库的会话 (conversation),可以将其想象成对象的容器,包含的对象叫 identity map 的结构,identity map 的作用就是保证对象的唯一性。另外,Session 对 Python 对象进行状态管理,支持上下文。
首先,需要构建一个 Session 对象,比较常用的方式是使用 sessionmaker() 函数来创建一个 global 的 Session Factory,进行调用后就生成 Session 对象
from sqlalchemy.orm import sessionmaker, Session
session:Session = sessionmaker(bind=engine)();
with session:
session.commit();
2
3
4
from sqlalchemy.orm import Session
with Session(engine) as session:
spongebob = User(
name="spongebob",
fullname="Spongebob Squarepants",
addresses=[Address(email_address="spongebob@sqlalchemy.org")],
)
sandy = User(
name="sandy",
fullname="Sandy Cheeks",
addresses=[
Address(email_address="sandy@sqlalchemy.org"),
Address(email_address="sandy@squirrelpower.org"),
],
)
patrick = User(name="patrick", fullname="Patrick Star")
session.add_all([spongebob, sandy, patrick])
session.commit()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 3、声明表对象及创建表
from typing import List
from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "user_account"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(30))
fullname: Mapped[Optional[str]] = mapped_column(String(255))
addresses: Mapped[List["Address"]] = relationship(
back_populates="user", cascade="all, delete-orphan"
)
def __repr__(self) -> str:
return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"
class Address(Base):
__tablename__ = "address"
id: Mapped[int] = mapped_column(primary_key=True)
email_address: Mapped[str] = mapped_column(String(255))
user_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
user: Mapped["User"] = relationship(back_populates="addresses")
def __repr__(self) -> str:
return f"Address(id={self.id!r}, email_address={self.email_address!r})"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from sqlalchemy import create_engine, Column, SmallInteger, Text, String, DateTime
from sqlalchemy.orm import DeclarativeMeta, Mapped, mapped_column
class Buyer(Base):
#表名
__tablename__="buyer";
#表结构
cid = Column( String(32), primary_key=True, comment="买家id" );
nickName = Column(String(100), comment="昵称")
tag = Column(String(100), comment="标签")
description = Column(String(255), comment="描述")
openid = Column(String(100), comment="粉丝openid")
visitDate = Column(DateTime(), comment="访问时间")
name = Column(String(100), comment="姓名")
phone = Column(String(100), comment="号码")
employee = Column(String(100), comment="宏脉所属")
inviteEmp = Column(String(100), comment="邀请人")
inviteCid = Column(String(32), comment="邀请人cid") # Column方式创建
hmKaHao:Mapped[str] = mapped_column(String(100), comment="宏脉卡号") # mapped_column方式创建
# 添加配置设置编码
__table_args__ = { 'mysql_charset': 'utf8mb4'}
def __str__(self):
s = f'<Buyer:{self.cid} {self.nickName} {self.name} {self.hmKaHao} {self.inviteEmp} {self.employee}>';
return s;
__repr__ = __str__;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 建表: 不会先删表
#Base.metadata.drop_all(bind=engine);
#Base.metadata.create_all(engine)
2
3
参考:https://docs.sqlalchemy.org/en/20/orm/quickstart.html#declare-models
# 4、执行SQL语句, 按需开启事件是否自动提交
filter、 filter_by
#查询
#r:list = session.query(Buyer).filter(Buyer.cid == "123").all(); # query方式查询
r:Buyer = session.query(Buyer).filter(Buyer.cid == "cc1").first();
#r:ScalarResult = session.scalars( select(Buyer).where(Buyer.cid == "cc2") ) ; # select纯量方式
r:Buyer =session.get(Buyer,"cc8") #根据主键获取
#修改
r.openid="adsf";
# 删除
#session.delete(r)
# 查询多个
l.append("cc4");
l.append("cc6");
r:list = session.scalars(select(Buyer).where(Buyer.cid.in_( l ) )).all() ; # .all() .one()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
参考:https://zhuanlan.zhihu.com/p/27400862
# 12种字段类型
Integer, SmallInteger, Text, String, Float, DECIMAL, Boolean, Enum, DateTime, Date, Time
class Person(Base):
__tablename__ = "person"
id = Column(Integer , primary_key=True , autoincrement=True)
# age = Column(Integer)
# name = Column(String(20))
# price = Column(Float)
# price = Column(DECIMAL(10,2)) # 存储10位数字,小数点后最多有两位
# delete = Column(Boolean)
# sex = Column(Enum("男","女"))
# create_time = Column(Date)
# create_time = Column(DateTime)
# create_time = Column(Time)
content = Column(Text)
2
3
4
5
6
7
8
9
10
11
12
13
14
# 一对多和多对一
因为外键(ForeignKey)始终定义在多的一方.如果relationship定义在多的一方,那就是多对一,一对多与多对一的区别在于其关联(relationship)的属性在多的一方还是一的一方,如果relationship定义在一的一方那就是一对多.
class Parent(Base):#一
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
name = Column(String(64), nullable=False)
full_name = Column(String(64))
children = relationship("Child", back_populates="parent", lazy="dynamic") #一对多关系,两边都设置则为双向
class Child(Base):#多
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
name = Column(String(64), nullable=False)
full_name = Column(String(64))
parent_id = Column(Integer, ForeignKey('parent.id')) #外键
parent = relationship("Parent", order_by = 'Parent.id' back_populates="children") # 多对一关系
# 子表类中附加一个 relationship() 方法
# 并且在(父)子表类的 relationship() 方法中使用 relationship.back_populates 参数
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# cascade
- all , 所有操作都会自动处理到关联对象上.
- save-update , 关联对象自动添加到会话.
- delete , 关联对象自动从会话中删除.
- delete-orphan , 属性中去掉关联对象, 则会话中会自动删除关联对象.只适用于一对多的关系
- merge , session.merge() 时会处理关联对象.
- refresh-expire , session.expire() 时会处理关联对象.
- expunge , session.expunge() 时会处理关联对象.
- 不设置cascade,删除parent时,其关联的chilren不会删除,只会把chilren关联的parent.id置为空,设置cascade后就可以级联删除children
class Buyer(Base): # Base子类
#表名
__tablename__="buyer";
#表结构
cid: Mapped[int] = Column( String(32), primary_key=True, comment="买家id" );
nickName: Mapped[str] = Column(String(100),nullable=False, comment="昵称")
tag: Mapped[str] = Column(String(100), comment="标签")
description: Mapped[str] = Column(String(100), comment="描述")
openid: Mapped[str] = Column(String(100), comment="粉丝openid")
visitDate: Mapped[DateTime] = Column(DateTime(), comment="访问时间")
name: Mapped[str] = Column(String(100), comment="姓名")
phone: Mapped[str] = Column(String(100), comment="号码")
employee: Mapped[str] = Column(String(100), comment="宏脉所属")
inviteEmp: Mapped[str] = Column(String(100), comment="邀请人")
inviteCid: Mapped[str] = Column(String(32), index=True, comment="邀请人cid") # Column方式创建
hmKaHao:Mapped[str] = mapped_column(String(100), comment="宏脉卡号") # mapped_column方式创建
# 非表里面的字段,orm属性双向一对多 多对一, 级联删除订单 cascade="all, delete-orphan", 不设cascade,不会级联删除,cid会设置为空
orders:Mapped[List["Buyer"]] = relationship("Order",back_populates="buyer", )
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Order(Base):
# 表名
__tablename__ = "ry_order";
# 添加配置设置编码
__table_args__ = {'mysql_charset': 'utf8mb4'}
id:Mapped[int] = Column(Integer, primary_key=True, autoincrement=True, comment="id");
saleId:Mapped[str] = Column(String(100),nullable=False, comment="订单号")
wxOrderId: Mapped[str] = Column(String(100), comment="交易单号")
goods: Mapped[str] = Column(String(100),nullable=False, comment="商品")
goodsPrice: Mapped[decimal] = Column(DECIMAL(10,2), comment="") # 存储10位数字,小数点后最多有两位
buyNum: Mapped[int] = Column(Integer, comment="购买数量")
buyMoney: Mapped[decimal] = Column(DECIMAL(10,2), comment="购买金额")
payMoney: Mapped[decimal] = Column(DECIMAL(10,2), comment="订单金额")
tradeStatus = Column(String(50), comment="交易状态")
orderStatus = Column(String(50), comment="订单状态")
zhibo: Mapped[str] = Column(String(50), comment="直播间")
payDate: Mapped[DateTime] = Column(DateTime, comment="付款时间")
endDate: Mapped[DateTime] = Column(DateTime, comment="完成时间")
updateTime: Mapped[DateTime] = Column(DateTime, default=datetime.now(), comment="保存更新时间")
cid: Mapped["Buyer"] = mapped_column(String(32),ForeignKey("buyer.cid"), nullable=True, comment="买家cid");
buyer: Mapped["Buyer"] = relationship(back_populates="orders") #多对一关系
"""
订单对象 saleId,payMoney,cid,nickName,goods,buyNum,goodsPrice,buyMoney,tradeStatus,orderStatus,name,phone,payDate,endDate,zhibo,inviteEmp,inviteCid, wxOrderId
"""
def __init__(self, saleId:str=None,payMoney:decimal=None,cid:str=None,nickName:str=None,goods:str=None,buyNum:int=None,goodsPrice:decimal=None,buyMoney:decimal=None,tradeStatus:str=None,orderStatus:str=None,name:str=None,phone:str=None,
payDate:datetime=None,endDate:datetime=None,zhibo:str=None,inviteEmp:str=None,inviteCid:str=None, wxOrderId:str=None):
self.saleId = saleId;
self.payMoney = payMoney;
self.cid = cid;
self.goods = goods;
self.buyNum = buyNum;
self.goodsPrice = goodsPrice;
self.buyMoney = buyMoney;
self.tradeStatus = tradeStatus;
self.orderStatus = orderStatus;
self.zhibo = zhibo;
self.inviteEmp = inviteEmp;
self.inviteCid = inviteCid;
self.wxOrderId = wxOrderId;
if name:
self.buyer.name = name;
if phone:
self.buyer.phone = phone;
if nickName:
self.buyer.nickName = nickName;
if payDate is not None and (type(payDate) is datetime): # 日期类型转成格式化字符串
payDate = payDate.strftime("%Y-%m-%d %H:%M:%S");
self.payDate = payDate;
if endDate is not None and (type(endDate) is datetime): # 日期类型转成格式化字符串
endDate = endDate.strftime("%Y-%m-%d %H:%M:%S");
self.endDate = endDate;
def __str__(self): # 不要输出非表字段数据
s = f'Order:<{self.saleId} {self.zhibo!r} {self.goods!r} {self.cid} {self.buyer.nickName!r} {self.buyer.name!r} {self.payMoney!r} {self.buyNum!r} {self.buyMoney!r}> ';
return s;
__repr__ = __str__;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65