Python: 数据库
- TAGS: Python
配置 python 开发环境和编程思想入门
主要内容:
- MySQL安装
- MySQL服务器配置与客户端配置
- MySQL编码的设置
- MySQL环境变更查看与修改
- MySQL启动与修改
- MySQL客户端远程登录与使用
- MySQL库切换,查看库,查看表等常见指令
- MySQL脚本执行
- MySQL用户管理与权限
- MySQL日志查看与分析
关系模型和SQL语句
主要内容:
- 关系数据库基础
- SQL脚本编程基础
- MySQL 数据内置数据类型与用户数据类型
- DDL数据库与表定义语句
- 字段表级约束与字段级约束
- DML 数据表操作语句
- DQL 数据查询基本语句
- DTL 数据事务管理语句
- 多表关联 SELECT 查询
- 条件 where 查询
- groupby 分组查询
- 排序 orderby 查询
- having 条件查询与 where 区别
- 数据库单行、聚合、字符串、日期、统计等常见函数使用
- 索引
- 视图
- 存储过程和触发器
- 数据库 ACID 和 隔离机制
Python 关系数据访问编程
主要内容:
- PyMySQL 模块
- SQLAlchemy 模块详解
- ORM 对象关系映射
- 模型映射
- 数据库连接
- 会话对象
- 数据记录访问与遍历
- 复杂查询操作
- 排序、分页、聚合、分组操作
- 关联查询操作
- 事务提交与回滚操作
- 事务 ACID 和隔离机制
- 二进制与大对象数据存储与利弊
数据库
概念 数据库 按照数据结构来组织、存储、管理数据的仓库
诞生
计算机的发明是为了做科学计算的,而科学计算需要大量的数据输入和输出 早期,可以使用打孔卡片的孔、灯泡的亮灭来表示数据输入、输出
1940年,数据可以存储在磁带上,顺序的读取、写入磁带。 1956年IBM发明了磁盘驱动器这个革命性产品,支持随机访问。
随着信息化时代的到来,有了硬件存储技术的发展,有大量的数据需要存储和管理
数据库DBMS发展
萌芽期:文件管理 第一代:层次数据库、网状数据库 第二代:SQL、关系型数据库 第三代:面向对象的DBMS(OODBMS)、对象关系的DBMS(ORDBMS)
文件系统管理
磁盘上一个个文件,数据孤立,数据冗余 格式不统一,很难统一管理 无法高效查询,无法灵活查询
层次数据库
以树型结构表示实体及其之间的联系。关系只支持一对多 代表数据库IBM IMS
网状数据库
通用电气最早在1964年开发出网状数据库IDS,只能运行在GE自家的主机上 结点描述数据,结点的联系就是数据的关系 能够直接描述客观世界,可以表示实体间多种复杂关系,而这是层次数据模型无法做到的。比如,一个结点可以有多个父结点,结点之间支持可以多对多关联
关系数据库
使用行、列组成的二维表来组织数据和关系,表中行(记录)即可以描述数据实体,也可以描述实体间关系
关系模型比网状模型、层次模型更简单,不需要关系数存储的物理细节,专心于数据的逻辑构建,而且关系模型有论文的严格的数学理论基础支撑
1970年6月,IBM的研究员E.F.Codd发表了名为“A Relational Model of Data for Large Shared Data Banks”的论文,提出了关系模型的概念,奠定了关系模型的理论基础
1976年,IBM实验室System R项目,通过实现数据结构和操作来证明关系模型实用性,并直接产生了结构化查询语言SQL。1987年,SQL被ISO组织标准化
关系模型,有严格的数学基础,抽象级别较高,简单清晰,便于理解和使用
经过几十年的发展,关系数据库百花齐放,技术日臻成熟和完善
基于关系模型构建的数据库系统称为RDBMS(Relational DataBase System)
BM DB2、Oracle的Oracle和Mysql、微软的MS SQL。以前的Infomix、Sybase等
Oracle的发展
拉里·埃里森(Larry Ellison)仔细阅读了IBM的关系数据库的论文,敏锐意识到在这个研究基础上可以开发商用软件系统。他们几个创始人决定开发通用商用数据库系统Oracle,这个名字来源于他们曾给中央情报局做过的项目名 1979年发布了ORACLE 2.0版本(实际上是1.0)。1983年,Oracle v3。1984年,Oracle v4。1985年,Oraclev5。1988年,Oracle v6引入了行级锁等新技术,然而这是个不稳定的版本。直到1992年的时候,Oracle7才逐渐稳定下来,并取得巨大成功。2001年的9i版本被广泛应用 2009年4月20日,甲骨文公司宣布将以每股9.50美元,总计74亿美金收购SUN(计算机系统)公司。2010年1月成功收购 2013年,甲骨文超过IBM,成为继微软之后的全球第二大软件公司
Mysql发展
1985年几个瑞典人为大型零售商的项目设计了一种利用索引顺序存取数据的软件,这就是MyISAM的前身。1996年,MySQL 1.0发布,随后发布了3.11.1版本,并开始往其它平台移植。2000年MySQL采用GPL协议开源 MySQL 4.0开始支持MyISAM、InnoDB引擎。2005年10月,MySQL 5.0成为里程碑版本 2008年1月被Sun公司收购 2009年1月,在Oracle收购MySQL之前,Monty Widenius担心收购,就从MySQL Server 5.5开始一条新的GPL分支,起名MariaDB MySQL的引擎是插件化的,可以支持很多种引擎:
MyISASM,不支持事务,插入、查询速度快。 InnoDB,支持事务,行级锁,MySQL 5.5起的默认引擎
去IOE
它是阿里巴巴造出的概念。其本意是,在阿里巴巴的IT架构中,去掉IBM的小型机、Oracle数据库、EMC存储设备,取而代之使用自己在开源软件基础上开发的系统。传统上,一个高端大气的数据中心,IBM小型机、Oracle数据库、EMC存储设备,可以说缺一不可。而使用这些架构的企业,不但采购、维护成本极高,核心架构还掌握在他人手中 对于阿里巴巴这样大规模的互联网应用,应该采用开源、开放的系统架构。这种思路并不是阿里巴巴的新发明,国外的谷歌、Facebook、亚马逊等早已为之。只不过它们几乎一开始就有没有采用IT商业公司的架构,所以他们也不用“去IOE” 去IOE,转而使用廉价的架构,稳定性一定下降,需要较高的运维水平解决
NoSQL
NoSQL是对非SQL、非传统关系型数据库的统称。 NoSQL一词诞生于1998年,2009年这个词汇被再次提出指非关系型、分布式、不提供ACID的数据库设计模式 随着互联网时代的到来,数据爆发式增长,数据库技术发展日新月异,要适应新的业务需求。 随着移动互联网、物联网的到来,大数据的技术中NoSQL也同样重要 数据库流行度排名 2017.12
数据库流行度排名 2018.6
关系模型和SQL
关系模型和SQL 为了介绍关系模型,以MySQL数据库为例
安装
MariaDB 安装
yum list | grep mariadb mariadb-libs.x86_64 1:5.5.60-1.el7_5 @anaconda mariadb.x86_64 1:5.5.60-1.el7_5 base mariadb-bench.x86_64 1:5.5.60-1.el7_5 base mariadb-devel.i686 1:5.5.60-1.el7_5 base mariadb-devel.x86_64 1:5.5.60-1.el7_5 base mariadb-embedded.i686 1:5.5.60-1.el7_5 base mariadb-embedded.x86_64 1:5.5.60-1.el7_5 base mariadb-embedded-devel.i686 1:5.5.60-1.el7_5 base mariadb-embedded-devel.x86_64 1:5.5.60-1.el7_5 base mariadb-libs.i686 1:5.5.60-1.el7_5 base mariadb-server.x86_64 1:5.5.60-1.el7_5 base mariadb-test.x86_64 1:5.5.60-1.el7_5 base #安装mariadb 服务,会自动安装mairadb yum install mariadb-server systemctl start mariadb.service ss -tanl State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 50 *:3306 *:* #开机启动 systemctl enable mariadb.service #为了安全设置Mysql服务 mysql_secure_installation 数据库密码登录 mysql -u root -p
mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | +--------------------+ 3 rows in set (0.00 sec) #创建并授权用户 mysql> grant all on *.* to 'tom'@'%' identified by 'tom'; mysql> flush privileges; #导入测试脚本 testsql文件 # mysql -u root -p < test.sql
如果使用客户端连接数据库提示这个
证明防火墙未关闭,CentOS7可以使用systemctl stop firewalld.service来关闭,或者参考下面连接 https://www.cnblogs.com/moxiaoan/p/5683743.html
SQL语句
SQL是结构化查询语言Structured Query Language。1987年被ISO组织标准化 所有主流的关系型数据库都支持SQL,NoSQL也有很大一部分支持SQL
SQL语句分为
- DDL数据定义语言,负责数据库定义、数据库对象定义,由CREATE、ALTER与DROP三种语句组成
- DML数据操作语言,负责对数据库对象的操作,CRUD增删改查
- DCL数据控制语言,负责数据库权限访问控制,由 GRANT 和 REVOKE 两个指令组成
- TCL事务控制语言,负责处理ACID事务,支持commit、rollback指令
语言规范
- SQL语句大小写不敏感
- 一般建议,SQL的关键字、函数等大写
- SQL语句末尾应该使用分号结束
- 注释
- 多行注释/*注释内容*/
- 单行注释 – 注释内容
- MySQL 注释可以使用#
- 使用空格或缩进来提高可读性
- 命名规范
- 必须以字母开头
- 可以使用数字、#、$和_
- 不可使用关键字
DCL
GRANT授权、REVOKE撤销 GRANT ALL ON employees.* TO 'tom'@'%' IDENTIFIED by 'tom'; REVOKE ALL ON . FROM tom;
为通配符,指代任意库或者任意表。 *. 所有库的所有表; employees.* 表示employees库下所有的表 % 为通配符,它是SQL语句的通配符,匹配任意长度字符串
DDL
删除用户(慎用) DROP USER tom;
创建数据库 库是数据的集合,所有数据按照数据模型组织在数据库中 CREATE DATABASE IF NOT EXISTS test CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE DATABASE IF NOT EXISTS test CHARACTER SET utf8;
CHARACTER SET指定字符集 utf8mb4是utf8的扩展,支持4字节utf8mb4,需要MySQL5.5.3+ COLLATE指定字符集的校对规则,用来做字符串的比较的。例如a、A谁大? 删除数据库 DROP DATABASE IF EXISTS gogs;
创建表 表分为行和列,MySQL是行存数据库。数据是一行行存的,列必须固定多少列 行Row,也称为记录Record,元组 列Column,也称为字段Field、属性 字段的取值范围叫做 域Domain。例如gender字段的取值就是M或者F两个值
CREATE TABLE `employees` ( `emp_no` int(11) NOT NULL, `birth_date` date NOT NULL, `first_name` varchar(14) NOT NULL, `last_name` varchar(16) NOT NULL, `gender` enum('M','F') NOT NULL, `hire_date` date NOT NULL, PRIMARY KEY (`emp_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
反引号标注的名称,会被认为是非关键字,使用反引号避免冲突
DESC 查看列信息 {DESCRIBE | DESC} tbl_name [col_name | wild]
DESC employees; DESC employees '%name';
练习 设计一张表,记录登录账户的注册信息,应该存储用户的姓名、登录名、密码
DROP DATABASE IF EXISTS test; CREATE DATABASE IF NOT EXISTS test CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE TABLE `reg` ( `id` int(11) NOT NULL, `loginname` varchar(50) NOT NULL, `name` varchar(64) DEFAULT NULL, `password` varchar(128) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB;
关系
在关系数据库中,关系就是二维表,由行和列组成 行Row,也称为记录Record,元组 列Column,也称为字段Field、属性 字段的取值范围叫做 域Domain。例如gender字段的取值就是M或者F两个值
维数:关系的维数指关系中属性的个数 基数:元组的个数
注意在关系中,属性的顺序并不重要。理论上,元组顺序也不重要,但是由于元组顺序与存储相关,会影响查询效率
候选键
关系中,能唯一标识一条元组的属性或属性集合,称为候选键
PRIMARY KEY主键
表中一列或者多列组成唯一的key,也就是通过这一个或者多个列能唯一的标识一条记录。即被选择的候选键 主键的列不能包含空值null。主键往往设置为整型、长整型,可以为自增AUTO_INCREMENT字段 表中可以没有主键,但是,一般表设计中,往往都会有主键,以避免记录重复
Foreign KEY外键
严格来说,当一个关系中的某个属性或属性集合与另一个关系(也可以是自身)的候选键匹配时,就称作这个属性或属性集合是外键
索引Index
可以看做是一本字典的目录,为了快速检索用的。空间换时间,显著提高查询效率 可以对一列或者多列字段设定索引
主键索引,主键会自动建立主键索引,主键本身就是为了快速定位唯一记录的 唯一索引,表中的索引列组成的索引必须唯一,但可以为空,非空值必须唯一 普通索引,没有唯一性的要求,就是建了一个字典的目录而已
B+树
在MySQL中,InnoDB和MyISAM的索引数据结构可以使用Hash或BTree,默认是BTree
Hash时间复杂度是O(1),但是只能进行精确匹配,也就是Hash值的匹配,比如范围匹配就没办法了,hash值无序所以无法知道原有记录的顺序。Hash问题较多
BTree索引,以B+树为存储结构
虽然,索引可以提高查询所读,但是却影响增删改的效率,因为需要索引更新或重构。频繁出现在where子句中的列可以考虑使用索引。要避免把性别这种字段设索引
约束Constraint
为了保证数据的完整正确,数据模型还必须支持完整性约束
“必须有值”约束 某些列的值必须有值,不许为空NULL
域约束Domain Constraint
限定了表中字段的取值范围
实体完整性Entity Integrity
PRIMARY KEY约束定义了主键,就定义了主键约束。主键不重复且唯一,不能为空
引用完整性Referential Integrity *
外键定义中,可以不是引用另一张表的主键,但是,往往实际只会关注引用主键 外键:在表B中的列,引用了表A中的主键,表B中的列就是外键 A表称为主表,B表称为从表 插入规则 不需要指定 如果在表B插入一条记录,B的外键列插入了一个值,这个值必须是表A中存在的主键值 更新规则 定义外键约束时指定该规则 删除规则 定义外键约束时指定该规则 外键约束的操作 设定值 说明 CASCADE 级联,从父表删除或更新会自动删除或更新子表中匹配的行 SET NULL 从父表删除或更新行,会设置子表中的外键列为NULL,但必须保证子表列没有指定NOT NULL,也就是说子表的字段可以为NULL才行 RESTRICT 如果从父表删除主键,如果子表引用了,则拒绝对父表的删除或更新操作 NO ACTION 标准SQL的关键字,在MySQL中与RESTRICT相同。拒绝对父表的删除或更新操作 外键约束,是为了保证数据完整性、一致性,杜绝数冗余、数据错误
实体-联系E-R
数据库建立,需要收集用户需求,设计符合企业要求的数据模型。而构建这种模型需要方法,这种方法需要成为E-R实体-联系建模。也出现了一种建模语言——UML(Unified Modeling Language)统一建模语言
实体Entity:现实世界中具有相同属性的一组对象,可以是物理存在的事物或抽象的事物
联系Relationship:实体之间的关联集合
实体间联系类型
假设有实体部门,实体员工
类型 描述 解决方案 一对多联系 1:n 一个员工属于一个部门,一个部门有多个员工 员工外键;部门主键 多对多联系 m:n 一个员工属于多个部门,一个部门有多个员工 建立第三表 一对一联系 1:1 假设有实体管理者,一个管理者管理一个部门,一个部门只有一个管理者 字段建在哪张表都行 一对一关系用的较少,往往表示表A的一条记录唯一关联表B的一条记录,反之亦然 它往往是为了将一张表多列分割并产生成了多张表,合起来是完整的信息,或为了方便查询,或为了数据安全隔离一部分字段的数据等等
视图
视图,也称虚表,看起来像表。它是由查询语句生成的。可以通过视图进行CRUD操作 视图的作用
简化操作,将复杂查询SQL语句定义为视图,可以简化查询 数据安全,视图可以只显示真实表的部分列,或计算后的结果,从而隐藏真实表的数据
必学必会SQL
数据类型
MySQL中的数据类型 类型 含义 tinyint 1字节,带符号的范围是-128到127。无符号的范围是0到255。bool或boolean,就是tinyint,0表示假,非0表示真 smallint 2字节,带符号的范围是-32768到32767。无符号的范围是0到65535 int 整型,4字节,同Integer,带符号的范围是-2147483648到2147483647。无符号的范围是0到4294967295 bigint 长整型,8字节,带符号的范围是-9223372036854775808到9223372036854775807。无符号的范围是0到18446744073709551615 float 单精度浮点数精确到大约7位小数位 double 双精度浮点数精确到大约15位小数位 DATE 日期。支持的范围为’1000-01-01’到’9999-12-31’ DATETIME 支持的范围是’1000-01-01 00:00:00’到’9999-12-31 23:59:59’ TIMESTAMP 时间戳。范围是’1970-01-01 00:00:00’到2037年 char(M) 固定长度,右边填充空格以达到长度要求。M为长度,范围为0~255。M指的是字符个数 varchar(M) 变长字符串。M 表示最大列长度。M的范围是0到65,535。但不能突破行最大字节数65535 text 大文本。最大长度为65535(2^16-1)个字符 BLOB 大字节。最大长度为65535(2^16–1)字节的BLOB列 LENGTH函数返回字节数。而char和varchar定义的M是字符数限制 char可以将字符串定义为固定长度,空间换时间,效率略高;varchar为变长,省了空间
关系操作
关系:在关系数据库中,关系就是二维表 关系操作就是对表的操作
选择(selection):又称为限制,是从关系中选择出满足给定条件的元组 投影(projection):在关系上投影就是从选择出若干属性列组成新的关系 连接(join):将不同的两个关系连接成一个关系
DML–CRUD增删改查
所有操作一定要加条件
Insert语句
向表中插入一行数据,自增字段、缺省值字段、可为空字段可以不写 INSERT INTO table_name(col_name,…) VALUES (value1,…); 将select查询的结果插入到表中 INSERT INTO table_name SELECT … ; 如果主键冲突、唯一键冲突就执行update后的设置。这条语句的意思,就是主键不在新增记录,主键在就更新部分字 段 INSERT INTO table_name (col_name1,…) VALUES (value1,…) ON DUPLICATE KEY UPDATE col_name1=value1,…; 如果主键冲突、唯一键冲突就忽略错误,返回一个警告 INSERT IGNORE INTO table_name (col_name,…) VALUES (value1,…); 例: INSERT INTO reg(loginname, `name`, `password`) VALUES('tom', 'tom', 'tom');
INSERT INTO reg(if, loginname, `name`, `password`) VALUES (5, 'tom', 'tom', 'tom');
INSERT INTO reg(id, loginname, `name`, `password`) VALUED (1, 'tom', 'tom', 'tom') ON DUPLICATE KEY UPDATE name = 'jerry'
Update语句
IFNORE 意义同Insert语句 UPDATA [IGNORE] tal_name SET col_name1=expr1 [, col_name2=expr2 …] [WHERE where_definition] – 例 UPDATE reg SET name='tom· WHERE id=5;
注意这一句非常危险,会更新所有数据 UPDATE reg SET name ='ben';
更新一定要加条件 UPDATE reg SET name = 'ben', password = 'benpwd' WHRER id = 1;
Delete语句
删除符合条件的记录 DELETE FROM tal_name [WHERE where_defintion] 删除一定要有条件 DELETE FROM reg WHERE id =1;
Select语句
SELECT [DISTINCT] select_expr, … [FROM table_references [WHERE where_definition] [FROUP BY {col_name | expr | position} [ASC | DESC], … [WITH ROLLUP]] [HAVING whrer_definition] [ORDER BY {col_name | expr | position} [ASC | DESC], …] [LIMIT {[offset,} roe_count | row_count OFFSET offset}] [FOR UPDATE | LOCK IN SHARE MODE]]
FOR UPDATE会把行进行写锁定,这是排它锁
查询 查询的结果成为结果集recordset
最简单的查询 SELECT 1; SELECT * FROM employees;
字符串合并 SELECT emp_no, first_name + last_name FROM employees;
执行前
执行后
使用字符串相加函数 CONCAT SELECT emp_no, CONCAT(first_name,' ',last_name) FROM employees;
执行后
AS定义别名,可选。 写AS是一个好习惯 SELECT emp_no as en, CONCAT(first_name,' ',last_name) as name FROM employees; 执行后
Limit子句
返回5条记录, [1,5]左闭右闭 SELECT * FROM employees as emp LIMIT 5;
返回5条记录,偏移3条,(3,8]左开右闭 SELECT * FROM employees as emp LIMIT 3, 5; 等价于 SELECT * FROM employees as emp LIMIT 5 offset 3; ,
Where子句
运算符 描述符 = 等于 <> 不等于 >、 <、 >=、 <= 大于、小于、大于等于、小于等于 BETWEEN 在某个范围之内,between a and b等价于[a, b] LIKE 字符串模式匹配,%表示任意多个字符,_表示一个字符 IN 指定针对某个列的多个可能值 AND 与 OR 或 能用键匹配用键 LIKE 只能使用左前缀,尽量不使用,性能差 注意:如果很多表达式需要使用AND、OR计算逻辑表达式的值的时候,由于有结合律的问题,建议使用小括号来避免产生错误
查询条件 SELECT * FROM employees WHERE emp_no < 10015 and last_name LIKE 'P%'; SELECT * FROM employees WHERE emp_no BETWEEN 10010 AND 10015 AND last_name LIKE 'P%'; SELECT * FROM employees WHERE emp_no in (10001, 10002, 10010);
EXPLAIN 可判断查询条件性能如何 EXPLAIN SELECT emp_no AS id, birth_date, concat(FIRST_name, ' ', last_name) as name FROM employees as emp WHERE last_name='Sluis';
EXPLAIN SELECT emp_no AS id, birth_date, concat(FIRST_name, ' ', last_name) as name FROM employees as emp WHERE emp_no BETWEEN 10010 and 10015 and last_name='Sluis';
说明第二个查询条件优于第一个
ORder by 子句
对查询结果进行排序,可以升序ASC、降序DESC。默认不填为升序
升序 SELECT * FROM employees WHERE emp_no in (10001, 10002, 10010) ORDER BY emp_no; 降序 SELECT * FROM employees WHERE emp_no in (10001, 10002, 10010) ORDER BY emp_no DESC
OROER BY 先执行,在执行 LIMIT SELECT * FROM employees WHERE emp_no in (10001, 10002, 10010) ORDER BY birth_date, emp_no DESC LIMIT 1, 2;
会先按照第一条件进行排序,如果无法区分先后顺序,在使用第二条件进行排序
DISTINCT
不返回重复记录
原表
SELECT DISTINCT dept_no FROM dept_emp; 执行结果如下
SELECT DISTINCT emp_no FROM dept_emp; 执行结果如下
SELECT dept_no,emp_no FROM dept_emp; 会将dept_no与emp_no看成二元祖来筛选
聚合函数
函数 描述 COUNT(expr) 返回记录中记录的数目,如果指定列,则返回非NULL值的行数 COUNT(DISTINCT expr,[expr…]) 返回不重复的非NULL值的行数 AVG([DISTINCT] expr) 返回平均值,返回不同值的平均值 MIN(expr), MAX(expr) 最小值,最大值 SUM([DISTINCT] expr) 求和,Distinct返回不同值求和 SELECT COUNT(*), AVG(emp_no), sum(emp_no), min(emp_no), max(emp_no) FROM employees;
原表
执行后
SELECT emp_no, COUNT( emp_no ), sum( emp_no ), avg( emp_no ) AS sal_avg FROM employees WHERE emp_no > 10001 GROUP BY emp_no HAVING sal_avg > 10005 ORDER BY sal_avg DESC LIMIT 1;
执行顺序 :HAVING > select > GROUP BY > HAVING > ORDER BY
分别查询 使用Group by子句,如果有条件,使用Having子句过滤分组、聚合过的结果
原表 emp_no salary from_date to_date 10001 60117 1986-06-26 1987-06-26 10001 62102 1987-06-26 1988-06-25 10001 66074 1988-06-25 1989-06-25 10001 66596 1989-06-25 1990-06-25 10001 66961 1990-06-25 1991-06-25 10001 71046 1991-06-25 1992-06-24 10001 74333 1992-06-24 1993-06-24 10001 75286 1993-06-24 1994-06-24 10001 75994 1994-06-24 1995-06-24 10001 76884 1995-06-24 1996-06-23 10001 80013 1996-06-23 1997-06-23 10001 81025 1997-06-23 1998-06-23 10001 81097 1998-06-23 1999-06-23 10001 84917 1999-06-23 2000-06-22 10001 85112 2000-06-22 2001-06-22 10001 85097 2001-06-22 2002-06-22 10001 88958 2002-06-22 9999-01-01 10002 65828 1996-08-03 1997-08-03 10002 65909 1997-08-03 1998-08-03 10002 67534 1998-08-03 1999-08-03 10002 69366 1999-08-03 2000-08-02 10002 71963 2000-08-02 2001-08-02 10002 72527 2001-08-02 9999-01-01 10003 40006 1995-12-03 1996-12-02 10003 43616 1996-12-02 1997-12-02 10003 43466 1997-12-02 1998-12-02 10003 43636 1998-12-02 1999-12-02 10003 43478 1999-12-02 2000-12-01 10003 43699 2000-12-01 2001-12-01 10003 43311 2001-12-01 9999-01-01 10004 40054 1986-12-01 1987-12-01 10004 42283 1987-12-01 1988-11-30 10004 42542 1988-11-30 1989-11-30 10004 46065 1989-11-30 1990-11-30 10004 48271 1990-11-30 1991-11-30 10004 50594 1991-11-30 1992-11-29 10004 52119 1992-11-29 1993-11-29 10004 54693 1993-11-29 1994-11-29 10004 58326 1994-11-29 1995-11-29 10004 60770 1995-11-29 1996-11-28
聚合所有 SELECT emp_no, SUM(salary), avg(salary), count(emp_no) from salaries;
聚合被选择的记录 SELECT emp_no, SUM(salary), avg(salary), count(emp_no) from salaries WHERE emp_no < 10003;
分组查询
SELECT emp_no FROM salaries GROUP BY emp_no;
SELECT emp_no FROM salaries WHERE emp_no < 10003 GROUP BY emp_no;
按照不同emp_no分组,每组分别聚合 SELECT emp_no, SUM(salary), AVG(salary), count(emp_no) from salaries WHERE emp_no < 10003 GROUP BY emp_no;
HAVING子句对分组结果过滤 SELECT emp_no, SUM(salary), AVG(salary), count(emp_no) from salaries GROUP BY emp_no HAVING AVG(salary) > 45000
使用别名 SELECT emp_no, SUM(salary), AVG(salary) as sal_avg, count(emp_no) from salaries GROUP BY emp_no HAVING sal_avg > 45000
最后对分组过滤后的结果排序 SELECT emp_no, SUM(salary), AVG(salary) AS sal_avg, COUNT(emp_no) from salaries GROUP BY emp_no HAVING sal_avg > 60000 ORDER BY sal_avg;
分组是将数据按照指定的字段分组,最终每组只能出来一条记录。这就带来了问题,每一组谁做代表,其实谁做代表都不合适 如果只投影分组字段、聚合数据,不会有问题,如果投影非分组字段,显示的时候不能确定是组内谁的数据 分组 SELECT emp_no, MAX(salary) FROM salaries; – 10001 88958 SELECT emp_no, MIN(salary) FROM salaries; – 10001 40006
上例很好的说明了使用了聚合函数,虽然没有显式使用Group By语句,但是其实就是把所有记录当做一组,每组只能出一条,那么一组也只能出一条,所以结果就一条
但是emp_no就是非分组字段,那么它就要开始覆盖,所以,显示为10001。当求最大值的时候,正好工资表中10001的工资最高,感觉是对的。但是,求最小工资的时候,明明最小工资是10003的40006,由于emp_no不是分组字段,导致最后被覆盖为10001
SELECT emp_no, MIN(salary) FROM salaries GROUP BY emp_no;
上句才是正确的语义,按照不同员工emp_no工号分组,每一个人一组,每一个人有多个工资记录,按时每组只能按照人头出一条记录 单标较为复杂的语句 SELECT emp_no, AVG( salary ) AS avg_sal FROM salaries WHERE salary > 70000 GROUP BY emp_no HAVING avg( salary ) > 50000 ORDER BY avg_sal DESC LIMIT 1;
子查询
查询语句可以嵌套,内部查询就是子查询 子查询必须在一组小括号中 子查询中不能使用Order by SELECT
FROM employees WHERE emp_no IN ( SELECT emp_no FROM employees WHERE emp_no > 10015 ) ORDER BY emp_no DESC;
SELECT emp.emp_no, emp.first_name, gender FROM ( SELECT * FROM employees WHERE emp_no > 10015 ) AS emp WHERE emp.emp_no < 10019 ORDER BY emp_no DESC;
连接join
交叉连接 cross join 笛卡尔乘积,全部交叉 在MySQL中,CROSS JOIN从语法上说与INNER JOIN等同 没有 join 就不能用 on Join会构建一张临时表
– 工资40行 SELECT * FROM salaries;
– 20行 SELECT * FROM employees;
– 800行 SELECT * FROM employees CROSS JOIN salaries;
– 隐式连接,800行 SELECT * FROM employees, salaries;
注意:salaries和employees并没有直接的关系,做笛卡尔乘积只是为了看的清楚
内连接 inner join 可省略为join 等值连接,只选某些field相等的元组(行),使用On限定关联的结果 自然连接,特殊的等值连接,会去掉重复的列。用的少 – 内连接,笛卡尔乘积 800 行 SELECT * FROM employees JOIN salaries; SELECT * FROM employees INNER JOIN salaries;
– on等值连接 40行 SELECT * FROM employees JOIN salaries ON employees.emp_no = salaries.emp_no;
– 自然连接,去掉了重复列,且自行使用 employees.emp_no = salaries.emp_no的条件 SELECT * FROM employees NATURAL JOIN salaries;
外连接 outer join 可以省略为join 分为左外连接,即左连接;右外连接,即右连接;全外连接 左连接 (56条记录)
SELECT * FROM employees LEFT JOIN salaries ON employees.emp_no = salaries.emp_no;
右连接 (40 条记录)
SELECT * FROM employees RIGHT JOIN salaries ON employees.emp_no = salaries.emp_no;
这个右连接等价于上面的左连接(56条记录) SELECT * FROM salaries RIGHT JOIN employees ON employees.emp_no = salaries.emp_no;
左外连接、右外连接 SELECT * FROM employees RIGHT JOIN salaries ON employees.emp_no = salaries.emp_no;
结果是先employees后salaries的字段显示,Right是看表的数据的方向,从salaries往employees看,以salaries为准,它的所有数据都显示
SELECT employees.* FROM salaries RIGHT JOIN employees ON employees.emp_no salaries.emp_no WHERE salaries.emp_no IS NULL
自连接 表,自己和自己连接
select manager.* from emp manager,emp worker where manaer.empno=worker.mgr and worker.empno=1;
select manager.* from emp manager inner join emp worker on manaer.empno=worker.mgr where worker.empno=1;
存储过程、触发器 存储过程(Stored Procedure),数据库系统中,一段完成特定功能的SQL语句。编写成类似函数的方式,可以传参并调用。支持流程控制语句
触发器(Trigger),由事件触发的特殊的存储过程,例如insert数据时触发
这两种技术,虽然是数据库高级内容,性能不错,但基本很少用了
它们移植性差,使用时占用的服务器资源,排错、维护不方便
最大的原因,不太建议把逻辑放在数据库中
事务和锁
事务Transaction
InnoDB引擎,支持事务。 事务,由若干条语句组成的,指的是要做的一系列操作。
关系型数据库中支持事务,必须支持其四个属性(ACID): 特性 描述 原子性(atomicity) 一个事务是一个不可分割的工作单位,事务中包括的所有操作要么全部做完,要么什么都不做 一致性(consistency) 事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的 隔离性(isolation) 一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰 持久性(durability) 持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响 原子性,要求事务中的所有操作,不可分割,不能做了一部分操作,还剩一部分操作 一致性,多个事务并行执行的结果,应该和事务排队执行的结果一致。如果事务的并行执行和多线程读写共享资源一样不可预期,就不能保证一致性 隔离性,就是指多个事务访问共同的数据了,应该互不干扰。隔离性,指的是究竟在一个事务处理期间,其他事务能不能访问的问题 持久性,比较好理解,就是事务提交后,数据不能丢失
MySQL隔离级别
隔离性不好,事务的操作就会互相影响,带来不同严重程度的后果 首先看看隔离性不好,带来哪些问题:
更新丢失Lost Update 事务A和B,更新同一个数据,它们都读取了初始值100,A要减10,B要加100,A减去10后更新为90,B加100更新为200,A的更新丢失了,就像从来没有减过10一样 脏读 事务A和B,事务B读取到了事务A未提交的数据(这个数据可能是一个中间值,也可能事务A后来回滚事务)。事务A是否最后提交并不关心。只要读取到了这个被修改的数据就是脏读 不可重复读Unrepeatable read 事务A在事务执行中相同查询语句,得到了不同的结果,不能保证同一条查询语句重复读相同的结果就是不可以重复读 例如,事务A查询了一次后,事务B修改了数据,事务A又查询了一次,发现数据不一致了 注意,脏读讲的是可以读到相同的数据的,但是读取的是一个未提交的数据,而不是提交的最终结果。 幻读Phantom read 事务A中同一个查询要进行多次,事务B插入数据,导致A返回不同的结果集,如同幻觉,就是幻读 数据集有记录增加了,可以看做是增加了记录的不可重复读 有了上述问题,数据库就必须要解决,提出了隔离级别。 隔离级别由低到高,如下表
隔离级别 描述 READ UNCOMMITTED 读取到未提交的数据 READ COMMITTED 读已经提交的数据,ORACLE默认隔离级别 REPEATABLE READ 可以重复读,MySQL的 默认隔离级别 SERIALIZABLE 可串行化。事务间完全隔离,事务不能并发,只能串行执行 隔离级别越高,串行化越高,数据库执行效率低;隔离级别越低,并行度越高,性能越高 隔离级别越高,当前事务处理的中间结果对其它事务不可见程度越高
设置会话级或者全局隔离级别 SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
查询隔离级别 SELECT @@global.tx_isolation; SELECT @@tx_isolation;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
禁用自动提交 SET AUTOCOMMIT = 0
SERIALIZABLE,串行了,解决所有问题
REPEATABLE READ,事务A中同一条查询语句返回同样的结果,就是可以重复读数据了。例如语句为(select * from user)。解决的办法有:
对select的数据加锁,不允许其它事务删除、修改的操作 第一次select的时候,对最后一次确切提交的事务的结果做快照 解决了不可以重复读,但是有可能出现幻读。因为另一个事务可以增删数据
READ COMMITTED,在事务中,每次select可以读取到别的事务刚提交成功的新的数据。因为读到的是提交后的数据,解决了脏读,但是不能解决 不可重复读 和 幻读 的问题。因为其他事务前后修改了数据或增删了数据
READ UNCOMMITTED,能读取到别的事务还没有提交的数据,完全没有隔离性可言,出现了脏读,当前其他问题都可能出现
事务语法
START TRANSACTION或BEGIN开始一个事务,START TRANSACTION是标准SQL的语法 使用COMMIT提交事务后,变更成为永久变更 ROLLBACK可以在提交事务之前,回滚变更,事务中的操作就如同没有发生过一样(原子性) SET AUTOCOMMIT语句可以禁用或启用默认的autocommit模式,用于当前连接。SET AUTOCOMMIT = 0禁用自动提交事务。如果开启自动提交,如果有一个修改表的语句执行后,会立即把更新存储到磁盘
数据仓库和数据库的区别
本质上来说没有区别,都是存放数据的地方
但是数据库关注数据的持久化、数据的关系,为业务系统提供支持,事务支持 数据仓库存储数据的是为了分析或者发掘而设计的表结构,可以存储海量数据 数据库存储在线交易数据OLTP(联机事务处理OLTP,On-line Transaction Processing);数据仓库存储历史数据用于分析OLAP(联机分析处理OLAP,On-Line Analytical Processing)
数据库支持在线业务,需要频繁增删改查;数据仓库一般囤积历史数据支持用于分析的SQL,一般不建议删改
游标Cursor 操作查询的结果集的一种方法 可以将游标当做一个指针,指向结果集中的某一行
数据库开发
驱动
MySQL基于TCP协议之上开发,但是网络连接后,传输的数据必须遵循MySQL的协议 封装好MySQL协议的包,就是驱动程序
MySQL的驱动
MySQLdb 最有名的库。对MySQL的C Client封装实现,支持Python 2,不更新了,不支持Python3 MySQL官方Connector Mysql官网 https://dev.mysql.com/downloads/connector/ pymysql 语法兼容MySQLdb,使用Python写的库,支持Python 3
pymysql使用
安装
$ pip install pymysql
创建数据库和表
CREATE DATABASE IF NOT EXISTS school; SHOW DATABASES; USE school;
CREATE TABLE `student` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(30) NOT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
import pymysql
IP = '192.168.1.6' USERNAME = 'lqx' PASSWORD = 'lqx' DBNAME ='test' PORT = 3306
conn = None currsor = None # 对结果集的操作 try: conn = pymysql.connect(IP, USERNAME, PASSWORD, DBNAME, PORT) currsor = conn.cursor()
sql = "CREATE TABLE `student` (" \ "`id` int(11) NOT NULL AUTO_INCREMENT," \ "`name` varchar(30) NOT NULL," \ "`age` int(11) DEFAULT NULL," \ "PRIMARY KEY (`id`)" \ ") ENGINE=InnoDB DEFAULT CHARSET=utf8;"
r = currsor.execute(sql) print(r)
conn.commit() except: conn.rollback() print('roll back') finally: if currsor: currsor.close() if conn: conn.close()
连接Connect
首先,必须建立一个传输数据通加粗样式道——连接。
pymysql.connect()方法返回的是Connections模块下的Connection类实例 connect方法传参就是给Connection类的 init 提供参数 Connection初始化常用参数 说明 host 主机 user 用户名 password 密码 database 数据库 port 端口 Connection.ping()方法,测试数据库服务器是否活着。有一个参数reconnect表示断开与服务器连接是否重连。连接关闭抛出异常
import pymysql
IP = '192.168.1.6' USERNAME = 'lqx' PASSWORD = 'lqx' DBNAME ='test' PORT = 3306
conn = None try: conn = pymysql.connect(IP, USERNAME, PASSWORD, DBNAME, PORT) conn.ping(False) # ping不通则抛异常 print(conn) finally: if conn: conn.close()
<pymysql.connections.Connection object at 0x000002370C8996D8>
游标Cursor
操作数据库,必须使用游标,需要先获取一个游标对象 Connection.cursor(cursor=None) 方法返回一个新的游标对。 连接没有关闭前,游标对象可以反复使用
cursor参数,可以指定一个Cursor类。如果为None,则使用默认Cursor类
操作数据库 数据库操作需要使用Cursor类的实例,提供execute() 方法,执行SQL语句,成功返回影响的行数
新增记录
使用insert into语句插入数据
import pymysql
IP = '192.168.1.6' USERNAME = 'lqx' PASSWORD = 'lqx' DBNAME ='test' PORT = 3306
conn = None currsor = None # 对结果集的操作 try: conn = pymysql.connect(IP, USERNAME, PASSWORD, DBNAME, PORT) currsor = conn.cursor()
sql = "insert into student (name,age) values('tom', 20)" r = currsor.execute(sql) print(r)
conn.commit() except: conn.rollback() print('roll back') finally: if currsor: currsor.close() if conn: conn.close()
提交时需提供commit()方法,原因在于,在Connection类的 __init__方法的注释中有这么一句话 autocommit: Autocommit mode. None means use server default. (default: False) 一般不用开启自动提交功能,使用手动管理事务
事务管理
Connection类有三个方法:
begin 开始事务 commit 将变更提交 rollback 回滚事务 批量增加数据 import pymysql
IP = '192.168.1.6' USERNAME = 'lqx' PASSWORD = 'lqx' DBNAME ='test' PORT = 3306
conn = None currsor = None # 对结果集的操作 try: conn = pymysql.connect(IP, USERNAME, PASSWORD, DBNAME, PORT)
currsor = conn.cursor() for i in range(5): sql = "insert into student (name,age) values('ben{}', {})".format(i,20+i) r = currsor.execute(sql)
conn.commit() # 提交 except: conn.rollback() # 回滚 print('roll back') finally: if currsor: currsor.close() if conn: conn.close()
一般流程
建立连接 获取游标 执行SQL 提交事务 释放资源
查询
Cursor类的获取查询结果集的方法有fetchone()、fetchmany(size=None)、fetchall() 查询会根据Cursor游标移动 import pymysql
IP = '192.168.1.6' USERNAME = 'lqx' PASSWORD = 'lqx' DBNAME ='test' PORT = 3306
conn = None currsor = None # 对结果集的操作 try: conn = pymysql.connect(IP, USERNAME, PASSWORD, DBNAME, PORT) currsor = conn.cursor()
sql = 'select * from student' rows = currsor.execute(sql) # 返回影响的行数
print(currsor.fetchone())
print(currsor.fetchone())
print(currsor.rownumber, currsor.rowcount)
print('1 ~~~~~~~~~~
')
print(currsor.fetchmany(2))
print(currsor.rownumber, currsor.rowcount)
print('2 ~~~~~~~~~~~
')
print(currsor.fetchall()) print(currsor.rownumber, currsor.rowcount)
for x in currsor.fetchall():
print(x, '~
')
currsor.rownumber = 0
for x in currsor.fetchall():
print(x, '=
')
finally: if currsor: currsor.close() if conn: conn.close()
执行结果
(1, 'tom', 20)
(2, 'ben0', 20)
2 6
1 ~~~~~~~~~~
((3, 'ben1', 21), (4, 'ben2', 22))
4 6
2 ~~~~~~~~~~~
((5, 'ben3', 23), (6, 'ben4', 24))
6 6
(1, 'tom', 20) =
(2, 'ben0', 20) =
(3, 'ben1', 21) =
(4, 'ben2', 22) =
(5, 'ben3', 23) =
(6, 'ben4', 24) =
Process finished with exit code 0
名称 说明 fetchone() 获取结果集的下一行 fetchmany(size=None) size指定返回的行数的行,None则返回空元组 fetchall() 返回剩余所有行,如果走到末尾,就返回空元组,否则返回一个元组,其元素是每一行的记录封装的一个元组 cursor.rownumber 返回当前行号。可以修改,支持负数。 负向超界,拉到起始位置;正向超界,拉到末位位置 cursor.rowcount 返回的总行数 注意:fetch操作的是结果集,结果集是保存在客户端的,也就是说fetch的时候,查询已经结束了
先查后改 import pymysql
IP = '192.168.1.6' USERNAME = 'lqx' PASSWORD = 'lqx' DBNAME ='test' PORT = 3306
conn = None currsor = None # 对结果集的操作 try: conn = pymysql.connect(IP, USERNAME, PASSWORD, DBNAME, PORT) currsor = conn.cursor()
sql = 'select * from student' rows = currsor.execute(sql) # 返回影响的行数 userid = currsor.fetchone()[0] print(userid) sql = "update student set name='jerry' where id='{}'".format(userid) currsor.execute(sql)
conn.commit()
except: conn.rollback() finally: if currsor: currsor.close() if conn: conn.close()
带列名查询
Cursor类有一个Mixin的子类DictCursor 只需要 导入from pymysql.cursors import DictCursor库 cursor = conn.cursor(DictCursor) 就可以了 具体实现 import pymysql from pymysql.cursors import DictCursor
IP = '192.168.1.6' USERNAME = 'lqx' PASSWORD = 'lqx' DBNAME ='test' PORT = 3306
conn = None currsor = None # 对结果集的操作 try: conn = pymysql.connect(IP, USERNAME, PASSWORD, DBNAME, PORT) currsor = conn.cursor(DictCursor)
sql = 'select * from student where id={}'.format('2') currsor.execute(sql) print(currsor.fetchall())
except: conn.rollback() finally: if currsor: currsor.close() if conn: conn.close()
[{'id': 2, 'name': 'ben0', 'age': 20}]
返回一行,是一个字典 返回多行,放在列表中,元素是字典,代表一行
SQL注入攻击
找出用户id为3的用户信息的SQL语句如下 SELECT * from student WHERE id = 3
现在,要求可以找出某个id对应用户的信息,代码如下 userid = 5 # 用户id可以变 sql = 'SELECT * from student WHERE id = {}'.format(userid)
userid可以变,例如从客户端request请求中获取,直接拼接到查询字符串中 如果userid = '5 or 1=1' sql = 'SELECT * from student WHERE id = {}'.format('5 or 1=1')
运行的结果竟然是返回了全部数据
SQL注入攻击
猜测后台数据库的查询语句使用拼接字符串等方式,从而经过设计为服务端传参,令其拼接出特殊字符串的SQL语句,返回攻击者想要的结果 永远不要相信客户端传来的数据是规范及安全的!!!
如何解决注入攻击
参数化查询 可以有效防止注入攻击,并提高查询的效率 Cursor.execute(query, args=None)
args,必须是元组、列表或字典。 如果查询字符串使用%(name)s,就必须使用字典 import pymysql from pymysql.cursors import DictCursor
IP = '192.168.1.6' USERNAME = 'lqx' PASSWORD = 'lqx' DBNAME ='test' PORT = 3306
conn = None currsor = None # 对结果集的操作 try: conn = pymysql.connect(IP, USERNAME, PASSWORD, DBNAME, PORT) currsor = conn.cursor(DictCursor)
userid = '5 or 1=1' sql = 'SELECT * from student WHERE id = %s' currsor.execute(sql, (2,)) # 参数化查询 print(currsor.fetchall())
currsor.execute(sql, (userid,)) print(currsor.fetchall())
finally: if currsor: currsor.close() if conn: conn.close()
参数化查询为什么提高效率?
原因就是——SQL语句缓存 数据库服务器一般会对SQL语句编译和缓存,编译只对SQL语句部分,所以参数中就算有SQL指令也不会被当做指令执行 编译过程,需要词法分析、语法分析、生成AST、优化、生成执行计划等过程,比较耗费资源 服务端会先查找是否对同一条查询语句进行了缓存,如果缓存未失效,则不需要再次编译,从而降低了编译的成本,降低了内存消耗 可以认为SQL语句字符串就是一个key,如果使用拼接方案,每次发过去的SQL语句都不一样,都需要编译并缓存 大量查询的时候,首选使用参数化查询,以节省资源 开发时,应该使用参数化查询 注意:这里说的是查询字符串的缓存,不是查询结果的缓存
批量执行executemany() import pymysql from pymysql.cursors import DictCursor
IP = '192.168.1.6' USERNAME = 'lqx' PASSWORD = 'lqx' DBNAME ='test' PORT = 3306
conn = None currsor = None # 对结果集的操作 try: conn = pymysql.connect(IP, USERNAME, PASSWORD, DBNAME, PORT) currsor = conn.cursor()
sql = "insert into student (name,age) values(%s, %s)" currsor.executemany(sql, ( ('ben{}'.format(i), 20+i) for i in range(5) ))
conn.commit() except Exception as e: print(e) conn.rollback() finally: if currsor: currsor.close() if conn: conn.close()
上下文支持
查看连接类和游标类的源码
class Connection(object): def __enter__(self): """Context manager that returns a Cursor""" return self.cursor()
def __exit__(self, exc, value, traceback): """On successful exit, commit. On exception, rollback""" if exc: self.rollback() else: self.commit()
class Cursor(object): def __enter__(self): return self
def __exit__(self, *exc_info): del exc_info self.close()
连接类进入上下文的时候会返回一个游标对象,退出时如果没有异常会提交更改 游标类也使用上下文,在退出时关闭游标对象 import pymysql
IP = '192.168.1.6' USERNAME = 'lqx' PASSWORD = 'lqx' DBNAME ='test' PORT = 3306
conn = pymysql.connect(IP, USERNAME, PASSWORD, DBNAME, PORT) try: with conn.cursor() as cursor: sql = "insert into student (name,age) values('tom'%s, %s)"
r = cursor.executemany(sql, (("{}".format(i), 20+i) for i in range(2))) print(r) # 插入行数 with conn as cursor: cursor.execute('select * from student') print(cursor.fetchall())
except Exception as e: print(e) finally: if conn: conn.close()
执行结果
2 ((1, 'jerry', 20), (2, 'ben0', 20), (3, 'ben1', 21),\ (4, 'ben2', 22), (5, 'ben3', 23), (6, 'ben4', 24),\ (50, "tom'0", 20), (51, "tom'1", 21))
Process finished with exit code 0
换一种写法,使用连接的上下文
import pymysql
IP = '192.168.1.6' USERNAME = 'lqx' PASSWORD = 'lqx' DBNAME ='test' PORT = 3306
conn = pymysql.connect(IP, USERNAME, PASSWORD, DBNAME, PORT)
with conn as cursor: for i in range(2): sql = "insert into student (name,age) values('tom{0}', 20+{0})".format(i) rows = cursor.execute(sql)
cursor.execute('select * from student') print(cursor.fetchall())
cursor.close() conn.close()
conn的with进入是返回一个新的cursor对象,退出时,只是提交或者回滚了事务。并没有关闭cursor和conn。 不关闭cursor就可以接着用,省的反复创建它
如果想关闭cursor对象,这样写 import pymysql
IP = '192.168.1.6' USERNAME = 'lqx' PASSWORD = 'lqx' DBNAME ='test' PORT = 3306
conn = pymysql.connect(IP, USERNAME, PASSWORD, DBNAME, PORT)
with conn as cursor: with cursor: sql = "select * from student" cursor.execute(sql) print(cursor.fetchall())
conn.close()
通过上面的实验,我们应该知道,连接应该不需要反反复复创建销毁,应该是多个cursor共享一个conn
元编程
元编程概念来自LISP和smalltalk。 我们写程序是直接写代码,是否能够用代码来生成未来我们需要的代码吗?这就是元编程 例如,我们写一个类class A,能否用代码生成一个类出来?
用来生成代码的程序称为元程序metaprogram,编写这种程序就称为元编程metaprogramming Python语言能够通过反射实现元编程 Python中 所有非object类都继承自object类 所有类的类型包括type类都是type type类继承自object类 object类的类型也是type类
type类
type构建类
class type(object): def init__(cls, what, bases=None, dict=None): # known special case of type.__init """ type(object_or_name, bases, dict) type(object) -> the object's type type(name, bases, dict) -> a new type
""" pass
type(object) -> the object's type,返回对象的类型,例如type(10) type(name, bases, dict) -> a new type, 返回一个新的类型 XClass = type('myclass', (object,), {'a':100, "b":'string'}) # (objcect,) 可直接为空元组() print(XClass) print('-' * 30) print(XClass.__dict__) print('-' * 30) print(XClass.__name__) print('-' * 30) print(XClass.__bases__) print('-' * 30) print(XClass.mro())
<class 'main.myclass'>
{'a': 100, 'b': 'string', 'module': 'main',\ 'dict': <attribute 'dict' of 'myclass' objects>, \ 'weakref': <attribute 'weakref' of 'myclass' objects>, 'doc': None}
myclass
(<class 'object'>,)
[<class 'main.myclass'>, <class 'object'>]
创建更加复杂的类 def __init__(self): self.x = 1000
def show(self): print(self.__dict__)
XClass = type('myclass', (object,), {'a':100, 'show':show, 'init':__init__}) print(XClass) print('-' * 30) print(XClass.__dict__) print('-' * 30) print(XClass.__name__) print('-' * 30) print(XClass.mro()) print('-' * 30) XClass().show()
<class 'main.myclass'>
{'a': 100, 'show': <function show at 0x0000024D0C7B5158>,\ 'init': <function init at 0x0000024D0C7B51E0>, \ 'module': 'main', 'dict': <attribute 'dict' of 'myclass' objects>,\ 'weakref': <attribute 'weakref' of 'myclass' objects>, 'doc': None}
myclass
[<class 'main.myclass'>, <class 'object'>]
{'x': 1000}
总结 可以借助type构造任何类,用代码来生成代码,这就是元编程
构建元类
一个类可以继承自type类
注意不是继承自object类 class ModelMeta(type): def __new__(cls, *args, **kwargs): print(cls) print(args) print(kwargs) return super().__new__(cls, *args, **kwargs)
继承自type,ModelMeta就是元类,它可以创建出其它类
class ModelMeta(type): # 继承自type def __new__(cls, *args, **kwargs): print(cls) print(args) print(kwargs) print() return super().__new__(cls, *args, **kwargs)
class A(metaclass=ModelMeta): id = 1000
def __init__(self): print('A.init~~~')
class B(A): def __init__(self): print('B.init~~~')
C = ModelMeta('C', (), {})
class D: pass # D = type('D', (), {}) E = type('E', (), {})
class F(ModelMeta): pass
print('-' * 30) print('A', type(A), A.__bases__) print('B', type(B), B.__bases__) print('C', type(C), C.__bases__) print('~' * 30)
print('D', type(D), D.__bases__) print('E', type(E), E.__bases__) print('F', type(F), C.__bases__)
<class 'main.ModelMeta'> ('A', (), {'module': 'main', 'qualname': 'A',\ 'id': 1000, 'init': <function A.__init__ at 0x0000021CDD5B5268>}) {}
<class 'main.ModelMeta'> ('B', (<class 'main.A'>,), {'module': 'main', \ 'qualname': 'B', 'init': <function B.__init__ at 0x0000021CDD5B5598>}) {}
<class 'main.ModelMeta'> ('C', (), {}) {}
A <class 'main.ModelMeta'> (<class 'object'>,)
B <class 'main.ModelMeta'> (<class 'main.A'>,)
C <class 'main.ModelMeta'> (<class 'object'>,)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
D <class 'type'> (<class 'object'>,)
E <class 'type'> (<class 'object'>,)
F <class 'type'> (<class 'object'>,)
从运行结果还可以分析出·__new__(cls, *args, **kwargs)的参数结构:
中间是一个元组('A', (), {'init': <function A.__init__ at 0x0000000000B6E598>, 'module': 'main', 'qualname': 'A', 'id': 100}) 对应 (name, bases, dict) 修改代码如下
class ModelMeta(type): # 继承自type def __new__(cls, name, bases, dict): print('CLS : ', cls) print('NAME : ', name) print('BASES : ', bases) print('DICT : ', dict) return super().__new__(cls, name, bases, dict)
执行结果 CLS : <class 'main.ModelMeta'> NAME : A BASES : ()
DICT : {'module': 'main', 'qualname': 'A', 'id': 1000,\ 'init': <function A.__init__ at 0x000001DB6E6F5268>} CLS : <class 'main.ModelMeta'>
NAME : B BASES : (<class 'main.A'>,)
DICT : {'module': 'main', 'qualname': 'B', \ 'init': <function B.__init__ at 0x000001DB6E6F5598>}
CLS : <class 'main.ModelMeta'> NAME : C BASES : () DICT : {}
A <class 'main.ModelMeta'> (<class 'object'>,)
B <class 'main.ModelMeta'> (<class 'main.A'>,)
C <class 'main.ModelMeta'> (<class 'object'>,)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
D <class 'type'> (<class 'object'>,)
E <class 'type'> (<class 'object'>,)
F <class 'type'> (<class 'object'>,)
从运行结果可以看出,只要元类是ModelMeta,创建类对象时,就会调用ModelMeta的__new__方法 上例中,F也是元类, F –继承自–> ModelMeta –继承自–> type type(元类) 返回type。但是type被metaclass修改了的元类的类返回其元类
元类的应用
class Column: def __init__(self, fieldname=None, pk = False, nullable=False): self.fieldname = fieldname self.pk = pk self.nullable = nullable
def repr__(self): return "<{} {}>".format(__class.__name, self.fieldname)
class ModelMeta(type): def __new__(cls, name, bases, attrs:dict): print(cls) print(name, bases, attrs)
if 'tablename' not in attrs: attrs['tablename'] = name.lower() # 添加表名
primarykeys = [] for k, v in attrs.items(): if isinstance(v, Column): if v.fieldname is None or v.fieldname.strip() == '': v.fieldname = k # 字段没有名字使用属性名 if v.pk: primarykeys.append(v)
attrs['primarykeys'] = primarykeys return super().__new__(cls, name, bases, attrs)
class Base(metaclass=ModelMeta): """从ModelBase继承的类的类型都是ModelMeta"""
class Student(Base): id = Column(pk=True, nullable=False) name = Column('username', nullable=False) age = Column()
print('-' * 30) print(Student.__dict__)
元编程总结
元类是制造类的工厂,是用来构造类的类
构造好元类,就可以在类定义时,使用关键字参数·metaclass·指定元类,可以使用最原始的·metatype(name, bases, dict)·的方式构造一个类 元类的 __new__()方法中,可以获取元类信息、当前类、基类、类属性字典 元编程一般用于框架开发中
开发中除非你明确的知道自己在干什么,否则不要随便使用元编程 99%的情况下用不到元类,可能有些程序员一辈子都不会使用元类 Django、SQLAlchemy使用了元类,让我们使用起来很方便
ORM
ORM
ORM,对象关系映射,对象和关系之间的映射,使用面向对象的方式来操作数据库 关系模型和Python对象之间的映射 table => class ,表映射为类 row => object ,行映射为实例 column => property ,字段映射为属性
举例 有表student,字段为id int,name varchar,age int
映射到Python为 class Student: id = ?某类型字段 name = ?某类型字段 age = ?某类型字段
最终得到实例 class Student: def __init__(self): self.id = ? self.name = ? self.age = ?
SQLALchemy
SQLAlchemy是一个ORM框架
安装
$ pip install sqlalchemy
文档
官方文档 http://docs.sqlalchemy.org/en/latest/ 查看版本
import sqlalchemy print(sqlalchemy.__version__)# 1.3.5
开发
SQLAlchemy内部使用了连接池
创建连接
数据库连接的事情,交给引擎 from sqlalchemy import create_engine dialect+driver://username:password@host:port/database
#mysqldb的连接 mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname> engine = sqlalchemy.create_engine("mysql+mysqldb://lqx:lqx@127.0.0.1:3306/hello")
#pymysql的连接 mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>] engine = sqlalchemy.create_engine("mysql+pymysql://lqx:lqx@127.0.0.1:3306/hello") engine = sqlalchemy.create_engine("mysql+pymysql://lqx:lqx@127.0.0.1:3306/hello", echo=True)
echo=True 所有的操作都输入到日志。引擎是否打印执行的语句,调试的时候打开很方便
lazy connecting 懒连接。创建引擎并不会马上连接数据库,直到让数据库执行任务时才连接
import sqlalchemy from sqlalchemy import create_engine
IP = '192.168.1.6' USERNAME = 'lqx' PASSWORD = 'lqx' DBNAME ='test' PORT = 3306
engine = create_engine("mysql+pymysql://{}:{}@{}:{}/{}".format( USERNAME, PASSWORD, IP, PORT, DBNAME ), echo=True)
print(engine)
Engine(mysql+pymysql://lqx:***@192.168.1.6:3306/test)
Declare a Mapping创建映射
创建基类
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
创建实体类
student表 CREATE TABLE student ( id INTEGER NOT NULL AUTO_INCREMENT, name VARCHAR(64) NOT NULL, age INTEGER, PRIMARY KEY (id) )
做关系映射 import sqlalchemy from sqlalchemy import create_engine, Column, String, Integer from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base() # 基类
class Student(Base):
__tablename__= 'student'
id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(64), nullable=False) age = Column(Integer)
def repr__(self): return "<{} id={}, name={}, age={}>".format( __class.__name__, self.id, self.name, self.age )
print(Student) print(repr(Student.__table__))
<class 'main.Student'> Table('student', MetaData(bind=None), Column('id', Integer(), table=<student>, primary_key=True, nullable=False), Column('name', String(length=64), table=<student>, nullable=False), Column('age', Integer(), table=<student>), schema=None)
__tablename__指定表名 Column类指定对应的字段,必须指定
实例化
s = Student(name='tom') print(s.name) # tom s.age= 20 print(s.age) # 20
创建表
可以使用SQLAlchemy来创建、删除表 删除继承自Base的所有表 Base.metadata.drop_all(engine) 创建继承自Base的所有表 Base.metadata.create_all(engine) import sqlalchemy from sqlalchemy import create_engine, Column, String, Integer from sqlalchemy.ext.declarative import declarative_base
IP = '192.168.1.6' USERNAME = 'lqx' PASSWORD = 'lqx' DBNAME ='test' PORT = 3306 engine = create_engine("mysql+pymysql://{}:{}@{}/{}".format( USERNAME, PASSWORD, IP, PORT, DBNAME ), echo=True) # lazy 懒
print(engine)
Base = declarative_base() # 基类
class Student(Base):
__tablename__= 'student'
id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(64), nullable=False) age = Column(Integer)
def repr__(self): return "<{} id={}, name={}, age={}>".format( __class.__name__, self.id, self.name, self.age )
student = Student(id=1, name='jerry') student.name = 'tom' student.age= 20
print(student)
生产环境很少这样创建表,都是系统上线的时候由脚本生成 生成环境很少删除表,宁可废弃都不能删除
创建回话session
在一个会话中操作数据库,会话建立在连接上,连接被引擎管理。 当第一次使用数据库时,从引擎维护的连接池中获取一个连接使用.
from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine) # 工厂方法返回类 session = Session() # 实例化
session对象线程不安全。所以不同线程应该使用不用的session对象。 Session类和engine有一个就行了
CRUD操作
- 增
add():增加一个对象 add_all():可迭代对象,元素是对象 import sqlalchemy from sqlalchemy import create_engine, Column, String, Integer from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base
IP = '192.168.1.6' USERNAME = 'lqx' PASSWORD = 'lqx' DBNAME ='test' PORT = 3306 engine = create_engine("mysql+pymysql://{}:{}@{}:{}/{}".format( USERNAME, PASSWORD, IP, PORT, DBNAME ), echo=True) # lazy 懒
Base = declarative_base() # 基类
class Student(Base):
__tablename__= 'student'
id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(64), nullable=False) age = Column(Integer)
def repr__(self): return "<{} id={}, name={}, age={}>".format( __class.__name__, self.id, self.name, self.age )
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine) session = Session()
s = Student(name='tom') # 构造时传入 s.age = 20 # 属性赋值 print(s)
session.add(s) print(s) session.commit() print(s) print('
~~~~
')try: session.add_all([s]) print(s) session.commit() # 能提交成功吗? print(s) except: session.rollbake() print('roll back') raise
add_all()方法不会提交成功的,不是因为它不对,而是s,s成功提交后,s的主键就有了值,所以,只要s没有修改过,就认为没有改动。如下,s变化了,就可以提交修改了
s.name = 'jerry' # 修改 session.add_all([s])
s主键没有值,就是新增;主键有值,就是找到主键对应的记录修改
- 简单查询 query()
使用query()方法,返回一个Query对象
import sqlalchemy from sqlalchemy import create_engine, Column, String, Integer from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base
IP = '192.168.1.6' USERNAME = 'lqx' PASSWORD = 'lqx' DBNAME ='test' PORT = 3306 engine = create_engine("mysql+pymysql://{}:{}@{}:{}/{}".format( USERNAME, PASSWORD, IP, PORT, DBNAME ), echo=True) # lazy 懒
Base = declarative_base() # 基类
class Student(Base):
__tablename__= 'student'
id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(64), nullable=False) age = Column(Integer)
def repr__(self): return "<{} id={}, name={}, age={}>".format( __class.__name__, self.id, self.name, self.age )
Session = sessionmaker(bind=engine) session = Session()
students = session.query(Student) print(students.count()) for student in students: print(student) print('
~~~~~~~~~
')student = session.query(Student).get(1) # 通过主键查询 print(student)
query方法将实体类传入,返回类的对象可迭代对象,这时候并不查询。迭代它就执行SQL来查询数据库,封装数据到指定类的实例 get方法使用主键查询,返回一条传入类的一个实例
- 修改
修改需先查询,在修改,不然会调用插入方法 import sqlalchemy from sqlalchemy import create_engine, Column, String, Integer from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base
IP = '192.168.1.6' USERNAME = 'lqx' PASSWORD = 'lqx' DBNAME ='test' PORT = 3306 engine = create_engine("mysql+pymysql://{}:{}@{}:{}/{}".format( USERNAME, PASSWORD, IP, PORT, DBNAME ), echo=True) # lazy 懒
Base = declarative_base() # 基类
class Student(Base):
__tablename__= 'student'
id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(64), nullable=False) age = Column(Integer)
def repr__(self): return "<{} id={}, name={}, age={}>".format( __class.__name__, self.id, self.name, self.age )
Session = sessionmaker(bind=engine) session = Session()
student = session.query(Student).get(1) # 通过主键查询 print(student) student.name = 'ben' student.age = 30 print(student) session.add(student) session.commit()
修改前
修改后
- 删除
先看下数据库,表中有
1 ben 30 1 编写如下程序来删除,会发生什么?
import sqlalchemy from sqlalchemy import create_engine, Column, String, Integer from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base
IP = '192.168.1.6' USERNAME = 'lqx' PASSWORD = 'lqx' DBNAME ='test' PORT = 3306 engine = create_engine("mysql+pymysql://{}:{}@{}:{}/{}".format( USERNAME, PASSWORD, IP, PORT, DBNAME ), echo=True) # lazy 懒
Base = declarative_base() # 基类
class Student(Base):
__tablename__= 'student'
id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(64), nullable=False) age = Column(Integer)
def repr__(self): return "<{} id={}, name={}, age={}>".format( __class.__name__, self.id, self.name, self.age )
Session = sessionmaker(bind=engine) session = Session()
try: student = Student(id=1, name='ben', age=30)
session.delete(student) session.commit() except Exception as e: session.rollback() print('roll back') print(e)
roll back Instance '<Student at 0x1779fea9b70>' is not persisted
会产生一个异常 Instance '<Student at 0x3e654e0>' is not persisted 未持久的异常!
- 状态**
需导入from sqlalchemy.orm.state import InstanceState库
每一个实体,都有一个状态属性_sa_instance_state,其类型是sqlalchemy.orm.state.InstanceState 使用sqlalchemy.inspect(entity)函数查看状态 常见的状态值有transient、pending、persistent、deleted、detached 状态 说明 transient 实体类尚未加入到session中,同时并没有保存到数据库中 pending transient的实体被add()到session中,状态切换到pending,但它还没有flush到数据库中 persistent session中的实体对象对应着数据库中的真实记录。pending状态在提交成功后可以变成persistent状态,或者查询成功返回的实体也是persistent状态 deleted 实体被删除且已经flush但未commit完成。事务提交成功了,实体变成detached,事务失败,返回persistent状态 detached 删除成功的实体进入这个状态 新建一个实体,状态是transient临时的 一旦add()后从transient变成pending状态 成功commit()后从pending变成persistent状态 成功查询返回的实体对象,也是persistent状态
persistent状态的实体,修改依然是persistent状态 persistent状态的实体,删除后,flush但没有commit,就变成deteled状态 成功提交,变为detached状态提交失败,还原到persistent状态。flush方法,主动把改变应用到数据库中去
删除、修改操作,需要对应一个真实的记录,所以要求实体对象是persistent状态
import sqlalchemy from sqlalchemy import create_engine, Column, String, Integer from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm.state import InstanceState
IP = '192.168.1.6' USERNAME = 'lqx' PASSWORD = 'lqx' DBNAME ='test' PORT = 3306 engine = create_engine("mysql+pymysql://{}:{}@{}:{}/{}".format( USERNAME, PASSWORD, IP, PORT, DBNAME ), echo=True) # lazy 懒
Base = declarative_base() # 基类
class Student(Base):
__tablename__= 'student'
id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(64), nullable=False) age = Column(Integer)
def repr__(self): return "<{} id={}, name={}, age={}>".format( __class.__name__, self.id, self.name, self.age )
Session = sessionmaker(bind=engine) session = Session()
def getstate(instance, i): inp:InstanceState = sqlalchemy.inspect(student) states = "{}: key={}\nid={}, attached={}, transient={}," \ "pending={}, \npersistant={}, deleted={}, detached={}".format( i ,inp.key, inp.session_id, inp._attached, inp.transient, inp.pending, inp.persistent, inp.deleted, inp.detached ) print(states, end='\n~~~~~~~~~\n')
student = session.query(Student).get(1) getstate(student, 1) # persistent
try: student = Student(id=1, name='ben', age=30) getstate(student, 2) # transit
student = Student(name='tom', age=20) getstate(student, 3) # transit session.add(student) # add后变成pending getstate(student,4) # pending
session.commit() # 提交后,变成persistent getstate(student, 6) # persistent except Exception as e: session.rollback() print('roll back')
运行结果
1: key=(<class 'main.Student'>, (1,), None) id=1, attached=True, transient=False,pending=False, persistant=True, deleted=False, detached=False persistent就是key不为None,附加的,且不是删除的,有sessionid
~~~~~~~
2: key=None id=None, attached=False, transient=True,pending=False, persistant=False, deleted=False, detached=False transient的key为None,且无附加~~~~~~~
3: key=None id=None, attached=False, transient=True,pending=False, persistant=False, deleted=False, detached=False 同上~~~~~~~
4: key=None id=1, attached=True, transient=False,pending=True, persistant=False, deleted=False, detached=False add后变成pending,已附加,但是没有key,有了sessionid~~~~~~~
6: key=(<class 'main.Student'>, (2,), None) id=1, attached=True, transient=False,pending=False, persistant=True, deleted=False, detached=False 提交成功后,变成persistent,有了key~~~~~~~
Process finished with exit code 0删除状态的变化 import sqlalchemy from sqlalchemy import create_engine, Column, String, Integer from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm.state import InstanceState
IP = '192.168.1.6' USERNAME = 'lqx' PASSWORD = 'lqx' DBNAME ='test' PORT = 3306 engine = create_engine("mysql+pymysql://{}:{}@{}:{}/{}".format( USERNAME, PASSWORD, IP, PORT, DBNAME ), echo=True) # lazy 懒
Base = declarative_base() # 基类
class Student(Base):
__tablename__= 'student'
id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(64), nullable=False) age = Column(Integer)
def repr__(self): return "<{} id={}, name={}, age={}>".format( __class.__name__, self.id, self.name, self.age )
Session = sessionmaker(bind=engine) session = Session()
def getstate(instance, i): inp:InstanceState = sqlalchemy.inspect(student) states = "{}: key={}\nid={}, attached={}, transient={}," \ "pending={}, \npersistant={}, deleted={}, detached={}".format( i ,inp.key, inp.session_id, inp._attached, inp.transient, inp.pending, inp.persistent, inp.deleted, inp.detached ) print(states, end='\n~~~~~~~~~\n')
student = session.query(Student).get(1) getstate(student, 7) # persistent
try: session.delete(student) # 删除的前提是persistent
getstate(student, 8) # persistent session.flush() getstate(student, 9) # deleted session.commit() getstate(student, 13) # detached except Exception as e: session.rollback() print('roll back')
执行结果
7: key=(<class 'main.Student'>, (1,), None) id=1, attached=True, transient=False,pending=False, persistant=True, deleted=False, detached=False
~~~~~~~
8: key=(<class 'main.Student'>, (1,), None) id=1, attached=True, transient=False,pending=False, persistant=True, deleted=False, detached=False~~~~~~~
2019-06-27 16:49:04,554 INFO sqlalchemy.engine.base.Engine DELETE FROM student WHERE student.id = %(id)s 2019-06-27 16:49:04,554 INFO sqlalchemy.engine.base.Engine {'id': 1} 9: key=(<class 'main.Student'>, (1,), None) id=1, attached=True, transient=False,pending=False, persistant=False, deleted=True, detached=False delete后flush,状态变成deleted,不过是附加的~~~~~~~
2019-06-27 16:49:04,556 INFO sqlalchemy.engine.base.Engine COMMIT 一旦提交后 13: key=(<class 'main.Student'>, (1,), None) id=None, attached=False, transient=False,pending=False, persistant=False, deleted=False, detached=True 状态转为detached
~~~~~~~
- 复杂查询 filter
实体类
import sqlalchemy from sqlalchemy import create_engine, Column, String, Integer, Date, Enum, ForeignKey from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm.state import InstanceState import enum
Base = declarative_base() # 基类
IP = '192.168.1.6' USERNAME = 'lqx' PASSWORD = 'lqx' DBNAME ='test' PORT = 3306 engine = create_engine("mysql+pymysql://{}:{}@{}:{}/{}".format( USERNAME, PASSWORD, IP, PORT, DBNAME ), echo=True)
Session = sessionmaker(bind=engine) session = Session()
class MyEnum(enum.Enum): M = 'M' F = 'F'
class Employee(Base):
tablename = 'employees'
emp_no = Column(Integer, primary_key=True) birth_date = Column(Date, nullable=False) first_name = Column(String(14), nullable=False) last_name = Column(String(16), nullable=False) gender = Column(Enum(MyEnum), nullable=False) hire_date = Column(Date, nullable=False)
def repr__(self): return "<{} no={}, name={}, gender={}>".format( __class.__name__, self.emp_no, "{} {}".format( self.first_name, self.last_name), self.gender.value )
def show(emps): for x in emps: print(x) print('
~~~~~~~~~~
\n')emps = session.query(Employee).filter(Employee.emp_no > 10015) show(emps)
- 与或非
需导入from sqlalchemy import or_, and_, not_
查询 与 and 四种方式 emps = session.query(Employee).filter(Employee.emp_no > 10015, Employee.emp_no < 10018) show(emps)
emps = session.query(Employee).filter(emps.emp_no > 10015).filter(Employee.emp_no < 10018) show(emps)
emps = session.query(Employee).filter(and_(Employee.emp_no > 10015, Employee.emp_no <10018)) show(emps)
emps = session.query(Employee).filter((Employee.emp_no > 10015) & (Employee.emp_no < 10018)) show(emps)
查询 或 or 两种方法 emps = session.query(Employee).filter(or_(Employee.emp_no > 10015, Employee.emp_no < 10018)) show(emps)
emps = session.query(Employee).filter((Employee.emp_no > 10015) | (Employee.emp_no < 10018)) show(emps)
查询 非 两种方法 emps = session.query(Employee).filter(not_(Employee.emp_no < 10018)) show(emps)
emps = session.query(Employee).filter(~(Employee.emp_no > 10018)) show(emps)
in 操作 emps = session.query(Employee).filter(Employee.emp_no.in_([10015, 10018, 10020])) show(emps)
not in 操作 emps = session.query(Employee).filter(~Employee.emp_no.in_([10015, 10018, 10020])) show(emps)
emps = session.query(Employee).filter(~Employee.emp_no.notin_([10015, 10018, 10020])) show(emps)
like 字符串匹配操作 emps = session.query(Employee).filter(Employee.last_name.like('p%')) show(emps)
not like emps = session.query(Employee).filter(Employee.last_name.notlike('p%')) show(emps)
ilike可以忽略带小写匹配
- 与或非
- 排序 order_by
升序 emps = session.query(Employee).filter(Employee.emp_no > 10010).order_by(Employee.emp_no) emps = session.query(Employee).filter(Employee.emp_no > 10010).order_by(Employee.emp_no.asc()) show(emps)
降序 emps = session.query(Employee).filter(Employee.emp_no > 10010).order_by(Employee.emp_no.desc()) show(emps)
多列排序 emps = session.query(Employee).filter(Employee.emp_no > 10010).order_by(Employee.last_name).order_by(Employee.emp_no.desc()) show(emps)
- 分页 limit
emps = session.query(Employee).limit(4) show(emps)
emps = session.query(Employee).limit(4).offset(18) show(emps)
- 消费者方法
消费者方法调用后,Query对象(可迭代)就转换成了一个容器
emps = session.query(Employee) print(emps.count()) # 聚合函数count(*)的查询
print(emps.all()) # 返回列表,查不到返回空列表
print(emps.first()) # 返回首行,查不到返回None,等价limit
print(emps.one()) #如果查询结果是多行抛异常 print(emps.limit(1).one())
session.query(Employee).filter(Employee.emp_no > 10018).delete() session.commmit # 提交则删除
- 聚合、分组
需导入 from sqlalchemy import func
聚合函数
from sqlalchemy import func
query = session.query(func.count(Employee.emp_no)) print(query.all()) # 列表中一个元素 print(query.first) # 一个只有一个元组的元组 print(query.one()) # 只能有一行返回,一个元组 print(query.scalar()) # 取one()的第一个元素
print(session.query(func.max(Employee.emp_no)).scalar()) print(session.query(func.min(Employee.emp_no)).scalar()) print(session.query(func.avg(Employee.emp_no)).scalar())
分组 query = session.query(Employee.gender, func.count(Employee.emp_no)).group_by(Employee.gender).all() for g,y in query: print(g.value, y)
- 关联查询
从语句看出员工、部门之间的关系是多对多关系。 先把这些表的Model类和字段属性建立起来。
import sqlalchemy from sqlalchemy import create_engine, Column, String, Integer, Date, Enum, ForeignKey from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm.state import InstanceState from sqlalchemy import or_, and_, not_ import enum from sqlalchemy import func
Base = declarative_base() # 基类
IP = '192.168.1.6' USERNAME = 'lqx' PASSWORD = 'lqx' DBNAME ='test' PORT = 3306 engine = create_engine("mysql+pymysql://{}:{}@{}:{}/{}".format( USERNAME, PASSWORD, IP, PORT, DBNAME ), echo=True)
Session = sessionmaker(bind=engine) session = Session()
class MyEnum(enum.Enum): M = 'M' F = 'F'
def show(emps): for x in emps: print(x) print('
~~~~~~~~~~
\n')class Employee(Base):
tablename = 'employees'
emp_no = Column(Integer, primary_key=True) birth_date = Column(Date, nullable=False) first_name = Column(String(14), nullable=False) last_name = Column(String(16), nullable=False) gender = Column(Enum(MyEnum), nullable=False) hire_date = Column(Date, nullable=False)
def repr__(self): return "<{} no={}, name={}, gender={}>".format( __class.__name__, self.emp_no, "{} {}".format( self.first_name, self.last_name), self.gender.value )
class Department(Base): tablename = 'departments'
dept_no = Column(String(4), primary_key=True) dept_name = Column(String(40), nullable=False, unique=True)
def repr__(self): return "{} no={} name={}".format( type(self).__name, self.dept_no, self.dept_name)
class Dept_emp(Base): tablename = 'dept_emp'
emp_no = Column(Integer, ForeignKey('employees.emp_no', ondelete='CASCADE'), primary_key=True) dept_no = Column(String(4), ForeignKey('depatments.dept_no', ondelete='CASCADE'), primary_key=True) from_date = Column(Date, nullable=False) to_date = Column(Date, nullable=False)
def repr__(self): return "{} empno={} deptno={}".format( type(self).__name, self.emp_no, self.dept_no)
ForeignKey('employees.emp_no', ondelete='CASCADE')定义外键约束
需求:查询10010员工的所在的部门编号及员工信息
1、使用隐式内连接
results = session.query(Employee, Dept_emp).filter( Employee.emp_no
= Dept_emp.emp_no).filter(Employee.emp_no =
10010).all()show(results)
(<Employee no=10010, name=Duangkaew Piveteau, gender=F>, Dept_emp empno=10010 deptno=d004) (<Employee no=10010, name=Duangkaew Piveteau, gender=F>, Dept_emp empno=10010 deptno=d006)
这种方式会产生隐式连接的语句
SELECT * FROM employees, dept_emp WHERE employees.emp_no = dept_emp.emp_no AND employees.emp_no = 10010
使用join
results = session.query(Employee).join(Dept_emp).filter(Employee.emp_no == 10010).all()
第二种写法 results = session.query(Employee).join(Dept_emp, Employee.emp_no
= Dept_emp.emp_no).filter(Employee.emp_no =
10010).all()show(results)
这两种写法,返回都只有一行数据,为什么?
它们生成的SQL语句是一样的,执行该SQL语句返回确实是2行记录,可是Python中的返回值列表中只有一个元素? 原因在于 query(Employee) 这个只能返回一个实体对象中去,为了解决这个问题,需要修改实体类Employee,增加属性用来存放部门信息 sqlalchemy.orm.relationship(实体类名字符串) 需导入from sqlalchemy.orm import sessionmaker,relationship
from sqlalchemy.orm import sessionmaker,relationship
class Employee(Base):
tablename = 'employees'
emp_no = Column(Integer, primary_key=True) birth_date = Column(Date, nullable=False) first_name = Column(String(14), nullable=False) last_name = Column(String(16), nullable=False) gender = Column(Enum(MyEnum), nullable=False) hire_date = Column(Date, nullable=False)
departmens = relationship('Dept_emp') #
def repr__(self): # 注意增加self.dept_emps return "<{} no={}, name={}, gender={} depts={}>".format( __class.__name__, self.emp_no, "{} {}".format( self.first_name, self.last_name), self.gender.value, self.departmens )
查询信息
results = session.query(Employee).join(Dept_emp).filter( Employee.emp_no
= Dept_emp.emp_no).filter(Employee.emp_no =
10010)results = session.query(Employee).join(Dept_emp, Employee.emp_no
= Dept_emp.emp_no).fiter(Employee.emp_no =
10010)results = session.query(Employee).join(Dept_emp, (Employee.emp_no
= Dept_emp.emp_no) & (Employee.emp_no =
10010)) show(results.all()) # 打印结果第一种方法join(Dept_emp)中没有等值条件,会自动生成一个等值条件,如果后面有filter,哪怕是filter(Employee.emp_no == Dept_emp.emp_no),这个条件会在where中出现。第一种这种自动增加join的等值条件的方式不好,不要这么写 第二种方法在join中增加等值条件,阻止了自动的等值条件的生成。这种方式推荐 第三种方法就是第二种,这种方式也可以 再看一个现象
results = session.query(Employee).join(Dept_emp).filter( Employee.emp_no
= Dept_emp.emp_no).filter(Employee.emp_no =
10010)for x in results: print(x.emp_no)
可以看出只要不访问departments属性,就不会查dept_emp这张表
总结
在开发中,一般都会采用ORM框架,这样就可以使用对象操作表了 定义表映射的类,使用Column的描述器定义类属性,使用ForeignKey来定义外键约束 如果在一个对象中,想查看其它表对应的对象的内容,就要使用relationship来定义关系
是否使用外键约束?
力挺派 能使数据保证完整性一致性 弃用派 开发难度增加,大量数据的时候影响插入、修改、删除的效率。 在业务层保证数据的一致性。
Django ORM
安装Django
安装官网LTS版本
pip install django==3.2.15
Django命令 > django-admin
Type 'django-admin help <subcommand>' for help on a specific subcommand.
Available subcommands:
[django] check compilemessages createcachetable sqlmigrate sqlsequencereset squashmigrations startapp startproject test testserver Note that only Django core commands are listed as settings are not properly configured (error: Requested setting INSTALLED_APPS, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings.). startproject 作用:创建Django项目。
语法:django-admin startproject name [directory]
命令默认在当前目录创建一个文件夹,文件夹下包含manage.py文件以及工程文件夹,在工程文件夹下包含settings.py文件和其他必要文件。
django-admin startproject stduTest与django-admin startproject stduTest .的区别
不带点,会多新建一个文件夹
带点
startapp python manage.py startapp <app name>
在manage管理的这个项目下创建一个app
makemigrations python manage.py makemigrations
在manage管理的这个项目下创建迁移文件
migrate 执行迁移
python manage.py migrate
当需要迁移的时候,执行迁移,第一次建议完全迁移
migrate后接app名,便是迁移某个app
python manage.py migrate <app name>
createsuperuser 创建管理员用户和密码
python .\manage.py createsuperuser
runserver 启动一个测试用的server
python .\manage.py runserver 0.0.0.0:8080
直接就可以访问本机的8080端口
项目准备
创建项目大致文件结构 django-admin startproject stduTest .
出现了一个文件夹和一个文件
会出现一个以项目名命名的文件夹,以及一个manage.py的文件
$ python .\manage.py
Type 'manage.py help <subcommand>' for help on a specific subcommand.
Available subcommands:
[auth] changepassword createsuperuser
[contenttypes] remove_stale_contenttypes
[django] check compilemessages createcachetable dbshell diffsettings dumpdata startapp startproject test testserver
[sessions] clearsessions
[staticfiles] collectstatic findstatic runserver 创建应用 python manage.py startapp firstapp
会生成一个应用名命名的文件夹
配置 打开salary/settings.py主配置文件
修改数据库
配置修改时区
注册应用
重点
生产环境下一定要把DEBUG设置为False
DEBUG = True 注册应用,添加上自己的应用
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'firstapp', #添加上自己的刚刚创建的应用 ] 修改数据库配置
ENGINE
要使用的数据库后端。内置的数据库后端有:
'django.db.backends.postgresql' 'django.db.backends.mysql' 'django.db.backends.sqlite3' 'django.db.backends.oracle'
#文档
#加上自己数据库的配置段 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'test', #数据库名 'USER': 'root', #用户名 'PASSWORD': '111111', #密码 'HOST': '10.0.0.12', #主机 'PORT': '3306', #端口 } } 修改时区
TIME_ZONE = 'Asia/Shanghai' 修改语言
#LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = 'zh-Hans' 修改全局的编码
需要添加DEFAULT_CHARSET关键字
DEFAULT_CHARSET='utf8'
加上日志服务
在djangoproject.com上,找到文档区,然后用浏览器的搜索logging,找到范例
https://docs.djangoproject.com/zh-hans/3.2/topics/logging/#examples
写入文件的范例
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'file': { 'level': 'DEBUG', 'class': 'logging.FileHandler', 'filename': '/path/to/django/debug.log', }, }, 'loggers': { 'django': { 'handlers': ['file'], 'level': 'DEBUG', 'propagate': True, }, }, } 控制台输出的范例
import os
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console': { 'class': 'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'level': 'DEBUG', }, }, } logger也分好多种
https://docs.djangoproject.com/zh-hans/3.2/topics/logging/#id3
为了能看到sql的执行过程,我们使用django.db.backends的logger
这里我们两种handler一起使用
#必须保证DEBUG = True LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'verbose': { 'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}', 'style': '{', }, 'simple': { 'format': '{levelname} {message}', 'style': '{', }, }, 'handlers': { 'console': { 'class': 'logging.StreamHandler', 'formatter': 'simple' }, 'file': { 'class': 'logging.FileHandler', 'filename': './debug.log', 'formatter': 'verbose' } }, 'loggers': { 'django.db.backends': { 'handlers': ['console', 'file'], 'propagate': True, 'level': 'DEBUG', }, } } 配置后,就可以在控制台看到执行的SQL语句。
注意,settings.py中必须DEBUG=True,同时loggers的level是DEBUG,否则从控制台看不到SQL语句。
模型Model
字段类型
字段类 说明 AutoField 自增的整数字段。 如果不指定,django会为模型类自动增加主键字段 BooleanField 布尔值字段,True和False 对应表单控件CheckboxInput NullBooleanField 比BooleanField多一个null值 CharField 字符串,max_length设定字符长度 对应表单控件TextInput TextField 大文本字段,一般超过4000个字符使用 对应表单控件Textarea IntegerField 整数字段 BigIntegerField 更大整数字段,8字节 DecimalField 使用Python的Decimal实例表示十进制浮点数。max_digits总位数,decimal_places小数点后的位数 FloatField Python的Float实例表示的浮点数 DateField 使用Python的datetime.date实例表示的日期 auto_now=False每次修改对象自动设置为当前时间。 auto_now_add=False对象第一次创建时自动设置为当前时间。 auto_now_add、auto_now、default互斥 对应控件为TextInput,关联了一个Js编写的日历控件 TimeField 使用Python的datetime.time实例表示的时间,参数同上 DateTimeField 使用Python的datetime.datetime实例表示的时间,参数同上 FileField 一个上传文件的字段 ImageField 继承了FileField的所有属性和方法,但是对上传的文件进行校验,确保是一个有效的图片 EmailField 能做Email检验,基于CharField,默认max_length=254 GenericIPAddressField 支持IPv4、IPv6检验,缺省对应文本框输入 URLField 能做URL检验,基于基于CharField,默认max_length=200
缺省主键
缺省情况下,Django的每一个Model都有一个名为id的AutoField字段,如下
id = models.AutoField(primary_key=True)
如果显式定义了主键,这种缺省主键就不会被创建了。
字段选项
官方文档
值 说明 db_column 表中字段的名称。如果未指定,则使用属性名 primary_key 是否主键 unique 是否是唯一键 default 缺省值。这个缺省值不是数据库字段的缺省值,而是新对象产生的时候被填入的缺省值 null 表的字段是否可为null,默认为False blank Django表单验证中,如果True,则允许该字段为空。默认为False db_index 字段是否有索引 to_field 关联对象的字段。默认情况下,Django 使用相关对象的主键。如果你引用了一个不同的字段,这个字段必须有 unique=True 关系类型字段类
类 说明 ForeignKey 外键,表示一对多 ForeignKey('production.Manufacturer') 自关联ForeignKey('self') ManyToManyField 表示多对多 OneToOneField 表示一对一 一对多时,自动创建会增加_id后缀。
从一访问多,使用对象.小写模型类_set
从一访问一,使用对象.小写模型类
访问id 对象.属性_id
Model类
基类 django.db.models.Model
表名不指定默认使用<appname>_<model_name>。使用Meta类修改表名
编辑应用下的models.py文件(这里是employee/models.py)
from django.db import models
""" CREATE TABLE `employees` ( `emp_no` int(11) NOT NULL, `birth_date` date NOT NULL, `first_name` varchar(14) NOT NULL, `last_name` varchar(16) 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=utf8mb3;
"""
class Employee(models.Model): class Meta: db_table = 'employees'#定义表名
#将列名一一映射出来 emp_no = models.IntegerField(primary_key=True) birth_date = models.DateField(null=False) first_name = models.CharField(null=False, max_length=14) last_name = models.CharField(null=False, max_length=16) gender = models.SmallIntegerField(null=False) hire_date = models.DateField(null=False)
def __repr__(self):#重写可视化效果 return '< Employee {}:{}-{} >'.format(self.emp_no, self.first_name, self.last_name)
str = repr 在项目根目录编写一段测试代码
import os import django
#简单的连接前,需要安装mysqlclient,使用pip install mysqlclient os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salay.settings') django.setup()
#导入必须在连接setup之后,不然会报应用未注册 from employee.models import Employee emps=Employee.objects.all() for i in emps: print(type(i),i)
#结果 <class 'employee.models.Employee'> < Employee 10001:Georgi-Facello > <class 'employee.models.Employee'> < Employee 10002:Bezalel-Simmel > <class 'employee.models.Employee'> < Employee 10003:Parto-Bamford > <class 'employee.models.Employee'> < Employee 10004:Chirstian-Koblick > <class 'employee.models.Employee'> < Employee 10005:Kyoichi-Maliniak > <class 'employee.models.Employee'> < Employee 10006:Anneke-Preusig > <class 'employee.models.Employee'> < Employee 10007:Tzvetan-Zielinski > <class 'employee.models.Employee'> < Employee 10008:Saniya-Kalloufi > <class 'employee.models.Employee'> < Employee 10009:Sumant-Peac > <class 'employee.models.Employee'> < Employee 10010:Duangkaew-Piveteau > <class 'employee.models.Employee'> < Employee 10011:Mary-Sluis > <class 'employee.models.Employee'> < Employee 10012:Patricio-Bridgland > <class 'employee.models.Employee'> < Employee 10013:Eberhardt-Terkki > <class 'employee.models.Employee'> < Employee 10014:Berni-Genin > <class 'employee.models.Employee'> < Employee 10015:Guoxiang-Nooteboom > <class 'employee.models.Employee'> < Employee 10016:Kazuhito-Cappelletti > <class 'employee.models.Employee'> < Employee 10017:Cristinel-Bouloucos > <class 'employee.models.Employee'> < Employee 10018:Kazuhide-Peha > <class 'employee.models.Employee'> < Employee 10019:Lillian-Haddadi > <class 'employee.models.Employee'> < Employee 10020:Mayuko-Warwick > (0.001) SELECT @@SQL_AUTO_IS_NULL; args=None (0.000) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None (0.001) SELECT `employees`.`emp_no`, `employees`.`birth_date`, `employees`.`first_name`, `employees`.`last_name`, `employees`.`gender`, `employees`.`hire_date` FROM `employees`; args=() 查出了所有记录
管理器对象
Django会为模型类提供一个objects对象,它是django.db.models.manager.Manager类型,用于与数据库交互。当定义模型类的时候没有指定管理器,则Django会为模型类提供一个objects的管理器。如果在模型类中手动指定管理器后,Django不再提供默认的objects的管理器了。
管理器是Django的模型进行数据库查询操作的接口,Django应用的每个模型都至少拥有一个管理器。用户也可以自定义管理器类,继承自django.db.models.manager.Manager,实现表级别控制。
查询
查询集
查询会返回结果的集,它是django.db.models.query.QuerySet类型。
import os import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salay.settings') django.setup()
from employee.models import Employee emps=Employee.objects.all() print(type(emps))
#结果 <class 'django.db.models.query.QuerySet'> 它是惰性求值,和sqlalchemy一样。结果就是查询的集。
它是可迭代对象。
限制查询集(切片)
分页功能实现,使用限制查询集。
查询集对象可以直接使用索引下标的方式(不支持负索引),相当于SQL语句中的limit和offset子句。注意使用索引返回的新的结果集,依然是惰性求值,不会立即查询。
import os import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salay.settings') django.setup()
from employee.models import Employee emps=Employee.objects.all() print(emps[2:5])
#结果 <class 'django.db.models.query.QuerySet'> <QuerySet [< Employee 10003:Parto-Bamford >, < Employee 10004:Chirstian-Koblick >, < Employee 10005:Kyoichi-Maliniak >]> (0.000) SELECT @@SQL_AUTO_IS_NULL; args=None (0.000) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None (0.000) SELECT `employees`.`emp_no`, `employees`.`birth_date`, `employees`.`first_name`, `employees`.`last_name`, `employees`.`gender`, `employees`.`hire_date` FROM `employees` LIMIT 3 OFFSET 2; args=()
结果集方法
名称 说明 all() 所有的 filter() 过滤,返回满足条件的数据,如:mgr.filter(emp_no=10010) 如果未查询到,返回空 exlude() 排除,排除满足条件的数据,多条排除条件则为and关系 order_by() 排序,注意参数是字符串,倒序需要在字符串前面加-号,如:mgr.order_by('-emp_no') values() 返回一个对象字典的列表,列表的元素是字典,字典内的字段和值的键值对,将字段以参数的形式带入,可以控制投影的字段 mgr =Employee.objects print(mgr.all()) print(mgr.values()) print(mgr.values('pk','first_name')) print(mgr.filter(pk=10010).values()) print(mgr.exclude(emp_no=10001)) print(mgr.exclude(emp_no=10002).order_by('emp_no')) print(mgr.exclude(emp_no=10002).order_by('-pk')) print(mgr.exclude(emp_no=10002).order_by('-pk').values()) filter(pk=10)这里pk指的就是主键,不用关心主键字段名,当然也可以使用使用主键名filter(emp_no=10)
返回单个值的方法
名称 说明 get() 仅返回单个满足条件的对象 如果未能返回对象则抛出DoesNotExist异常; 如果能返回多条,抛出MultipleObjectsReturned异常 count() 返回当前查询的总条数 first() 返回第一个对象,找不到返回None last() 返回最后一个对象,返回最后一个对象,找不到返回None exists() 判断查询集中是否有数据,如果有则返回True
字段查询(Field Lookup)表达式
字段查询表达式可以作为filter()、exclude()、get()的参数,实现where子句。
语法:属性名称__比较运算符=值
注意:属性名和运算符之间使用双下划线
比较运算符如下 名称 例子 说明 exact Entry.objects.get(headline__exact="Cat bites dog") 等价于SQL语句SELECT … WHERE headline = 'Cat bites dog'; 严格等于,可省略不写 contains Entry.objects.get(headline__contains='Lennon')等价于SQL语句 SELECT … WHERE headline LIKE '%Lennon%'; 是否包含,大小写敏感,等价于like '%天%' startswith endswith Entry.objects.filter(headline__startswith='Lennon')等价于SQL语句SELECT … WHERE headline LIKE 'Lennon%'; Entry.objects.filter(headline__endswith='Lennon')等价于SQL语句SELECT … WHERE headline LIKE '%Lennon'; 以什么开头或结尾,大小写敏感 isnull isnotnull Entry.objects.filter(pub_date__isnull=True)等价于SQL语句SELECT … WHERE pub_date IS NULL; 是否为null iexact icontains istartswith iendswith i的意思是忽略大小写 in filter(emp_no__in=[1,2,3,100])等价于SQL语句SELECT … WHERE emp_no IN (1,2,3 100); 是否在指定范围数据中 gt、gte lt、lte filter(id__gt=3)等价于SQL语句SELECT … WHERE id>3; filter(id__lte=6)等价于SQL语句SELECT … WHERE id<=6; filter(pub_date__gt=datetime.date(2000,1,1))等价于SQL语句SELECT … WHERE pub_date > 2000-1-1; 大于,大于等于 小于,小于等于 year、month、dayweek_dayhour、minute、second filter(pub_date__year=2000)等价于SELECT … WHERE pub_date between '2000-01-01' and '2000-12-31' 对日期类型属性处理
Q对象
虽然Django提供传入条件的方式,但是不方便,它还提供了Q对象来解决。
Q对象是django.db.models.Q
可以使用&、|操作符来组成逻辑表达式。
~表示not。
from django.db.models import Q mgr = Employee.objects print(mgr.filter(Q(pk__lt=10006))) # 不如直接写filter(pk__lt=10006)
print(mgr.filter(pk__gt=10003).filter(pk__lt=10006)) # 与 print(mgr.filter(pk__gt=10003, pk__lt=10006)) # 与 print(mgr.filter(Q(pk__gt=10003), Q(pk__lt=10006))) print(mgr.filter(Q(pk__gt=10003) & Q(pk__lt=10006))) # 与 print(mgr.filter(pk__gt=10003) & mgr.filter(pk__lt=10006))
print(mgr.filter(pk__in=[10003, 10006])) # in print(mgr.filter(Q(pk=10003) | Q(pk=10006))) # 或 print(mgr.filter(pk=10003) | mgr.filter(pk=10006)) print(mgr.filter(~Q(pk__gt=10003))) # 非 可使用&,|和Q对象来构造复杂的逻辑表达式,可以使用一个或多个Q对象。
如果混用关键字参数和Q对象,那么Q对象必须位于关键字参数的前面。
聚合、分组
aggregate() 返回字典,方便使用,只能返回一条记录
from employee.models import Employee from django.db.models import Q, Avg, Sum, Max, Min, Count
mgr = Employee.objects print(mgr.filter(pk__gt=10010).count()) # 单值 print(mgr.filter(pk__gt=10010).aggregate(Count('pk'), Max('pk'))) # 字典 print(mgr.filter(pk__lte=10010).aggregate(Avg('pk'))) print(mgr.aggregate(Max('pk'), min=Min('pk'))) # 别名 annotate()方法用来分组聚合,返回查询集。
annotate()中结合统计方法Avg, Sum, Max, Min, Count等等做聚合,返回聚合后的查询集
只有annotate()时,就是以annotate()中的字段为聚合对象,以主键为分组基准,就是先以主键来分组,再对每个分组中annotate()中的字段进行聚合,并且投影所有字段
所得到的结果,以一个个的对象组成的列表,数据并没有显示出来,需要手动去取
emps.filter(pk__lt=10005).annotate(c=Count('gender'),a=Sum('pk')) – 等价SQL语句 SELECT `emp_no`, `birth_date`, `first_name`, `last_name`, `gender`, `hire_date`, COUNT(`gender`) AS `c`, SUM(`emp_no`) AS `a` FROM `employees` WHERE `emp_no` < 10005 GROUP BY `emp_no`; 有annotate()和values()的情况
1、如果values()在annotate()前面,则是以values()中的字段为分组基准,也就是先以values()中的字段来分组,在对各组中的annotate()中的字段进行聚合
如果values()中没有字段,则跟上面那种情况是一样的,只是返回的不再是对象,而是字典组成的查询集,数据都已经显示出来了
emps.filter(pk__lt=10005).values('gender').annotate(c=Count('gender'),a=Sum('pk')) – 等价SQL语句 SELECT `gender`, COUNT(`gender`) AS `c`, SUM(`emp_no`) AS `a` FROM `employees` WHERE `emp_no` < 10005 GROUP BY `gender` 2、如果values()在annotate()后面,则values()中的字段就表名了投影的字段,都是以主键来分组的,当values()中为空,则投影所有字段
返回的结果是字典组成的查询集,数据已经出来了
emps.filter(pk__lt=10005).annotate(c=Count('gender'),a=Sum('pk')).values('gender','c','a','first_name') – 等价SQL语句 SELECT `gender`, `first_name`, COUNT(`gender`) AS `c`, SUM(`emp_no`) AS `a` FROM `employees` WHERE `emp_no` < 10005 GROUP BY `emp_no` 3、如果annotate()前后都有values(),
前面的values()为空,无论后面的values()是否为空,都是以主键来分组,
emps.filter(pk__lt=10005).values().annotate(c=Count('gender'),a=Sum('pk')).values('c','a','first_name') – 等价SQL语句 SELECT `first_name`, COUNT(`gender`) AS `c`, SUM(`emp_no`) AS `a` FROM `employees` WHERE `emp_no` < 10005 GROUP BY `emp_no`; 后面的values()为空,无论前面的values()是否为空,都以主键来分组,并且投影所有字段
emps.filter(pk__lt=10005).values('gender').annotate(c=Count('gender'),a=Sum('pk')).values() – 等价SQL语句 SELECT `emp_no`, `birth_date`, `first_name`, `last_name`, `gender`, `hire_date`, COUNT(`gender`) AS `c`, SUM(`emp_no`) AS `a` FROM `employees` WHERE `emp_no` < 10005 GROUP BY `emp_no`; 后面的values()有主键存在,则无论前面的values()有什么,都是以主键来分组。
emps.filter(pk__lt=10005).values('gender').annotate(c=Count('gender'),a=Sum('pk')).values('c','a','first_name','emp_no') – 等价SQL语句 SELECT `first_name`, `emp_no`, COUNT(`gender`) AS `c`, SUM(`emp_no`) AS `a` FROM `employees` WHERE `emp_no` < 10005 GROUP BY `emp_no`; 后面的values()没有主键存在,也没有除了聚合后结果的字段,哪怕后面的也没有前面values()的字段,都有以 前面values()的字段来分组
emps.filter(pk__lt=10005).values('gender').annotate(c=Count('gender'),a=Sum('pk')).values('c','a') – 等价SQL语句 SELECT COUNT(`gender`) AS `c`, SUM(`emp_no`) AS `a` FROM `employees` WHERE `emp_no` < 10005 GROUP BY `gender`; 后面的values()没有主键存在,但是有其他字段存在,则是以前面values()字段和后面values()中存在的其他字段联合起来分组,也就是联合起来的字段必须完全相同才能被分到同一组
emps.filter(pk__lt=10005).values('gender').annotate(c=Count('gender'),a=Sum('pk')).values('c','a','first_name','last_name') – 等价SQL语句 SELECT `first_name`, `last_name`, COUNT(`gender`) AS `c`, SUM(`emp_no`) AS `a` FROM `employees` WHERE `emp_no` < 10005 GROUP BY `gender`, `first_name`, `last_name`; order_by()—》排序,一般在最后使用
升序就是原字段,例如order_by('c')
降序就是在原字段前面加个-,例如order_by('-c')
注意,如果order_by()中出现了主键,则annotate()前面的values()将无效,
emps.filter(pk__lt=10005).values('gender').annotate(c=Count('gender'),a=Sum('pk')).values('c','a','first_name').order_by('emp_no') – 等价SQL语句 SELECT `first_name`, COUNT(`gender`) AS `c`, SUM(`emp_no`) AS `a` FROM `employees` WHERE `emp_no` < 10005 GROUP BY `emp_no` ORDER BY `emp_no` ASC; 如果order_by()中出现了annotate()后面的values()中没有的字段,则联合起来分组的字段中会添加上这个字段,投影也会添加这个字段
emps.filter(pk__lt=10005).values('gender').annotate(c=Count('gender'),a=Sum('pk')).values('c','a','first_name').order_by('last_name') – 等价SQL语句 SELECT `first_name`, COUNT(`gender`) AS `c`, SUM(`emp_no`) AS `a` FROM `employees` WHERE `emp_no` < 10005 GROUP BY `gender`, `first_name`, `last_name` ORDER BY `last_name` ASC;
一对多
构建一对多表
员工表和员工工资表
– 员工表 CREATE TABLE `employees` ( `emp_no` int(11) NOT NULL, `birth_date` date NOT NULL, `first_name` varchar(14) NOT NULL, `last_name` varchar(16) 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=utf8mb3;
– 员工工资表 CREATE TABLE `salaries` ( `id` int(11) NOT NULL AUTO_INCREMENT, `emp_no` int(11) NOT NULL, `salary` int(11) NOT NULL, `from_date` date NOT NULL, `to_date` date NOT NULL, PRIMARY KEY (`id`), KEY `dsad` (`emp_no`), CONSTRAINT `dsad` FOREIGN KEY (`emp_no`) REFERENCES `employees` (`emp_no`) ON DELETE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=41 DEFAULT CHARSET=utf8mb3; 因为Django不支持多个主键,所以只能给组个主键降级,降为候选键,添加一个id主键,将员工号emp_no作为外键
新建模型
#在employee/models.py文件下创建工资表的模型 class Salary(models.Model): class Meta: db_table = 'salaries'
id = models.AutoField(primary_key=True, null=False) emp_no = models.ForeignKey('Employee', on_delete=models.CASCADE,db_column='emp_no') #直接将emp_no新建成外键 salary = models.IntegerField(null=False) from_date = models.DateField(null=False) to_date = models.DateField(null=False)
def __repr__(self): return '< Salary {} {} {} >'.format(self.id, self.emp_no, self.salary)
#新建属性 @property def name(self): return '{}_{}'.format(self.first_name, self.last_name)
str = repr emp_no = models.ForeignKey('Employee', on_delete=models.CASCADE,db_column='emp_no')
直接将emp_no设置成外键,查询时,如果不设置db_column,则以emp_no_id来搜索,在主表中是搜索不到的,所以需要写上真实的名字,所以写上db_column='emp_no'
外键
大概格式
emp_no = models.ForeignKey(Employee, on_delete=models.CASCADE,db_column='emp_no')
Employee是一个模型,也可以看作一个表,就是一对多的那个一,外键也会取这个模型的主键
db_column
用来设置外键的被搜索的字段,因为被设置为外键,当联合搜索时,会被自动默认为字段_id的字段。比如,emp_no被设置为外键,那么联合搜索时的主表中的字段就是emp_no_id,如果不设置db_column,就会报错,无法搜索到字段,设置db_column相当与设置成真实字段的名字
这个是设置本表的字段名
related_name
用来给主表搜索从表使用的,如果不设置,主表中就会多一个表名_set的属性,用来方便主表对从表进行操作,比如,主表的对象为Employee,那么在主表中就会产生一个employee_set的属性。如果手动设置一下,会方便自己查询。
on_delete!
位于 django.db.models
CASCADE
级联删除。Django 模拟 SQL 约束 ON DELETE CASCADE 的行为,并删除包含 ForeignKey 的对象。Model.delete()不会在相关模型上调用,但 会为所有已删除的对象发送pre_delete和 post_delete信号。
PROTECT
通过提高ProtectedError的子类来 防止删除引用的对象 django.db.IntegrityError。
测试
import os import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salay.settings') django.setup()
from employee.models import Employee, Salary from django.db.models import Q, Count, Sum
emps = Employee.objects semps = Salary.objects print(semps.all()) 会进入主表employees表中反复搜索
特殊属性
一端对多端
一端Empoyee类中会增加一个特殊属性'salary_set'
特殊属性'salary_set'可以用来查询多端表属性
例如:
from employee.models import Employee, Salary from django.db.models import Q, Count, Sum
emps = Employee.objects semps = Salary.objects
print(emps.get(emp_no=10004).abc.filter(from_date__year=1995)) #转换为SQL语句 """ SELECT `employees`.`emp_no`, `employees`.`birth_date`, `employees`.`first_name`, `employees`.`last_name`, `employees`.`gender`, `employees`.`hire_date` FROM `employees` WHERE `employees`.`emp_no` = 10004;
SELECT `salaries`.`id`, `salaries`.`emp_no`, `salaries`.`salary`, `salaries`.`from_date`, `salaries`.`to_date` FROM `salaries` WHERE ( `salaries`.`emp_no` = 10004 AND `salaries`.`from_date` BETWEEN '1995-01-01' AND '1995-12-31' ); """
print(emps.get(emp_no=10004).abc.filter(from_date__gt=datetime.date(1993,1,1))) #转换为SQL语句 """ SELECT `employees`.`emp_no`, `employees`.`birth_date`, `employees`.`first_name`, `employees`.`last_name`, `employees`.`gender`, `employees`.`hire_date` FROM `employees` WHERE `employees`.`emp_no` = 10004;
SELECT `salaries`.`id`, `salaries`.`emp_no`, `salaries`.`salary`, `salaries`.`from_date`, `salaries`.`to_date` FROM `salaries` WHERE ( `salaries`.`emp_no` = 10004 AND `salaries`.`from_date` > '1993-01-01' ); """
print(emps.get(last_name='Simmel').abc.filter(from_date__gt=datetime.date(2000,1,1))) #转化为SQL语句 """ SELECT `employees`.`emp_no`, `employees`.`birth_date`, `employees`.`first_name`, `employees`.`last_name`, `employees`.`gender`, `employees`.`hire_date` FROM `employees` WHERE `employees`.`last_name` = 'Simmel';
SELECT `salaries`.`id`, `salaries`.`emp_no`, `salaries`.`salary`, `salaries`.`from_date`, `salaries`.`to_date` FROM `salaries` WHERE ( `salaries`.`emp_no` = 10002 AND `salaries`.`from_date` > '2000-01-01' ); """ abc是对特殊属性的改名emp_no = models.ForeignKey('Employee', on_delete=models.CASCADE,db_column='emp_no',related_name='abc')
在创建模型,设置外键的时候,对特殊属性进行命名为abc
多端Salary类中会增加一个特殊属性'emp_no_id'
查询
例子:查询员工10004的所有工资
从一端往多端上查
需要借助特殊属性salary_set,来达到查询到从表的目的
emps.get(emp_no=10004).salary_set.all() 转换为SQL语句
SELECT `employees`.`emp_no`, `employees`.`birth_date`, `employees`.`first_name`, `employees`.`last_name`, `employees`.`gender`, `employees`.`hire_date` FROM `employees` WHERE `employees`.`emp_no` = 10004; SELECT `salaries`.`id`, `salaries`.`emp_no`, `salaries`.`salary`, `salaries`.`from_date`, `salaries`.`to_date` FROM `salaries` WHERE `salaries`.`emp_no` = 10004; 也可以给特殊属性salary_set命名
在employee/models.py中创建模型时,给外键添加一个related_name='abc',就会给Employ添加一个abc的属性,相当于salary_set
emp_no = models.ForeignKey('Employee', on_delete=models.CASCADE,db_column='emp_no',related_name='abc') print(*Employee.__dict__.items(),sep='\n')
('abc', <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x000001894FCD7D68>)
从多端往一端查询
semps = Salary.objects
x=semps.filter(emp_no=10004).values('id','emp_no_id','salary') for i in x: print(i) 转换成SQL语句
SELECT `salaries`.`id`, `salaries`.`emp_no`, `salaries`.`salary` FROM `salaries` WHERE `salaries`.`emp_no` = 10004;
distinct–—》去重
目标,查询工资salary>60000的所有员工信息
子查询的方式
emps = Employee.objects
emp_nos=semps.filter(salary__gt=60000).values('emp_no').distinct() #查工资表salaries所有salary大于60000的记录,根据emp_no投影,再去重,获得一个员工emp_no的查询集
print(emps.filter(emp_no__in=emp_nos).values('emp_no','first_name','last_name')) #可以直接把查询集放到条件中,用来过滤employees表,最后投影出想要的信息 转换为SQL语句
SELECT `employees`.`emp_no`, `employees`.`first_name`, `employees`.`last_name` FROM `employees` WHERE `employees`.`emp_no` IN ( SELECT DISTINCT U0.`emp_no` FROM `salaries` U0 WHERE U0.`salary` > 60000 ); 会发现,已经转换成了子查询的结构
非子查询的方式
emps = Employee.objects
emp_nos=semps.filter(salary__gt=60000).values('emp_no').distinct() #这里还是一样的,去重后的结果是一个emp_no的查询集
print(emps.filter(emp_no__in=(i['emp_no'] for i in emp_nos)).values('emp_no','first_name','last_name')) #将查询集转换成元组,再查询,就不会成为子查询 转换为SQL语句
SELECT DISTINCT `salaries`.`emp_no` FROM `salaries` WHERE `salaries`.`salary` > 60000;
SELECT `employees`.`emp_no`, `employees`.`first_name`, `employees`.`last_name` FROM `employees` WHERE `employees`.`emp_no` IN (10001, 10002, 10004);
raw的使用—>执行原生SQL语句
-- 将salaries表和employees表合并,然后查询salary大于55000的员工名
MariaDB [test]> select distinct e.first_name,e.last_name from salaries as s join employees as e on s.emp_no=e.emp_no where s.salary>55000;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| Georgi | Facello |
| Bezalel | Simmel |
| Chirstian | Koblick |
+------------+-----------+
3 rows in set (0.000 sec)
使用raw,在django中执行
from employee.models import Employee, Salary
emps = Employee.objects semps = Salary.objects
sql = """ SELECT DISTINCT e.emp_no, e.first_name, e.last_name FROM salaries AS s JOIN employees AS e ON s.emp_no = e.emp_no WHERE s.salary > 55000; """
x=emps.raw(sql) print(type(x)) print(list(x)) for i in x: print(i.first_name,i.gender)
#运行结果 <class 'django.db.models.query.RawQuerySet'> [< Employee 10001:Georgi-Facello >, < Employee 10002:Bezalel-Simmel >, < Employee 10004:Chirstian-Koblick >] Georgi 1 Bezalel 2 Chirstian 1 因为执行的SQL语句必须包含管理器对象的主键,也就是emps = Employee.objects,所以需要加上e.emp_no
而查询的结果,x,属于Raw类型的查询集,而产生的对象则是管理器对象 (emps)类型的对象,而最后可以调用的字段包含了管理器对象(emps)所有的字段和sql语句中投影的其他表的字段,没有加入投影的其他表的字段,是无法查询的
例如
sql = """ SELECT DISTINCT s.emp_no, s.salary, e.first_name, e.last_name FROM salaries AS s JOIN employees AS e ON s.emp_no = e.emp_no WHERE s.salary > 55000; """
x=emps.raw(sql) print(type(x)) for i in x: print(i.first_name,i.gender,i.salary) #这里查询了前面投影的字段,也有没有投影的字段
多对多
三个表
– 员工表 CREATE TABLE `employees` ( `emp_no` int(11) NOT NULL, `birth_date` date NOT NULL, `first_name` varchar(14) NOT NULL, `last_name` varchar(16) 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=utf8mb3;
– 部门表 CREATE TABLE `departments` ( `dept_no` char(4) NOT NULL, `dept_name` varchar(40) NOT NULL, PRIMARY KEY (`dept_no`), UNIQUE KEY `dept_name` (`dept_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
– 第三张表 CREATE TABLE `dept_emp` ( `id` int(11) NOT NULL AUTO_INCREMENT, `emp_no` int(11) NOT NULL, `dept_no` char(4) NOT NULL, `from_date` date NOT NULL, `to_date` date NOT NULL, PRIMARY KEY (`id`), KEY `dept_no` (`dept_no`), KEY `employee` (`emp_no`), CONSTRAINT `deptart` FOREIGN KEY (`dept_no`) REFERENCES `departments` (`dept_no`) ON DELETE CASCADE, CONSTRAINT `employee` FOREIGN KEY (`emp_no`) REFERENCES `employees` (`emp_no`) ON DELETE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb3;
创建模型
在employee/models.py文件下
class Employee(models.Model): class Meta: db_table = 'employees'
emp_no = models.IntegerField(primary_key=True) birth_date = models.DateField(null=False) first_name = models.CharField(null=False, max_length=14) last_name = models.CharField(null=False, max_length=16) gender = models.SmallIntegerField(null=False) hire_date = models.DateField(null=False)
def __repr__(self): return '< Employee {}:{}-{} >'.format(self.emp_no, self.first_name, self.last_name)
@property def name(self): return '{}_{}'.format(self.first_name, self.last_name)
str = repr
class Department(models.Model): class Meta: db_table = 'departments'
dept_no = models.CharField(primary_key=True, null=False, max_length=4) dept_name = models.CharField(unique=True, null=False, max_length=40)
def __repr__(self): return '< Dept {} {} >'.format(self.dept_no, self.dept_name)
str = repr
class Dept_emp(models.Model): class Meta: db_table = 'dept_emp'
id = models.AutoField(primary_key=True) emp_no = models.ForeignKey('Employee', on_delete=models.CASCADE,db_column='emp_no',related_name='en') dept_no = models.ForeignKey('Department', on_delete=models.CASCADE,db_column='dept_no',related_name='de') from_date = models.DateField(null=False) to_date = models.DateField(null=False)
def __repr__(self): return '< Dept_emp {} {} {} >'.format(self.id, self.emp_no_id, self.dept_no_id) #这里改成emp_no_id 是为了防止二次查询造成资源浪费
str = repr
使用查询
查询emp_no=10010的部门编号以及部门名称和员工信息
import os import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salay.settings') django.setup()
from employee.models import Employee,Department,Dept_emp
emgr = Employee.objects dmgr=Department.objects d_e_mgr=Dept_emp.objects print(emgr.filter(emp_no=10010).values().first()) dept_nos=[i.dept_no_id for i in emgr.get(emp_no=10010).en.all()] print(dmgr.filter(dept_no__in=dept_nos).values())
#运行结果 {'emp_no': 10010, 'birth_date': datetime.date(1963, 6, 1), 'first_name': 'Duangkaew', 'last_name': 'Piveteau', 'gender': 2, 'hire_date': datetime.date(1989, 8, 24)} <QuerySet [{'dept_no': 'd004', 'dept_name': 'Production'}, {'dept_no': 'd006', 'dept_name': 'Quality Management'}]>
显示事务处理
因为需要数据的完整性,所以需要显式控制事务
from django.db.transaction import atomic
两种方法
作为装饰器,为整个视图函数 from django.db import transaction
@transaction.atomic def viewfunc(request):
do_stuff() 作为上下文,对部分代码执行显示事务 from django.db import transaction
def viewfunc(request):
do_stuff()
with transaction.atomic():
do_more_stuff()
测试代码
import os import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blog.settings') django.setup()
from post.models import * from django.contrib.auth.models import User from django.db.transaction import atomic post=Post() content=Content() try: post.title='sadasd' post.author_id=User.objects.get(id=2) with atomic(): post.save() content.id=post raise BufferError content.content='qwer' content.save() except Exception as e: print(e) 当第一个表数据插入完成,抛出异常,事务会回滚
对数据库的增删改查
增加
不指定主键,就默认为插入数据
例如,插入一条,作者为admin,标题为abcd 的数据
from post.models import * from django.contrib.auth.models import User from django.db.transaction import atomic
post = Post() content = Content() try: post.title = 'abcd' post.author_id = User.objects.get(username='admin') with atomic(): post.save() content.id = post content.content = 'qwer' content.save() except Exception as e: print(e)
修改
先查出title='aaaaa'的post表中的数据,再把数据组成的对象与content表对应,然后修改content字段
先查再修改
多表修改
from post.models import * from django.contrib.auth.models import User from django.db.transaction import atomic
content = Content() try: post = Post.objects.filter(title='aaaaa').first() with atomic(): content.id = post content.content = '1111111' content.save() except Exception as e: print(e)
单表本表修改
from post.models import * from django.contrib.auth.models import User from django.db.transaction import atomic import datetime try: post = Post.objects.filter(title='aaaaa').first() with atomic(): post.postdate=datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=8))) post.save() except Exception as e: print(e)
删除
当有外键,且从表有数据
借助模板中从表中定义的related_name='content',用来删除从表中的数据,再删除主表的数据
通过related_name='content'返回的数据是一个关联管理器对象,需要转换成数据对象,再进行删除
from post.models import * from django.contrib.auth.models import User from django.db.transaction import atomic import datetime content=Content() try: post = Post.objects.filter(title='aaaaa').first() with atomic(): post.content.first().delete() post.delete() except Exception as e: print(e) 手动找到从表中的数据,然后删除 删除单个 from post.models import * from django.contrib.auth.models import User from django.db.transaction import atomic import datetime content=Content() try: post = Post.objects.filter(title='aaaaa').first() with atomic(): content.id=post content.delete() post.delete() except Exception as e: print(e) 删除多个 from post.models import * from django.contrib.auth.models import User from django.db.transaction import atomic import datetime content=Content() try: post = Post.objects.filter(title='sadasd').all() with atomic(): for i in post: content.id=i content.delete() post.delete() except Exception as e: print(e) 以上都是查从表,然后删主表
现在的需求时知道从表内容,要连主表一起删
由从表删主表
import datetime, os, django
print(datetime.date.fromtimestamp(datetime.datetime.now().timestamp())) os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blog.settings') django.setup(set_prefix=False)
from post.models import Post, Content from django.db.transaction import atomic
cont = Content()
with atomic(): content = Content.objects.filter(content="内容") post = Post() for i in content: post = Post.objects.filter(id=i.id_id).first() i.delete() post.delete()
迁移
如果建立好模型类,想从这些类来生成数据库的表,使用下面语句。
生成迁移文件
python manage.py makemigrations
会在app名下的migrations文件夹下生成一个迁移文件
例如上图的0001_initial.py文件
执行迁移
python manage.py migrate 第一次迁移,最好全部迁移
后续再指定app进行迁移
python manage.py migrate employee
Django后台管理
创建管理员
$(python_blog) PS D:\python_blog> python manage.py createsuperuser Username (leave blank to use 'hexug'): admin Email address: admin@123.com Password: Password (again): This password is too short. It must contain at least 8 characters. This password is too common. This password is entirely numeric. Bypass password validation and create user anyway? [y/N]: y Superuser created successfully. 创建管理员,实际上是在auth_user表中增加一个特殊用户
本地化
settings.py中设置语言、时区 语言名称可以查看 django\contrib\admin\locale 目录
LANGUAGE_CODE = 'zh-Hans' #'en-us' 中文简体 USE_TZ = True TIME_ZONE = 'Asia/Shanghai' #'UTC'
启动WEB Server
$ python manage.py runserver
Watching for file changes with StatReloader Performing system checks…
System check identified no issues (0 silenced). November 22, 2021 - 18:29:38 Django version 3.2.24, using settings 'blog.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CTRL-BREAK.
默认启动8000端口
登录后台管理
因为在urls.py文件中默认配置了一条,当访问根目录下的admin自动跳准的路由,所以后台登录地址http://127.0.0.1:8000/admin
表明成功登录
登录后如果希望后来能够管理这些表,就需要在admin文件中注册对应的models
举例
models.py
from django.db import models
class Us(models.Model): class meta: db_table="a" pass 在admin.py文件中注册
from .models import Us admin.site.register(Us) 在后台web端查看