索引,是數據庫中專門用於幫助用戶快速查詢數據的一種數據結構。相似於字典中的目錄,查找字典內容時能夠根據目錄查找到數據的存放位置,而後直接獲取便可。html
索引合併,使用多個單列索引組合搜索,組合索引效率 > 索引合併
覆蓋索引,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);
如上建立組合索引以後,查詢:(最左匹配原則)
注意:對於同時搜索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分頁是一個值得關注的問題
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 """
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
#語法: # 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
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('登陸失敗')
注意:符號 -- 會註釋掉它以後的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的規矩來。
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()
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) '''
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()
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])
# 準備表結構和數據 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])