MySQL補充——索引,流程控制,數據備份,python操做mysql,SQLAlchemy

1、索引

  索引,是數據庫中專門用於幫助用戶快速查詢數據的一種數據結構。相似於字典中的目錄,查找字典內容時能夠根據目錄查找到數據的存放位置,而後直接獲取便可。html

 

一、索引種類

  • 普通索引:僅加速查詢
  • 惟一索引:加速查詢 + 列值惟一(能夠有null)
  • 主鍵索引:加速查詢 + 列值惟一 + 表中只有一個(不能夠有null)
  • 組合索引:多列值組成一個索引,專門用於組合搜索,其效率大於索引合併
  • 全文索引:對文本的內容進行分詞,進行搜索 

索引合併,使用多個單列索引組合搜索,組合索引效率 > 索引合併
覆蓋索引,select的數據列只用從索引中就可以取得,沒必要讀取數據行,換句話說查詢列要被所建的索引覆蓋mysql

 

普通索引

建立表+索引git

create table in1( nid int not null auto_increment primary key, name varchar(32) not null, email varchar(64) not null, extra text, index ix_name (name) )

建立表以後,另外建立普通索引算法

create index index_name on table_name(column_name)

刪除普通索引sql

drop index_name on table_name;

查看索引數據庫

show index from table_name;

 

注意:對於建立索引時若是是BLOB 和 TEXT 類型,必須指定length。編程

create index ix_extra on in1(extra(32));

 

惟一索引

建立表+惟一索引vim

create table in1( nid int not null auto_increment primary key, name varchar(32) not null, email varchar(64) not null, extra text, unique ix_name (name) )

建立表以後,另外建立惟一索引windows

create unique index 索引名 on 表名(列名)

刪除惟一索引bash

drop unique index 索引名 on 表名

 

主鍵索引

建立表+建立主鍵

create table in1( nid int not null auto_increment primary key, name varchar(32) not null, email varchar(64) not null, extra text ) OR

create table in1( nid int not null auto_increment, name varchar(32) not null, email varchar(64) not null, extra text, primary key(ni1) )

建立完表以後,建立主鍵

alter table 表名 add primary key(列名);

刪除主鍵

alter table 表名 drop primary key; alter table 表名  modify  列名 int, drop primary key;

 

組合索引

組合索引是將n個列組合成一個索引

其應用場景爲:頻繁的同時使用n列來進行查詢,如:where n1 = 'alex' and n2 = 666。

-- 建立表

create table in3(
    nid int not null auto_increment primary key,
    name varchar(32) not null,
    email varchar(64) not null,
    extra text
)
-- 建立組合索引

create index ix_name_email on in3(name,email);

如上建立組合索引以後,查詢:(最左匹配原則)

  • name and email   -- 使用索引
  • name                   -- 使用索引
  • email                   -- 不使用索引

注意:對於同時搜索n個條件時,組合索引的性能好於多個單一索引合併。

 

二、正確使用索引及注意事項

如下狀況不會走索引:

- like '%xx'
    select * from tb1 where name like '%cn';
- 使用函數
    select * from tb1 where reverse(name) = 'wupeiqi';
- or
    select * from tb1 where nid = 1 or email = 'seven@live.com';
    特別的:當or條件中有未創建索引的列才失效,如下會走索引
            select * from tb1 where nid = 1 or name = 'seven';
            select * from tb1 where nid = 1 or email = 'seven@live.com' and name = 'alex'
- 類型不一致
    若是列是字符串類型,傳入條件是必須用引號引發來,否則...
    select * from tb1 where name = 999;
- !=
    select * from tb1 where name != 'alex'
    特別的:若是是主鍵,則仍是會走索引
        select * from tb1 where nid != 123
- >
    select * from tb1 where name > 'alex'
    特別的:若是是主鍵或索引是整數類型,則仍是會走索引
        select * from tb1 where nid > 123
        select * from tb1 where num > 123
- order by
    select email from tb1 order by name desc;
    當根據索引排序時候,選擇的映射若是不是索引,則不走索引
    特別的:若是對主鍵排序,則仍是走索引:
        select * from tb1 order by nid desc;
 
- 組合索引最左前綴
    若是組合索引爲:(name,email)
    name and email       -- 使用索引
    name                 -- 使用索引
    email                -- 不使用索引

注意事項:

-- 避免使用select *
-- count(1)或count(列) 代替 count(*)
-- 建立表時儘可能時 char 代替 varchar
-- 表的字段順序固定長度的字段優先
-- 組合索引代替多個單列索引(常用多個條件查詢時)
-- 儘可能使用短索引
-- 使用鏈接(JOIN)來代替子查詢(Sub-Queries)
-- 連表時注意條件類型需一致
-- 索引散列值(重複少)不適合建索引,例:性別不適合

 

三、limit分頁 ?

不管是否有索引,limit分頁是一個值得關注的問題

select 
        * 
    from tb1 where nid < (select nid from (select nid from tb1 where nid < 當前頁最小值 order by nid desc limit 每頁數據 *【頁碼-當前頁】) A order by A.nid asc limit 1) order by nid desc limit 10; select 
        * 
    from tb1 where nid < (select nid from (select nid from tb1 where nid < 970  order by nid desc limit 40) A order by A.nid asc limit 1) order by nid desc limit 10; 上一頁: select 
        * 
    from tb1 where nid < (select nid from (select nid from tb1 where nid > 當前頁最大值 order by nid asc limit 每頁數據 *【當前頁-頁碼】) A order by A.nid asc limit 1) order by nid desc limit 10; select 
        * 
    from tb1 where nid < (select nid from (select nid from tb1 where nid > 980 order by nid asc limit 20) A order by A.nid desc limit 1) order by nid desc limit 10;

 

四、執行計劃

explain + 查詢SQL ------ 用於顯示SQL執行信息參數,根據參考信息能夠進行SQL優化

mysql> explain select * from tb2;
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
|  1 | SIMPLE      | tb2   | ALL  | NULL          | NULL | NULL    | NULL |    2 | NULL  |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
1 row in set (0.00 sec)

詳細:


    id 查詢順序標識
            如:mysql> explain select * from (select nid,name from tb1 where nid < 10) as B;
            +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+
            | id | select_type | table      | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
            +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+
            |  1 | PRIMARY     | <derived2> | ALL   | NULL          | NULL    | NULL    | NULL |    9 | NULL        |
            |  2 | DERIVED     | tb1        | range | PRIMARY       | PRIMARY | 8       | NULL |    9 | Using where |
            +----+-------------+------------+-------+---------------+---------+---------+------+------+-------------+
            特別的:若是使用union鏈接其值可能爲null


    select_type
        查詢類型
            SIMPLE          簡單查詢
            PRIMARY         最外層查詢
            SUBQUERY        映射爲子查詢
            DERIVED         子查詢
            UNION           聯合
            UNION RESULT    使用聯合的結果
            ...
    table
        正在訪問的表名


    type
        查詢時的訪問方式,性能:all < index < range < index_merge < ref_or_null < ref < eq_ref < system/const
            ALL             全表掃描,對於數據表從頭至尾找一遍
                            select * from tb1;
                            特別的:若是有limit限制,則找到以後就不在繼續向下掃描
                                   select * from tb1 where email = 'seven@live.com'
                                   select * from tb1 where email = 'seven@live.com' limit 1;
                                   雖然上述兩個語句都會進行全表掃描,第二句使用了limit,則找到一個後就再也不繼續掃描。

            INDEX           全索引掃描,對索引從頭至尾找一遍
                            select nid from tb1;

            RANGE          對索引列進行範圍查找
                            select *  from tb1 where name < 'alex';
                            PS:
                                between and
                                in
                                >   >=  <   <=  操做
                                注意:!=> 符號


            INDEX_MERGE     合併索引,使用多個單列索引搜索
                            select *  from tb1 where name = 'alex' or nid in (11,22,33);

            REF             根據索引查找一個或多個值
                            select *  from tb1 where name = 'seven';

            EQ_REF          鏈接時使用primary key 或 unique類型
                            select tb2.nid,tb1.name from tb2 left join tb1 on tb2.nid = tb1.nid;



            CONST           常量
                            表最多有一個匹配行,由於僅有一行,在這行的列值可被優化器剩餘部分認爲是常數,const表很快,由於它們只讀取一次。
                            select nid from tb1 where nid = 2 ;

            SYSTEM          系統
                            表僅有一行(=系統表)。這是const聯接類型的一個特例。
                            select * from (select nid from tb1 where nid = 1) as A;
    possible_keys
        可能使用的索引

    key
        真實使用的

    key_len
        MySQL中使用索引字節長度

    rows
        mysql估計爲了找到所需的行而要讀取的行數 ------ 只是預估值

    extra
        該列包含MySQL解決查詢的詳細信息
        「Using index」
            此值表示mysql將使用覆蓋索引,以免訪問表。不要把覆蓋索引和index訪問類型弄混了。
        「Using where」
            這意味着mysql服務器將在存儲引擎檢索行後再進行過濾,許多where條件裏涉及索引中的列,當(而且若是)它讀取索引時,就能被存儲引擎檢驗,
       所以不是全部帶where子句的查詢都會顯示「Using
where」。有時「Using where」的出現就是一個暗示:查詢可受益於不一樣的索引。 「Using temporary」 這意味着mysql在對查詢結果排序時會使用一個臨時表。 「Using filesort」 這意味着mysql會對結果使用一個外部索引排序,而不是按索引次序從表裏讀取行。mysql有兩種文件排序算法,
這兩種排序方式均可以在內存或者磁盤上完成,explain不會告訴你mysql將使用哪種文件排序,也不會告訴你排序會在內存裏仍是磁盤上完成。 「Range checked
for each record(index map: N)」 這個意味着沒有好用的索引,新的索引將在聯接的每一行上從新估算,N是顯示在possible_keys列中索引的位圖,而且是冗餘的。

慢查詢優化的基本步驟:

0.先運行看看是否真的很慢,注意設置SQL_NO_CACHE
1.where條件單表查,鎖定最小返回記錄表。這句話的意思是把查詢語句的where都應用到表中返回的記錄數最小的表開始查起,單表每一個字段分別查詢,看哪一個字段的區分度最高
2.explain查看執行計劃,是否與1預期一致(從鎖定記錄較少的表開始查詢)
3.order by limit 形式的sql語句讓排序的表優先查
4.瞭解業務方使用場景
5.加索引時參照建索引的幾大原則
6.觀察結果,不符合預期繼續從0分析

 

五、慢日誌查詢

a、配置MySQL自動記錄慢日誌

slow_query_log = OFF 是否開啓慢日誌記錄 long_query_time = 2 時間限制,超過此時間,則記錄 slow_query_log_file = /usr/slow.log 日誌文件 log_queries_not_using_indexes = OFF         爲使用索引的搜索是否記錄

查看當前配置信息:show variables like '%query%'
修改當前配置:set global 變量名 = 值

 

b、查看MySQL慢日誌

mysqldumpslow -s at -a  /usr/local/var/mysql/MacBook-Pro-3-slow.log
""" --verbose 版本 --debug 調試 --help 幫助 -v 版本 -d 調試模式 -s ORDER 排序方式 what to sort by (al, at, ar, c, l, r, t), 'at' is default al: average lock time ar: average rows sent at: average query time c: count l: lock time r: rows sent t: query time -r 反轉順序,默認文件倒序拍。reverse the sort order (largest last instead of first) -t NUM 顯示前N條just show the top n queries -a 不要將SQL中數字轉換成N,字符串轉換成S。don't abstract all numbers to N and strings to 'S' -n NUM abstract numbers with at least n digits within names -g PATTERN 正則匹配;grep: only consider stmts that include this string -h HOSTNAME mysql機器名或者IP;hostname of db server for *-slow.log filename (can be wildcard), default is '*', i.e. match all -i NAME name of server instance (if using mysql.server startup script) -l 總時間中不減去鎖定時間;don't subtract lock time from total time """

 

2、流程控制

一、IF條件語句

delimiter \\
CREATE PROCEDURE proc_if ()
BEGIN
    
    declare i int default 0;
    if i = 1 THEN
        SELECT 1;
    ELSEIF i = 2 THEN
        SELECT 2;
    ELSE
        SELECT 7;
    END IF;

END\\
delimiter ;

 

二、循環語句

while循環

delimiter \\
CREATE PROCEDURE proc_while ()
BEGIN

    DECLARE num INT ;
    SET num = 0 ;
    WHILE num < 10 DO
        SELECT
            num ;
        SET num = num + 1 ;
    END WHILE ;

END\\
delimiter ;

 

repeat循環

delimiter \\
CREATE PROCEDURE proc_repeat ()
BEGIN

    DECLARE i INT ;
    SET i = 0 ;
    repeat
        select i;
        set i = i + 1;
        until i >= 5
    end repeat;

END\\
delimiter ;

 

loop循環  ?

BEGIN
    
    declare i int default 0;
    loop_label: loop
        
        set i=i+1;
        if i<8 then
            iterate loop_label;
        end if;
        if i>=10 then
            leave loop_label;
        end if;
        select i;
    end loop loop_label;

END

 

3、數據備份

一、三種備份方式

  • 物理備份:直接複製數據庫文件,適用於大型數據庫環境,但不能恢復到異構環境中,如windows。
  • 邏輯備份:備份建庫,建表,插入數據等操做所執行的SQL語句,適用於中小型數據庫,效率相對較低。
  • 導出表:直接將表導入到文本文件中。

 

二、用mysqldump實現邏輯備份

#語法:
# mysqldump -h 服務器 -u用戶名 -p密碼 數據庫名 > 備份文件.sql

#示例:
#單庫備份
mysqldump -uroot -p123 db1 > db1.sql
mysqldump -uroot -p123 db1 table1 table2 > db1-table1-table2.sql

#多庫備份
mysqldump -uroot -p123 --databases db1 db2 mysql db3 > db1_db2_mysql_db3.sql

#備份全部庫
mysqldump -uroot -p123 --all-databases > all.sql 

恢復邏輯備份

#方法一:
[root@egon backup]# mysql -uroot -p123 < /backup/all.sql

#方法二:
mysql> use db1;
mysql> SET SQL_LOG_BIN=0;
mysql> source /root/db1.sql

#注:若是備份/恢復單個庫時,能夠修改sql文件
DROP database if exists school;
create database school;
use school; 

 

三、實現自動化備份

備份計劃:
1. 什麼時間 2:00
2. 對哪些數據庫備份
3. 備份文件放的位置

備份腳本:
[root@egon ~]# vim /mysql_back.sql
#!/bin/bash
back_dir=/backup
back_file=`date +%F`_all.sql
user=root
pass=123

if [ ! -d /backup ];then
mkdir -p /backup
fi

# 備份並截斷日誌
mysqldump -u${user} -p${pass} --events --all-databases > ${back_dir}/${back_file}
mysql -u${user} -p${pass} -e 'flush logs'

# 只保留最近一週的備份
cd $back_dir
find . -mtime +7 -exec rm -rf {} \;

手動測試: [root@egon
~]# chmod a+x /mysql_back.sql [root@egon ~]# chattr +i /mysql_back.sql [root@egon ~]# /mysql_back.sql 配置cron: [root@egon ~]# crontab -l 2 * * * /mysql_back.sql

 

四、表的導入和導出

SELECT... INTO OUTFILE 導出文本文件
示例:
mysql> SELECT * FROM school.student1
INTO OUTFILE 'student1.txt'
FIELDS TERMINATED BY ',' //定義字段分隔符
OPTIONALLY ENCLOSED BY '' //定義字符串使用什麼符號括起來
LINES TERMINATED BY '\n' ; //定義換行符


mysql 命令導出文本文件
示例:
# mysql -u root -p123 -e 'select * from student1.school' > /tmp/student1.txt
# mysql -u root -p123 --xml -e 'select * from student1.school' > /tmp/student1.xml
# mysql -u root -p123 --html -e 'select * from student1.school' > /tmp/student1.html

LOAD DATA INFILE 導入文本文件
mysql> DELETE FROM student1;
mysql> LOAD DATA INFILE '/tmp/student1.txt'
INTO TABLE school.student1
FIELDS TERMINATED BY ','
OPTIONALLY ENCLOSED BY ''
LINES TERMINATED BY '\n';
#可能會報錯
mysql> select * from db1.emp into outfile 'C:\\db1.emp.txt' fields terminated by ',' lines terminated by '\r\n';
ERROR 1238 (HY000): Variable 'secure_file_priv' is a read only variable


#數據庫最關鍵的是數據,一旦數據庫權限泄露,那麼經過上述語句就能夠輕鬆將數據導出到文件中而後下載拿走,於是mysql對此做了限制,只能將文件導出到指定目錄
在配置文件中
[mysqld]
secure_file_priv='C:\\' #只能將數據導出到C:\\下

重啓mysql
從新執行上述語句

 

五、數據庫遷移

務必保證在相同版本之間遷移
# mysqldump -h 源IP -uroot -p123 --databases db1 | mysql -h 目標IP -uroot -p456

 

4、PyMySQL模塊

一、鏈接、執行sql、關閉(遊標)

import pymysql
user
=input('用戶名: ').strip() pwd=input('密碼: ').strip() #連接 conn=pymysql.connect(host='localhost',user='root',password='123',database='egon',charset='utf8') #遊標 cursor=conn.cursor() #執行完畢返回的結果集默認以元組顯示 #cursor=conn.cursor(cursor=pymysql.cursors.DictCursor) #執行sql語句 sql='select * from userinfo where name="%s" and password="%s"' %(user,pwd) #注意%s須要加引號 print(sql) res=cursor.execute(sql) #執行sql語句,返回sql查詢成功的記錄數目 print(res) cursor.close() conn.close() if res: print('登陸成功') else: print('登陸失敗')

 

二、execute()之sql注入

注意:符號 -- 會註釋掉它以後的sql

根本原理:就根據程序的字符串拼接name='%s',咱們輸入一個xxx' -- haha,用咱們輸入的xxx加'在程序中拼接成一個判斷條件name='xxx' -- haha'

在一條sql語句中若是遇到select * from t1 where id > 3 -- and name='egon';則--以後的條件被註釋掉了

#一、sql注入之用戶存在,繞過密碼
egon」 -- 任意字符

#二、sql注入之用戶不存在,繞過用戶與密碼
xxx」 or 1=1 -- 任意字符

解決方法:

原來是咱們對sql進行字符串拼接
  sql="select * from userinfo where name='%s' and password='%s'" %(user,pwd)
  print(sql)
  res=cursor.execute(sql)

 

改寫爲(execute幫咱們作字符串拼接,咱們無需且必定不能再爲%s加引號了)
  sql="select * from userinfo where name=%s and password=%s"      #!!!注意%s須要去掉引號,由於pymysql會自動爲咱們加上
  res=cursor.execute(sql,[user,pwd])    #pymysql模塊自動幫咱們解決sql注入的問題,只要咱們按照pymysql的規矩來。

 

三、增、刪、改:conn.commit()

import pymysql
#連接
conn=pymysql.connect(host='localhost',user='root',password='123',database='egon')
#遊標
cursor=conn.cursor()

#執行sql語句
#part1
# sql='insert into userinfo(name,password) values("root","123456");'
# res=cursor.execute(sql) #執行sql語句,返回sql影響成功的行數
# print(res)

#part2
# sql='insert into userinfo(name,password) values(%s,%s);'
# res=cursor.execute(sql,("root","123456")) #執行sql語句,返回sql影響成功的行數
# print(res)

#part3
sql='insert into userinfo(name,password) values(%s,%s);'
res=cursor.executemany(sql,[("root","123456"),("lhf","12356"),("eee","156")]) #執行sql語句,返回sql影響成功的行數
print(res)

conn.commit() #提交後才發現表中插入記錄成功
cursor.close()
conn.close()

 

四、查:fetchone,fetchmany,fetchall

import pymysql
#連接
conn=pymysql.connect(host='localhost',user='root',password='123',database='egon')
#遊標
cursor=conn.cursor()

#執行sql語句
sql='select * from userinfo;'
rows=cursor.execute(sql) #執行sql語句,返回sql影響成功的行數rows,將結果放入一個集合,等待被查詢

# cursor.scroll(3,mode='absolute') # 相對絕對位置移動
# cursor.scroll(3,mode='relative') # 相對當前位置移動
res1=cursor.fetchone()
res2=cursor.fetchone()
res3=cursor.fetchone()
res4=cursor.fetchmany(2)
res5=cursor.fetchall()
print(res1)
print(res2)
print(res3)
print(res4)
print(res5)
print('%s rows in set (0.00 sec)' %rows)



conn.commit() #提交後才發現表中插入記錄成功
cursor.close()
conn.close()

'''
(1, 'root', '123456')
(2, 'root', '123456')
(3, 'root', '123456')
((4, 'root', '123456'), (5, 'root', '123456'))
((6, 'root', '123456'), (7, 'lhf', '12356'), (8, 'eee', '156'))
rows in set (0.00 sec)
'''

 

五、獲取插入的最後一條數據的自增ID

import pymysql
conn=pymysql.connect(host='localhost',user='root',password='123',database='egon')
cursor=conn.cursor()

sql='insert into userinfo(name,password) values("xxx","123");'
rows=cursor.execute(sql)
print(cursor.lastrowid) #在插入語句後查看

conn.commit()

cursor.close()
conn.close()

 

5、SQLAchemy

  SQLAlchemy是Python編程語言下的一款ORM框架,該框架創建在數據庫API之上,使用關係對象映射進行數據庫操做,簡言之即是:將對象轉換成SQL,而後使用數據API執行SQL並獲取執行結果。(程序猿不用寫SQL語句哦)

# 安裝 pip3 install SQLAlchemy

#一、使用者經過ORM對象提交命令
#二、將命令交給SQLAlchemy Core(Schema/Types  SQL Expression Language)轉換成SQL
#三、使用 Engine/ConnectionPooling/Dialect 進行數據庫操做
    #3.一、匹配使用者事先配置好的egine
    #3.二、egine從鏈接池中取出一個連接
    #3.三、基於該連接經過Dialect調用DB API,將SQL轉交給它去執行

  簡化爲兩個過程:

#第一個階段(流程1-2):將SQLAlchemy的對象換成可執行的sql語句

#第二個階段(流程3):將sql語句交給數據庫執行

  若是咱們不依賴於SQLAlchemy的轉換而本身寫好sql語句,那是否是意味着能夠直接從第二個階段開始執行了,事實上正是如此,咱們徹底能夠只用SQLAlchemy執行純sql語句,以下

from sqlalchemy import create_engine

#1 準備
# 須要事先安裝好pymysql
# 須要事先建立好數據庫:create database db1 charset utf8;

#2 建立引擎
egine=create_engine('mysql+pymysql://root@127.0.0.1/db1?charset=utf8')

#3 執行sql
# egine.execute('create table if not EXISTS t1(id int PRIMARY KEY auto_increment,name char(32));')

# cur=egine.execute('insert into t1 values(%s,%s);',[(1,"egon1"),(2,"egon2"),(3,"egon3")]) #按位置傳值

# cur=egine.execute('insert into t1 values(%(id)s,%(name)s);',name='egon4',id=4) #按關鍵字傳值

#4 新插入行的自增id
# print(cur.lastrowid)

#5 查詢
cur=egine.execute('select * from t1')

cur.fetchone() #獲取一行
cur.fetchmany(2) #獲取多行
cur.fetchall() #獲取全部行

  SQLAlchemy自己沒法操做數據庫,其必須以來pymsql等第三方插件,Dialect用於和數據API進行交流,根據配置文件的不一樣調用不一樣的數據庫API,從而實現對數據庫的操做,如:

#一、MySQL-Python
    mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
   
#二、pymysql
    mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
   
#三、MySQL-Connector
    mysql+mysqlconnector://<user>:<password>@<host>[:<port>]/<dbname>
   
#四、cx_Oracle
    oracle+cx_oracle://user:pass@host:port/dbname[?key=value&key=value...]

更多詳見:http://docs.sqlalchemy.org/en/latest/dialects/index.html 

 

一、建立表

#  在ORM框架中:

#  類  ==>表
#  對象  ==>表中的一行記錄

四張表:業務線, 服務, 用戶, 角色,利用ORM建立出它們,並創建好它們之間的關係

from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column,Integer,String,DateTime,Enum,ForeignKey,UniqueConstraint,ForeignKeyConstraint,Index from sqlalchemy.orm import sessionmaker egine=create_engine('mysql+pymysql://root:123@127.0.0.1:3306/db1?charset=utf8',max_overflow=5) Base=declarative_base() #建立單表:業務線
class Business(Base): __tablename__='business' id=Column(Integer,primary_key=True,autoincrement=True) bname=Column(String(32),nullable=False,index=True) #多對一:多個服務能夠屬於一個業務線,多個業務線不能包含同一個服務
class Service(Base): __tablename__='service' id=Column(Integer,primary_key=True,autoincrement=True) sname=Column(String(32),nullable=False,index=True) ip=Column(String(15),nullable=False) port=Column(Integer,nullable=False) business_id=Column(Integer,ForeignKey('business.id')) __table_args__=( UniqueConstraint(ip,port,name='uix_ip_port'), Index('ix_id_sname',id,sname) ) #一對一:一種角色只能管理一條業務線,一條業務線只能被一種角色管理
class Role(Base): __tablename__='role' id=Column(Integer,primary_key=True,autoincrement=True) rname=Column(String(32),nullable=False,index=True) priv=Column(String(64),nullable=False) business_id=Column(Integer,ForeignKey('business.id'),unique=True) #多對多:多個用戶能夠是同一個role,多個role能夠包含同一個用戶
class Users(Base): __tablename__='users' id=Column(Integer,primary_key=True,autoincrement=True) uname=Column(String(32),nullable=False,index=True) class Users2Role(Base): __tablename__='users2role' id=Column(Integer,primary_key=True,autoincrement=True) uid=Column(Integer,ForeignKey('users.id')) rid=Column(Integer,ForeignKey('role.id')) __table_args__=( UniqueConstraint(uid,rid,name='uix_uid_rid'), ) def init_db(): Base.metadata.create_all(egine) def drop_db(): Base.metadata.drop_all(egine) if __name__ == '__main__': init_db()

 

二、增刪改查

表結構:

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column,Integer,String,ForeignKey
from sqlalchemy.orm import sessionmaker

egine=create_engine('mysql+pymysql://root@127.0.0.1:3306/db1?charset=utf8',max_overflow=5)

Base=declarative_base()


#多對一:假設多個員工能夠屬於一個部門,而多個部門不能有同一個員工(只有建立公司才把員工當駱駝用,一個員工身兼數職)
class Dep(Base):
    __tablename__='dep'
    id=Column(Integer,primary_key=True,autoincrement=True)
    dname=Column(String(64),nullable=False,index=True)

class Emp(Base):
    __tablename__='emp'
    id=Column(Integer,primary_key=True,autoincrement=True)
    ename=Column(String(32),nullable=False,index=True)
    dep_id=Column(Integer,ForeignKey('dep.id'))

def init_db():
    Base.metadata.create_all(egine)

def drop_db():
    Base.metadata.drop_all(egine)

drop_db()
init_db()
Session=sessionmaker(bind=egine)
session=Session()

 

#
row_obj=Dep(dname='銷售') #按關鍵字傳參,無需指定id,因其是自增加的
session.add(row_obj)
session.add_all([
    Dep(dname='技術'),
    Dep(dname='運營'),
    Dep(dname='人事'),
])

session.commit()

 

#
session.query(Dep).filter(Dep.id > 3).delete()
session.commit()

 

#
session.query(Dep).filter(Dep.id > 0).update({'dname':'哇哈哈'})
session.query(Dep).filter(Dep.id > 0).update({'dname':Dep.dname+'_SB'},synchronize_session=False)
session.query(Dep).filter(Dep.id > 0).update({'id':Dep.id*100},synchronize_session='evaluate')

session.commit()

 

#查全部,取全部字段
res=session.query(Dep).all() #for row in res:print(row.id,row.dname)

#查全部,取指定字段
res=session.query(Dep.dname).order_by(Dep.id).all() #for row in res:print(row.dname)

res=session.query(Dep.dname).first()

#過濾查
res=session.query(Dep).filter(Dep.id > 1,Dep.id <1000) #逗號分隔,默認爲and
print([(row.id,row.dname) for row in res])

 

三、條件、通配符、limit、排序、分組、連表、組合

#  準備表結構和數據

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column,Integer,String,ForeignKey
from sqlalchemy.orm import sessionmaker

egine=create_engine('mysql+pymysql://root:123@127.0.0.1:3306/db1?charset=utf8',max_overflow=5)

Base=declarative_base()


#多對一:假設多個員工能夠屬於一個部門,而多個部門不能有同一個員工(只有建立公司才把員工當駱駝用,一個員工身兼數職)
class Dep(Base):
    __tablename__='dep'
    id=Column(Integer,primary_key=True,autoincrement=True)
    dname=Column(String(64),nullable=False,index=True)

class Emp(Base):
    __tablename__='emp'
    id=Column(Integer,primary_key=True,autoincrement=True)
    ename=Column(String(32),nullable=False,index=True)
    dep_id=Column(Integer,ForeignKey('dep.id'))

def init_db():
    Base.metadata.create_all(egine)

def drop_db():
    Base.metadata.drop_all(egine)

drop_db()
init_db()
Session=sessionmaker(bind=egine)
session=Session()

# 準備數據
session.add_all([
    Dep(dname='技術'),
    Dep(dname='銷售'),
    Dep(dname='運營'),
    Dep(dname='人事'),
])

session.add_all([
    Emp(ename='林峯峯',dep_id=1),
    Emp(ename='李傑姐',dep_id=1),
    Emp(ename='武琪琪',dep_id=1),
    Emp(ename='元日天',dep_id=2),
    Emp(ename='李鋼彈',dep_id=3),
    Emp(ename='張二丫',dep_id=4),
    Emp(ename='李坦克',dep_id=2),
    Emp(ename='王大炮',dep_id=4),
    Emp(ename='牛榴彈',dep_id=3)
])

session.commit()
#1、條件
sql=session.query(Emp).filter_by(ename='林海峯') #filter_by只能傳參數:什麼等於什麼
res=sql.all() #sql語句的執行結果

res=session.query(Emp).filter(Emp.id>0,Emp.ename == '林海峯').all() #filter內傳的是表達式,逗號分隔,默認爲and,
res=session.query(Emp).filter(Emp.id.between(1,3),Emp.ename == '林海峯').all()
res=session.query(Emp).filter(Emp.id.in_([1,3,99,101]),Emp.ename == '林海峯').all()
res=session.query(Emp).filter(~Emp.id.in_([1,3,99,101]),Emp.ename == '林海峯') #~表明取反,轉換成sql就是關鍵字not

from sqlalchemy import and_,or_
res=session.query(Emp).filter(and_(Emp.id > 0,Emp.ename=='林海峯')).all()
res=session.query(Emp).filter(or_(Emp.id < 2,Emp.ename=='功夫熊貓')).all()
res=session.query(Emp).filter(
    or_(
        Emp.dep_id == 3,
        and_(Emp.id > 1,Emp.ename=='功夫熊貓'),
        Emp.ename != ''
    )
).all()


#2、通配符
res=session.query(Emp).filter(Emp.ename.like('%海_%')).all()
res=session.query(Emp).filter(~Emp.ename.like('%海_%')).all()

#3、limit
res=session.query(Emp)[0:5:2]

#4、排序
res=session.query(Emp).order_by(Emp.dep_id.desc()).all()
res=session.query(Emp).order_by(Emp.dep_id.desc(),Emp.id.asc()).all()

#5、分組
from sqlalchemy.sql import func

res=session.query(Emp.dep_id).group_by(Emp.dep_id).all()
res=session.query(
    func.max(Emp.dep_id),
    func.min(Emp.dep_id),
    func.sum(Emp.dep_id),
    func.avg(Emp.dep_id),
    func.count(Emp.dep_id),
).group_by(Emp.dep_id).all()


res=session.query(
    Emp.dep_id,
    func.count(1),
).group_by(Emp.dep_id).having(func.count(1) > 2).all()


#6、連表
#笛卡爾積
res=session.query(Emp,Dep).all() #select * from emp,dep;

#where條件
res=session.query(Emp,Dep).filter(Emp.dep_id==Dep.id).all()
# for row in res:
#     emp_tb=row[0]
#     dep_tb=row[1]
#     print(emp_tb.id,emp_tb.ename,dep_tb.id,dep_tb.dname)

#內鏈接
res=session.query(Emp).join(Dep)
#join默認爲內鏈接,SQLAlchemy會自動幫咱們經過foreign key字段去找關聯關係
#可是上述查詢的結果均爲Emp表的字段,這樣鏈表還有毛線意義,因而咱們修改成
res=session.query(Emp.id,Emp.ename,Emp.dep_id,Dep.dname).join(Dep).all()

#左鏈接:isouter=True
res=session.query(Emp.id,Emp.ename,Emp.dep_id,Dep.dname).join(Dep,isouter=True).all()

#右鏈接:同左鏈接,只是把兩個表的位置換一下


#7、組合
q1=session.query(Emp.id,Emp.ename).filter(Emp.id > 0,Emp.id < 5)
q2=session.query(Emp.id,Emp.ename).filter(
    or_(
        Emp.ename.like('%海%'),
        Emp.ename.like('%昊%'),
    )
)
res1=q1.union(q2) #組合+去重
res2=q1.union_all(q2) #組合,不去重

print([i.ename for i in q1.all()]) #['林海峯', '李傑', '武配齊', '元昊']
print([i.ename for i in q2.all()]) #['林海峯', '元昊']
print([i.ename for i in res1.all()]) #['林海峯', '李傑', '武配齊', '元昊']
print([i.ename for i in res2.all()]) #['林海峯', '李傑', '武配齊', '元昊', '元昊', '林海峯']

 

四、子查詢

形式一: 子查詢當作一張表來用,調用subquery()

#示例:查出id大於2的員工,當作子查詢的表使用

#原生SQL: # select * from (select * from emp where id > 2);

#ORM:
res=session.query( session.query(Emp).filter(Emp.id > 8).subquery() ).all()

形式二:子查詢當作in的範圍用,調用in_

#示例:#查出銷售部門的員工姓名

#原生SQL: # select ename from emp where dep_id in (select id from dep where dname='銷售');

#ORM:
res=session.query(Emp.ename).filter(Emp.dep_id.in_( session.query(Dep.id).filter_by(dname='銷售'), #傳的是參數
    # session.query(Dep.id).filter(Dep.dname=='銷售') #傳的是表達式
)).all()

形式三:子查詢當作select後的字段,調用as_scalar()

#示例:查詢全部的員工姓名與部門名

#原生SQL: # select ename as 員工姓名,(select dname from dep where id = emp.dep_id) as 部門名 from emp;

#ORM:
sub_sql=session.query(Dep.dname).filter(Dep.id==Emp.dep_id) #SELECT dep.dname FROM dep, emp WHERE dep.id = emp.dep_id
sub_sql.as_scalar() #as_scalar的功能就是把上面的sub_sql加上了括號
 res=session.query(Emp.ename,sub_sql.as_scalar()).all()

有三種形式的子查詢,注意:子查詢的sql必須用括號包起來,尤爲在形式三中須要注意這一點

 

五、正查、反查

#  修改一下表

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column,Integer,String,ForeignKey
from sqlalchemy.orm import sessionmaker,relationship

egine=create_engine('mysql+pymysql://root@127.0.0.1:3306/db1?charset=utf8',max_overflow=5)

Base=declarative_base()

class Dep(Base):
    __tablename__='dep'
    id=Column(Integer,primary_key=True,autoincrement=True)
    dname=Column(String(64),nullable=False,index=True)

class Emp(Base):
    __tablename__='emp'
    id=Column(Integer,primary_key=True,autoincrement=True)
    ename=Column(String(32),nullable=False,index=True)
    dep_id=Column(Integer,ForeignKey('dep.id'))

    #在ForeignKey所在的類內添加relationship的字段,注意:
    #1:Dep是類名
    #2:depart字段不會再數據庫表中生成字段
    #3:depart用於Emp表查詢Dep表(正向查詢),而xxoo用於Dep表查詢Emp表(反向查詢),
    depart=relationship('Dep',backref='xxoo') 

def init_db():
    Base.metadata.create_all(egine)

def drop_db():
    Base.metadata.drop_all(egine)

drop_db()
init_db()
Session=sessionmaker(bind=egine)
session=Session()

# 準備數據
session.add_all([
    Dep(dname='技術'),
    Dep(dname='銷售'),
    Dep(dname='運營'),
    Dep(dname='人事'),
])

session.add_all([
    Emp(ename='林海峯',dep_id=1),
    Emp(ename='李傑',dep_id=1),
    Emp(ename='武配齊',dep_id=1),
    Emp(ename='元昊',dep_id=2),
    Emp(ename='李鋼彈',dep_id=3),
    Emp(ename='張二丫',dep_id=4),
    Emp(ename='李坦克',dep_id=2),
    Emp(ename='王大炮',dep_id=4),
    Emp(ename='牛榴彈',dep_id=3)
])

session.commit()

標準連表查詢

# 示例:查詢員工名與其部門名
res=session.query(Emp.ename,Dep.dname).join(Dep) #迭代器
for row in res:
    print(row[0],row[1]) #等同於print(row.ename,row.dname)

基於relationship的正查、反查

#SQLAlchemy的relationship在內部幫咱們作好表的連接

#查詢員工名與其部門名(正向查)
res=session.query(Emp)
for row in res:
    print(row.ename,row.id,row.depart.dname)


#查詢部門名以及該部門下的員工(反向查)
res=session.query(Dep)
for row in res:
    # print(row.dname,row.xxoo)
    print(row.dname,[r.ename for r in row.xxoo])
相關文章
相關標籤/搜索