Python全栈(三)数据库优化之6.Python操作MySQL和视图

一、封装MySQL-DB类

用面向对象的思想,封装DB类。
实现初始化(连接)、查询、关闭等操作。

from pymysql import *

class MyDB(object):
    def __init__(self):
        self.conn()

    def conn(self):
        '''连接'''
        try:
            self.conn = connect(
                host='127.0.0.1',
                port=3306,
                db='demo',
                user='root',
                passwd='root',
                charset='utf8'
            )
        except Exception as e:
            print(e)

    def get_one(self):
        '''单个记录查询'''
        sql = 'select * from students;'
        #获取游标
        cur = self.conn.cursor()
        #执行SQL语句
        cur.execute(sql)
        #获取执行结果
        result = cur.fetchone()
        cur.close()
        return result

    def get_many(self):
        '''多个记录查询'''
        sql = 'select * from students;'
        #获取游标
        cur = self.conn.cursor()
        #执行SQL语句
        cur.execute(sql)
        #获取执行结果
        result = cur.fetchmany()
        cur.close()
        return result

    def get_all(self):
        '''所有记录查询'''
        sql = 'select * from students;'
        #获取游标
        cur = self.conn.cursor()
        #执行SQL语句
        cur.execute(sql)
        #获取执行结果
        result = cur.fetchall()
        cur.close()
        return result

    def __del__(self):
        '''关闭连接,释放'''
        self.conn.close()

def main():
    obj = MyDB()
    res1 = obj.get_one()
    print(res1)
    res2 = obj.get_all()
    for r in res2:
        print(r)

if __name__ == '__main__':
    main()

进行实例化并测试得,

(1, 'Tom', 18, 'male', 1, datetime.date(1999, 9, 9), 1, Decimal('165.00'))
(1, 'Tom', 18, 'male', 1, datetime.date(1999, 9, 9), 1, Decimal('165.00'))
(2, 'Jerry', 19, 'male', 3, datetime.date(1999, 10, 9), 1, Decimal('157.00'))
(3, 'Nancy', 17, 'male', 2, datetime.date(1999, 8, 9), 1, Decimal('165.00'))
(4, 'John', 19, 'secret', 1, datetime.date(1999, 7, 9), 1, Decimal('168.00'))
(6, 'John', 16, 'secret', 3, datetime.date(1999, 5, 9), 1, Decimal('165.00'))
(7, 'John', 19, 'secret', 1, datetime.date(1999, 7, 9), 1, Decimal('176.00'))
(8, 'John', 19, 'secret', 2, datetime.date(1999, 7, 9), 1, Decimal('165.00'))
(9, 'John', 19, 'secret', 3, datetime.date(1999, 7, 9), 1, Decimal('153.00'))
(10, 'John', 19, 'secret', 3, datetime.date(1999, 7, 9), 1, Decimal('165.00'))
...

二、练习-商品表的查询

需求:
输入1:查询所有商品
输入2:查询所有商品种类
输入3:查询所有品牌
输入4:添加商品种类
输入5:退出
代码如下,

from pymysql import *
class Jd(object):
    def __init__(self):
        '''初始化'''
        #连接数据库
        self.conn = connect(
            host='127.0.0.1',
            port=3306,
            db='jingdong',
            user='root',
            passwd='root',
            charset='utf8'
        )
        #获取游标
        self.cur = self.conn.cursor()

    @staticmethod
    def print_menu():
        '''静态方法,不需要self,单独定义打印菜单函数,解耦,降低耦合性'''
        print('--jd shop--')
        print('1-查询所有商品')
        print('2-查询所有种类')
        print('3-查询所有品牌')
        print('4-添加商品种类')
        print('5-退出')
        num = input('请输入一个数字:')
        return num

    def execute_sql(self,sql):
        self.cur.execute(sql)
        result = self.cur.fetchall()
        for item in result:
            print(item)

    def show_all_goods(self):
        '''查询所有商品'''
        sql = 'select * from goods;'
        self.execute_sql(sql)

    def show_all_cates(self):
        '''查询所有种类'''
        sql = 'select * from goods_cates;'
        self.execute_sql(sql)

    def show_all_brands(self):
        sql = 'select distinct brand_name from goods;'
        self.execute_sql(sql)

    def add_cate(self):
        name = input('请输入新的商品分类名:')
        sql = 'insert into goods_cates(name) values("%s")' % name
        self.cur.execute(sql)
        print('Adding cates successfully!')

    def run(self):
        while True:
            num = self.print_menu()
            if num == '1':
                self.show_all_goods()
            elif num == '2':
                self.show_all_cates()
            elif num == '3':
                self.show_all_brands()
            elif num == '4':
                self.add_cate()
            elif num == '5':
                print('Bye-Bye')
                break

    def __del__(self):
        self.cur.close()
        self.conn.close()

def main():
    jd = Jd()
    jd.run()

if __name__ == '__main__':
    main()

进行实例化并测试得,

--jd shop--
1-查询所有商品
2-查询所有种类
3-查询所有品牌
4-添加商品种类
5-退出
请输入一个数字:1
(1, 'r510vc 15.6英寸笔记本', '5', '华硕', Decimal('3399.000'), 1, 0)
(2, 'y400n 14.0英寸笔记本电脑', '5', '联想', Decimal('4999.000'), 1, 0)
(3, 'g150th 15.6英寸游戏本', '4', '雷神', Decimal('8499.000'), 1, 0)
(4, 'x550cc 15.6英寸笔记本', '5', '华硕', Decimal('2799.000'), 1, 0)
(5, 'x240 超极本', '7', '联想', Decimal('4880.000'), 1, 0)
(6, 'u330p 13.3英寸超极本', '7', '联想', Decimal('4299.000'), 1, 0)
(7, 'svp13226scb 触控超极本', '7', '索尼', Decimal('7999.000'), 1, 0)
(8, 'ipad mini 7.9英寸平板电脑', '2', '苹果', Decimal('1998.000'), 1, 0)
(9, 'ipad air 9.7英寸平板电脑', '2', '苹果', Decimal('3388.000'), 1, 0)
(10, 'ipad mini 配备 retina 显示屏', '2', '苹果', Decimal('2788.000'), 1, 0)
(11, 'ideacentre c340 20英寸一体电脑 ', '1', '联想', Decimal('3499.000'), 1, 0)
(12, 'vostro 3800-r1206 台式电脑', '1', '戴尔', Decimal('2899.000'), 1, 0)
(13, 'imac me086ch/a 21.5英寸一体电脑', '1', '苹果', Decimal('9188.000'), 1, 0)
(14, 'at7-7414lp 台式电脑 linux )', '1', '宏碁', Decimal('3699.000'), 1, 0)
(15, 'z220sff f4f06pa工作站', '3', '惠普', Decimal('4288.000'), 1, 0)
(16, 'poweredge ii服务器', '3', '戴尔', Decimal('5388.000'), 1, 0)
(17, 'mac pro专业级台式电脑', '3', '苹果', Decimal('28888.000'), 1, 0)
(18, 'hmz-t3w 头戴显示设备', '6', '索尼', Decimal('6999.000'), 1, 0)
(19, '商务双肩背包', '6', '索尼', Decimal('99.000'), 1, 0)
(20, 'x3250 m4机架式服务器', '3', 'ibm', Decimal('6888.000'), 1, 0)
(21, '商务双肩背包', '6', '索尼', Decimal('99.000'), 1, 0)
--jd shop--
1-查询所有商品
2-查询所有种类
3-查询所有品牌
4-添加商品种类
5-退出
请输入一个数字:2
(1, '台式机')
(2, '平板电脑')
(3, '服务器/工作站')
(4, '游戏本')
(5, '笔记本')
(6, '笔记本配件')
(7, '超级本')
--jd shop--
1-查询所有商品
2-查询所有种类
3-查询所有品牌
4-添加商品种类
5-退出
请输入一个数字:3
('华硕',)
('联想',)
('雷神',)
('索尼',)
('苹果',)
('戴尔',)
('宏碁',)
('惠普',)
('ibm',)
--jd shop--
1-查询所有商品
2-查询所有种类
3-查询所有品牌
4-添加商品种类
5-退出
请输入一个数字:4
请输入新的商品分类名:minipads
Adding cates successfully!
--jd shop--
1-查询所有商品
2-查询所有种类
3-查询所有品牌
4-添加商品种类
5-退出
请输入一个数字:5
Bye-Bye

这个类种定义了一个静态方法,不需要传入self参数,因为这个print_menu()方法与类无实际联系,所以定义为静态方法;
同时运用了解耦的思想,降低耦合性,能独立出方法块的尽量抽离出独立的方法,减少依赖性,写程序时要求高内聚、低耦合。

三、封装类中数据库的新增

在封装类的数据库中进行数据的新增、更新、删除。
执行插入语句后,

from pymysql import *

def operate_db():
    global conn
    #连接数据库
    conn = connect(
        host='127.0.0.1',
        port=3306,
        db='demo1125',
        user='root',
        passwd='root',
        charset='utf8'
    )
    #获取游标
    cur = conn.cursor()
    sql = 'insert into student(sname) values("Jack");'
    cur.execute(sql)
    cur.close()
    conn.close()

if __name__ == '__main__':
    operate_db()

再查询数据库并没有看到新增的数据,说明数据并没有被成功插入,此时需要提交事务。
如下

from pymysql import *

def operate_db():
    global conn
    #连接数据库
    conn = connect(
        host='127.0.0.1',
        port=3306,
        db='demo1125',
        user='root',
        passwd='root',
        charset='utf8'
    )
    #获取游标
    cur = conn.cursor()
    sql = 'insert into student(sname) values("Jack");'
    cur.execute(sql)
    # sql = 'insert into student(name) values("Jackson");'
    # cur.execute(sql)
    #提交事务
    conn.commit()
    cur.close()
    conn.close()

if __name__ == '__main__':
    operate_db()

此时,再去查询,发现已经有了一条刚刚插入的数据,并且id并不连续,即未成功提交事务的数据也会占用id,如下所示

+-----+-------+-----+
| sid | sname | gid |
+-----+-------+-----+
|   3 | Jack  |   1 |
|   4 | Jack  |   1 |
|   5 | Jack  |   1 |
|   8 | Jack  |   1 |
+-----+-------+-----+

5过了就直接到8了,id自增是不可逆的。
提交事务:
只有提交之后,数据才会被真正地添加到数据库中,数据库才会发生相应的更改。
同时执行多条插入语句时

from pymysql import *

def operate_db():
    global conn
    #连接数据库
    conn = connect(
        host='127.0.0.1',
        port=3306,
        db='demo1125',
        user='root',
        passwd='root',
        charset='utf8'
    )
    #获取游标
    cur = conn.cursor()
    sql = 'insert into student(sname) values("Jack");'
    cur.execute(sql)
    sql = 'insert into student(sname) values("Jackson");'
    cur.execute(sql)
    #提交事务
    conn.commit()
    cur.close()
    conn.close()

if __name__ == '__main__':
    operate_db()

查询到

+-----+---------+-----+
| sid | sname   | gid |
+-----+---------+-----+
|   3 | Jack    |   1 |
|   4 | Jack    |   1 |
|   5 | Jack    |   1 |
|   8 | Jack    |   1 |
|   9 | Jack    |   1 |
|  10 | Jackson |   1 |
+-----+---------+-----+

当第2条SQL语句出错时,

from pymysql import *

def operate_db():
    global conn
    #连接数据库
    conn = connect(
        host='127.0.0.1',
        port=3306,
        db='demo1125',
        user='root',
        passwd='root',
        charset='utf8'
    )
    #获取游标
    cur = conn.cursor()
    sql = 'insert into student(sname) values("Jack");'
    cur.execute(sql)
    sql = 'insert into student(name) values("Jackson");'
    cur.execute(sql)
    #提交事务
    conn.commit()
    cur.close()
    conn.close()

if __name__ == '__main__':
    operate_db()

此时,数据为

+-----+---------+-----+ 
| sid | sname   | gid | 
+-----+---------+-----+ 
|   3 | Jack    |   1 | 
|   4 | Jack    |   1 | 
|   5 | Jack    |   1 | 
|   8 | Jack    |   1 | 
|   9 | Jack    |   1 | 
|  10 | Jackson |   1 | 
+-----+---------+-----+ 
6 rows in set (0.00 sec)

可知,在第2条打起来语句有错的情况下,第1条语句也为成功插入。
可以加入异常处理

from pymysql import *

def operate_db():
    global conn
    try:
        #连接数据库
        conn = connect(
            host='127.0.0.1',
            port=3306,
            db='demo1125',
            user='root',
            passwd='root',
            charset='utf8'
        )
        #获取游标
        cur = conn.cursor()
        sql = 'insert into student(sname) values("Jack");'
        cur.execute(sql)
        sql = 'insert into student(name) values("Jackson");'
        cur.execute(sql)
        #提交事务
        conn.commit()
        cur.close()
        conn.close()
    except Exception as e:
        print(e.args[0],e.args[1])
        conn.commit()

if __name__ == '__main__':
    operate_db()

打印1054 Unknown column 'name' in 'field list'
此时再查询,

+-----+---------+-----+ 
| sid | sname   | gid | 
+-----+---------+-----+ 
|   3 | Jack    |   1 | 
|   4 | Jack    |   1 | 
|   5 | Jack    |   1 | 
|   8 | Jack    |   1 | 
|   9 | Jack    |   1 | 
|  10 | Jackson |   1 | 
|  22 | Jack    |   1 | 
+-----+---------+-----+ 
7 rows in set (0.01 sec)

显然,第1条语句被成功执行并插入,但是id已不再是从刚才的id自增1,而是间隔了很多个值,这样就能达到将前边正确的SQL语句插入的目的,即部分提交;
如果要使得如果在发生异常时,所有的操作都撤回,应该使用回滚,即rollback

from pymysql import *

def operate_db():
    global conn
    try:
        #连接数据库
        conn = connect(
            host='127.0.0.1',
            port=3306,
            db='demo1125',
            user='root',
            passwd='root',
            charset='utf8'
        )
        #获取游标
        cur = conn.cursor()
        sql = 'insert into student(sname) values("Jack");'
        cur.execute(sql)
        sql = 'insert into student(name) values("Jackson");'
        cur.execute(sql)
        #提交事务
        conn.commit()
        cur.close()
        conn.close()
    except Exception as e:
        print(e.args[0],e.args[1])
        #回滚
        conn.rollback()

if __name__ == '__main__':
    operate_db()

此时查询数据库,可得

+-----+---------+-----+
| sid | sname   | gid |
+-----+---------+-----+
|   3 | Jack    |   1 |
|   4 | Jack    |   1 |
|   5 | Jack    |   1 |
|   8 | Jack    |   1 |
|   9 | Jack    |   1 |
|  10 | Jackson |   1 |
|  22 | Jack    |   1 |
+-----+---------+-----+

显然,这时即便第1条SQL语句即便未出错,但是因为第2条语句出错,导致回滚操作使所有的操作撤回,数据表未增加。

from pymysql import *

def operate_db():
    global conn
    try:
        #连接数据库
        conn = connect(
            host='127.0.0.1',
            port=3306,
            db='demo1125',
            user='root',
            passwd='root',
            charset='utf8'
        )
        #获取游标
        cur = conn.cursor()
        sql = 'insert into student(sname) values("Tom");'
        cur.execute(sql)
        sql = 'insert into student(sname) values("Tommy");'
        cur.execute(sql)
        #提交事务
        conn.commit()
        cur.close()
        conn.close()
    except Exception as e:
        print(e.args[0],e.args[1])
        #回滚
        conn.rollback()


if __name__ == '__main__':
    operate_db()

此时插入两条正常数据

+-----+---------+-----+
| sid | sname   | gid |
+-----+---------+-----+
|   3 | Jack    |   1 |
|   4 | Jack    |   1 |
|   5 | Jack    |   1 |
|   8 | Jack    |   1 |
|   9 | Jack    |   1 |
|  10 | Jackson |   1 |
|  22 | Jack    |   1 |
|  26 | Tom     |   1 |
|  27 | Tommy   |   1 |
+-----+---------+-----+

插入成功,显然id并未从22开始,说明rollback即使未插入数据,但是还是占用了id。
总结:
只要cur.execute(sql)被正常执行,即SQL语句正确,id就会自增,未被正确执行,就不会自增,而不管数据是否被插入和是否提交事务;
回滚时,正确执行的使id增加,未被正确执行的使id不变;
全部执行成功时,id增加;
全部都不提交:rollback,id部分增加
提交一部分:commit,id部分增加。
数据的更新、删除操作与新增类似。

四、视图

1.问题提出

对于复杂的查询,往往是有多个数据表进行关联查询而得到,如果数据库因为需求等原因发生了改变,为了保证查询出来的数据与之前相同,则需要在多个地方进行修改,维护起来非常麻烦。

2.视图定义

通俗地讲,视图就是一条select语句执行后返回的结果集,即虚拟的表。
我们在创建视图的时候,主要的工作就落在创建这条SQL查询语句上。
视图是对若干张基本表的引用,一张虚表,查询语句执行的结果,不存储具体的数据(基本表数据发生了改变,视图也会跟着改变)。

3.视图操作

创建视图
语法:create view 视图名称 as select语句;
一般的查询语句:

select p.*,c.name as cname from areas as p inner join areas as c on p.id = c.pid having p.name = '湖南'

打印

+----+-----+--------+------+-----------+
| id | pid | name   | type | cname     |
+----+-----+--------+------+-----------+
| 14 |   1 | 湖南   |    1 | 长沙      |    
| 14 |   1 | 湖南   |    1 | 张家界    |     
| 14 |   1 | 湖南   |    1 | 常德      |    
| 14 |   1 | 湖南   |    1 | 郴州      |    
| 14 |   1 | 湖南   |    1 | 衡阳      |    
| 14 |   1 | 湖南   |    1 | 怀化      |    
| 14 |   1 | 湖南   |    1 | 娄底      |    
| 14 |   1 | 湖南   |    1 | 邵阳      |    
| 14 |   1 | 湖南   |    1 | 湘潭      |    
...  
+----+-----+--------+------+-----------+
14 rows in set (0.01 sec)               

运用查询到的结果创建视图

create view v_p_c as select p.*,c.name as cname from areas as p inner join areas as c on p.id = c.pid having p.name = '湖南';

打印

Query OK, 0 rows affected (0.01 sec)

查看视图
语法:show tables;

show tables;

打印

+--------------------+
| Tables_in_demo1125 |
+--------------------+
| areas              |
| cities             |
| classes            |
| provinces          |
| student            |
| v_p_c              |
+--------------------+
6 rows in set (0.00 sec)

很明显,此时已经增加了一个新表即视图v_p_c。
使用视图

select * from v_p_c;

打印

+----+-----+--------+------+-----------+
| id | pid | name   | type | cname     |
+----+-----+--------+------+-----------+
| 14 |   1 | 湖南   |    1 | 长沙      |
| 14 |   1 | 湖南   |    1 | 张家界    |
| 14 |   1 | 湖南   |    1 | 常德      |
| 14 |   1 | 湖南   |    1 | 郴州      |
| 14 |   1 | 湖南   |    1 | 衡阳      |
| 14 |   1 | 湖南   |    1 | 怀化      |
| 14 |   1 | 湖南   |    1 | 娄底      |
| 14 |   1 | 湖南   |    1 | 邵阳      |
| 14 |   1 | 湖南   |    1 | 湘潭      |
| 14 |   1 | 湖南   |    1 | 湘西      |
...
+----+-----+--------+------+-----------+
14 rows in set (0.01 sec)

删除视图
语法:drop view 视图名称;
例如:

drop view v_p_c;

视图修改
有下列内容之一,视图不能做修改:

  • select子句中包含distinct;
  • select子句中包含组函数;
  • select语句中包含group by子句;
  • select语句中包含order by子句;
  • where子句中包含相关子查询;
  • from子句中包含多个表;
  • 如果视图中有计算列,则不能更新;
  • 如果基表中有某个具有非空约束的列未出现在视图定义中,则不能做insert操作。

可知,视图基本不允许修改。

update v_p_c set cname ='长沙市' where cname ='长沙;'

打印

ERROR 1288 (HY000): The target table v_p_c of the UPDATE is not updatable

报错,因为视图本来就是为了查询创建的,而不是为了修改而创建,所以视图修改是没有必要的,也不允许。
原表中的数据改变时:

  • 如改变的数据未在select语句的条件中出现,视图中的数据也会相应发生改变;
  • 如改变的数据在select语句的条件中出现,视图中的数据也会相应发生改变,因为查询的条件可能因为数据的修改而不再满足,使得查询到的数据量减少或增加。
    可以删除视图,但是不能删除视图中的数据,只能对视图进行查询操作。

视图的作用

  • 方便操作,特别是查询操作;
  • 减少复杂的SQL语句,就像一个函数,提高了重用性,增强代码可读性;
  • 对数据库重构,却不影响程序的运行;
  • 隐藏数据,提高了安全性能,可以对不同的用户提供不同的视图;
  • 让数据更加清晰。

一般在SQL语句较复杂时使用视图来简化操作。
小编那么拼,赞一个再撤!
公众号二维码
大家也可以关注我的公众号:Python极客社区,在我的公众号里,经常会分享很多Python的文章,而且也分享了很多工具、学习资源等。另外回复“电子书”还可以获取十本我精心收集的Python电子书。

发布了51 篇原创文章 · 获赞 184 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/CUFEECR/article/details/103437574
今日推荐