# Python 進階語法使用 ###### tags: `Python/SQL商業資料分析` , `Python` [TOC] # Python SQL資料庫串接與檔案操作 ## 建立範例環境設定 將以下 SQL 內容複製貼上到 phpMyAdmin SQL 輸入框執行,即可建立學習使用的資料表和資料集(若已經有建立 demo_shop 資料庫和資料表可以沿用,不用重複新增)。 預計新增資料表: - users:使用者資訊 - products:商品資訊 - orders:訂單資訊 - order_details:訂單詳細資訊 1. **使用 XAMPP 啟動資料庫** 2. **開啟[phpMyAdmin](http://localhost/phpmyadmin)** 3. **建立資料庫** 4. **在 phpMyAdmin SQL 輸入框中新增資料表和資料集**(若已建立可以沿用): ![](https://i.imgur.com/E4ChvsO.jpg) ```sql /* PRIMARY KEY 加入 AUTO_INCREMENT 可以在新增時自動累加 id 數值,就不用手動輸入 */ CREATE table users ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(30) NOT NULL, email VARCHAR(120) NOT NULL, age INT NOT NULL, created_at DATETIME ); /* INSERT INTO 語法加入指定要插入的屬性欄位,就可以明確指出要插入的欄位,這樣就可以忽略自動產生或有預設值的欄位 */ INSERT INTO users (name, email, age, created_at) VALUES ('Jack Hung', 'jackh32@gmail.com', 20, '2019-07-24 17:11:01'), ('Tony Liu', 'tonykk@gmail.com', 62, '2020-06-03 17:11:01'), ('Amy Chang', 'amychang@gmail.com', 32, '2020-05-11 17:11:01'); CREATE table products ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(30) NOT NULL, price INT NOT NULL, weight DECIMAL(5, 2), category VARCHAR(20), created_at DATETIME ); /* INSERT INTO 資料表 (欄位) VALUES` 後面可以透過 , 分隔多筆資料,就可以一次新增多筆資料 */ INSERT INTO products (name, price, weight, category, created_at) VALUES ('華速 intel i3 筆電', 20000, 2.12, 'NB', '2020-05-11 17:11:01'), ('Mac Pro 筆電', 62000, 1.4, 'NB', '2020-05-11 17:11:01'), ('微興電競筆電', 32000, 3.00, 'NB', '2020-05-11 17:11:01'), ('戈林冰箱', 22000, 13.78, '3C', '2020-05-11 17:11:01'), ('三力冰箱', 52000, 23.18, '3C', '2020-05-11 17:11:01'), ('C 語言入門', 420, 0.31, 'Book', '2020-05-11 17:11:01'), ('python3 實戰', 580, 0.28, 'Book', '2020-05-11 17:11:01'), ('JavaScript 英雄', 1000, 0.12, 'Book', '2020-05-11 17:11:01'), ('Java 資料分析', 340, -1, 'Book', '2020-05-11 17:11:01'), ('python 資料分析', 640, -0.43, 'Book', '2020-05-11 17:11:01'); CREATE table orders ( id INT PRIMARY KEY AUTO_INCREMENT, amount INT NOT NULL, -- 先宣告欄位,再設定外鍵 FOREIGN KEY REFERENCES customer_id INT, created_at DATETIME, FOREIGN KEY (customer_id) REFERENCES users(id) ); INSERT INTO orders (amount, customer_id, created_at) VALUES (188420, 1, '2020-05-11 17:11:01'), (54000, 3, '2019-04-13 17:11:01'), (104420, 2, '2020-06-21 17:11:01'), (52420, 2, '2020-05-01 17:11:01'), (104000, 2, '2020-07-11 17:11:01'), (32420, 2, '2019-03-24 17:11:01'); CREATE table order_details ( id INT PRIMARY KEY AUTO_INCREMENT, -- 先宣告欄位,再設定外鍵 FOREIGN KEY REFERENCES order_id INT, product_id INT, created_at DATETIME, FOREIGN KEY (order_id) REFERENCES orders(id), FOREIGN KEY (product_id) REFERENCES products(id) ); INSERT INTO order_details (order_id, product_id, created_at) VALUES (1, 1, '2020-05-11 17:11:01'), (1, 2, '2020-05-11 17:11:01'), (1, 3, '2020-05-11 17:11:01'), (1, 4, '2020-05-11 17:11:01'), (1, 5, '2020-05-11 17:11:01'), (1, 6, '2020-05-11 17:11:01'), (2, 3, '2020-04-13 17:11:01'), (2, 4, '2020-04-13 17:11:01'), (3, 5, '2020-06-21 17:11:01'), (3, 6, '2020-06-21 17:11:01'), (3, 5, '2020-06-21 17:11:01'), (4, 5, '2020-05-01 17:11:01'), (4, 6, '2020-05-01 17:11:01'), (5, 5, '2020-07-11 17:11:01'), (5, 5, '2020-07-11 17:11:01'), (6, 6, '2020-03-24 17:11:01'), (6, 3, '2020-03-24 17:11:01'); ``` 5. 使用 `PyMySQL` 這個 Python 套件操作資料庫。首先,開啟 Anaconda Prompt 終端機安裝套件: ![](https://i.imgur.com/yGAZL2A.jpg) 6. Windows/Mac 建立資料庫使用者 在啟動 XAMPP 開始操作資料庫前我們可以進入 phpmyadmin 後台新增除了 root 管理員外的資料庫使用者。 ![](https://i.imgur.com/kEWB9tJ.png) ![](https://i.imgur.com/JcEpdCm.png) ![](https://i.imgur.com/IncElyC.png) - 進入 `phpmyadmin` 管理後台 - 進入使用者管理分頁 - 新增使用者(自訂使用者名稱和密碼) - 主機名稱設定為 `localhost`,若 Mac 則改設定為 % 因為 Mac 版本的伺服器位置不同,若設定為 localhost 會無法登入。 - 給予使用者查詢、新增、更新和刪除權限 - 執行新增使用者 - 可以使用該使用者帳號密碼登入資料庫 ## 整合 MariaDB/MySQL 與 Python 完成環境設定後,開始使用 `PyMySQL` 串接我們的 `MariaDB` 資料庫(PyMySQL 可以使用在 MySQL 和 MariaDB)。PyMySQL 相關介紹可以[參考官方網站](https://github.com/PyMySQL/PyMySQL)。 打開 VS Code 編輯器建立一個資料夾放我們要使用 Python 來操作資料庫的程式,建立程式檔案 demo_db.py: ![](https://i.imgur.com/4ZGjAff.png) ```python # 引用 pymsql 套件 import pymysql # 建立和資料庫連線,參數為資料庫系統位址(localhost 為本機電腦別名), 帳號(預設為 root,實務上不建議直接使用,建議可以另外建立資料庫用者和密碼), 密碼(預設為空), 資料庫名稱 # charset 為使用編碼,cursorclass 則使用 dict 取代 tuple 當作回傳資料格式 connection = pymysql.connect(host='localhost', user='root', password='', # 資料庫預設為 3306 若自己有更改不同 port 請依照需求更改 port=3306, db='demo_shop', charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor) ``` > 預設我們 MariaDB 若沒有特別設定帳號是 root,密碼為空值。但實務上使用建議不要使用 root 管理員權限,另外於 phpMyAdmin 或下 SQL 語法建立新的使用者帳號密碼(但一般資料庫權限不是資料分析師在管理) 透過 cursor 來操作 SQL 語法,注意到我們使用 %s 當作佔位符號(placeholder)而非直接寫死 SQL 語法。在 Python MySQL /MariaDB SQL 敘述中 %s 為佔位符號,執行時會被轉成 SQL 敘述對應值,SQL 敘述為字串,所以即便傳入值為整數,也不用更改為 %d 或其他符號。 ```python # 使用 try...finally 錯誤處理,可以讓程式即便錯誤最後會關閉資料庫連線避免浪費資源 try: # 使用 with...as 可以讓我們程式正確執行下自動關閉資料庫連線 with connection.cursor() as cursor: # 執行 SQL 敘述查詢資料,使用佔位符號 %s 而不是直接寫死,提升執行效率和降低資安風險 sql = 'SELECT id, name FROM users WHERE email=%s' # 語法傳入第一個參數,後面參數為 %s 內容 cursor.execute(sql, ('jackh32@gmail.com',)) # 取出第一筆結果 result = cursor.fetchone() print(result) finally: # 即便程式錯誤也會執行到這行關閉資料庫連線 connection.close() ``` 根據 `PyMySQL` 官方文件說明 `cursorclass` 是決定查詢回傳的資料格式,`cursor` 物件則為負責和資料庫互動的指標。預設 connect 不設定 `cursorclass` 參數回傳是 tuple 格式,另外一種是傳入 `pymysql.cursors.DictCursor `類別當作 `cursorclass` 參數,操作過程中回傳查詢資料會是 dict 格式。參考文件: - https://pymysql.readthedocs.io/en/latest/modules/connections.html - https://pymysql.readthedocs.io/en/latest/modules/cursors.html#pymysql.cursors.DictCursor 若使用 `pymysql.cursors.DictCursor` 當作 `cursorclass` 參數的程式碼: ```python # 引用 pymsql 套件 import pymysql # 建立和資料庫連線,參數為資料庫系統位址(localhost 為本機電腦別名), 帳號(預設為 root,實務上不建議直接使用), 密碼(預設為空), 資料庫名稱 # charset 為使用編碼,cursorclass 則使用 dict 取代 tuple 當作回傳資料格式 connection = pymysql.connect(host='localhost', user='root', password='', # 資料庫預設為 3306 若自己有更改不同 port 請依照需求更改 port=3306, db='demo_shop', charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor) ``` ## 查詢資料 ```python # 引用 pymsql 套件 import pymysql # 建立和資料庫連線,參數為資料庫系統位址(localhost 為本機電腦別名), 帳號(預設為 root,實務上不建議直接使用), 密碼(預設為空), 資料庫名稱 # charset 為使用編碼,cursorclass 則使用 dict 取代 tuple 當作回傳資料格式 connection = pymysql.connect(host='localhost', user='root', password='', # 資料庫預設為3306若自己有更改不同port請依照需求更改 port=3306, db='demo_shop', charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor) # 使用 try...finally 錯誤處理,可以讓程式即便錯誤最後會關閉資料庫連線避免浪費資源 try: # 使用 with...as 可以讓我們程式正確執行下自動關閉資料庫連線 with connection.cursor() as cursor: # 執行 SQL 敘述查詢資料,使用佔位符號 %s 而不是直接寫死,提升執行效率和降低資安風險 sql = 'SELECT id, name FROM users WHERE email=%s' cursor.execute(sql, ('jackh32@gmail.com',)) # 取出第一筆結果 result = cursor.fetchone() print(result) finally: # 即便程式錯誤也會執行到這行關閉資料庫連線 connection.close() ``` ## 新增資料 ```python # 引用 pymsql 套件 import pymysql # 建立和資料庫連線,參數為資料庫系統位址(localhost 為本機電腦別名), 帳號(預設為 root,實務上不建議直接使用), 密碼(預設為空), 資料庫名稱 # charset 為使用編碼,cursorclass 則使用 dict 取代 tuple 當作回傳資料格式 connection = pymysql.connect(host='localhost', user='root', password='', # 資料庫預設為 3306 若自己有更改不同 port 請依照需求更改 port=3306, db='demo_shop', charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor) # 使用 try...finally 錯誤處理,可以讓程式即便錯誤最後會關閉資料庫連線避免浪費資源 try: # 使用 with...as 可以讓我們程式正確執行下自動關閉資料庫連線 with connection.cursor() as cursor: # 執行 SQL 敘述新增資料,使用佔位符號 %s 而不是直接寫死,提升執行效率和降低資安風險 sql = 'INSERT INTO users (name, email, age, created_at) VALUES (%s, %s, %s, %s)' cursor.execute(sql, ('Henry Chao', 'henry123@gmail.com', 28, '2020-05-11 17:11:01')) # 有修改到資料內容需要將變更提交到資料庫才算完成操作 connection.commit() # 使用 with...as 可以讓我們程式正確執行下自動關閉資料庫連線 with connection.cursor() as cursor: # 執行 SQL 敘述查詢資料,使用佔位符號 %s 而不是直接寫死,提升執行效率和降低資安風險 sql = 'SELECT id, name FROM users WHERE email=%s' cursor.execute(sql, ('henry123@gmail.com',)) # 取出第一筆結果 result = cursor.fetchone() print(result) finally: # 即便程式錯誤也會執行到這行關閉資料庫連線 connection.close() ``` ## 更新資料 ```python # 引用 pymsql 套件 import pymysql # 建立和資料庫連線,參數為資料庫系統位址(localhost 為本機電腦別名), 帳號(預設為 root,實務上不建議直接使用), 密碼(預設為空), 資料庫名稱 # charset 為使用編碼,cursorclass 則使用 dict 取代 tuple 當作回傳資料格式 connection = pymysql.connect(host='localhost', user='root', password='', # 資料庫預設為 3306 若自己有更改不同 port 請依照需求更改 port=3306, db='demo_shop', charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor) # 使用 try...finally 錯誤處理,可以讓程式即便錯誤最後會關閉資料庫連線避免浪費資源 try: # 使用 with...as 可以讓我們程式正確執行下自動關閉資料庫連線 with connection.cursor() as cursor: # 執行 SQL 敘述更新資料,使用佔位符號 %s 而不是直接寫死,提升執行效率和降低資安風險 sql = 'UPDATE users SET name=%s WHERE email=%s' cursor.execute(sql, ('Henry Wu', 'henry123@gmail.com',)) connection.commit() finally: # 即便程式錯誤也會執行到這行關閉資料庫連線 connection.close() ``` ## 刪除資料 ```python # 引用 pymsql 套件 import pymysql # 建立和資料庫連線,參數為資料庫系統位址(localhost 為本機電腦別名), 帳號(預設為 root,實務上不建議直接使用), 密碼(預設為空), 資料庫名稱 # charset 為使用編碼,cursorclass 則使用 dict 取代 tuple 當作回傳資料格式 connection = pymysql.connect(host='localhost', user='root', password='', # 資料庫預設為 3306 若自己有更改不同 port 請依照需求更改 port=3306, db='demo_shop', charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor) # 使用 try...finally 錯誤處理,可以讓程式即便錯誤最後會關閉資料庫連線避免浪費資源 try: # 使用 with...as 可以讓我們程式正確執行下自動關閉資料庫連線 with connection.cursor() as cursor: # 執行 SQL 敘述刪除資料,使用佔位符號 %s 而不是直接寫死,提升執行效率和降低資安風險 sql = 'DELETE FROM users WHERE email=%s' cursor.execute(sql, ('henry123@gmail.com',)) connection.commit() finally: # 即便程式錯誤也會執行到這行關閉資料庫連線 connection.close() ``` ## 檔案處理基礎 使用內建函式 `open()` 來開啟檔案並用 `close()` 來關閉檔案。使用 `open()` 會建立一個 file 物件,在 Python 中可以同時讀寫多個檔案,依靠著 file 物件來識別目前操作的檔案。 ```python # 寫入模式 file_object = open('./demo.txt', 'w') # 讀取模式 file_object = open('./demo.txt', 'r') ``` 下是模式的列表(一般分為 read / write / append 三種模式,而 +,代表是否支援檔案更新,代表支援讀寫功能): | mode 模式 | 當檔案已經存在 | 檔案不存在 | |:--------- | ---------------------- | ------------ | | w | 開啟唯讀檔案 | 發生錯誤 | | r | 清除檔案內容後寫入 | 建立寫入檔案 | | a | 將內容寫到檔案最後面 | 建立寫入檔案 | | w+ | 開啟讀寫檔案 | 發生錯誤 | | r+ | 清除檔案內容後讀寫入 | 建立讀寫檔案 | | a+ | 將內容讀寫到檔案最後面 | 建立讀寫檔案 | ### Python 開關檔案 使用 `open('檔案路徑', '讀寫模式')` 來取得 `file object`。最後 close 關掉檔案。記得開檔案後要關掉,避免造成記憶體流失系統資源的浪費。 ```python file_object = open('./demo.txt', 'r') file_object.close() ``` ### Python 讀取檔案 在 Python 主要用來讀取檔案的方法有三個:`read([size])`、`readline()` 和 `readlines()`。 - `read([size])`:從檔案當前位置起讀取 size 大小的位元組。若無參數 size,則表示讀取到檔案結束為止(讀取整個檔案),回傳整個檔案內容。 ```python file_object = open('./demo.txt', 'r') # 讀取全部檔案案內容變成字串 print(file_object.read()) ``` - `readline([size])`:每次讀出一行內容,因此在讀取時佔用記憶體較小,比較適合大型檔案,回傳一行字串。也可選擇性傳入 size 參數 ```python file_obj = open('./data.txt', 'r') # 每次讀取一行,_ 是一個佔位符號,當沒有使用到變數時可以使用 for _ in range(3): print(file_obj.readline()) file_obj.close() ``` - `readlines()`:讀取整個檔案所有行,並將每行內容儲存在一個列表(list)中,每一行作為一個列表元素。但當讀取大型檔案會比較佔記憶體,不建議用於讀取大型檔案 ```python # 讀取內容成為列表,檔案一行代表一個元素,可以使用迭代方式操作 file_line_item_list = file_object.readlines() for file_line_item in file_line_item_list: print(file_line_item) file_object.close() ``` ### Python 寫入檔案 使用 `f.write('寫入的字串內容')` 可以將字串內容寫入檔案中(f 為檔案物件): ```python # 開啟檔案物件準備寫入 demo.txt 中,w 為寫入 mode file_object = open('./demo.txt', 'w') # 若需要換行可以於字串最後加入: \n 為換行符號 file_object.write('Java is awesome\n') file_object.write('Python is awesome') # 最後記得將檔案物件關閉 file_object.close() # Java is awesome # Python is awesome ``` ### with as 檔案操作 我們可以使用 `with...as` 區塊(背後原理為 [context managers](https://book.pythontips.com/en/latest/context_managers.html)),透過 `with...as` 和縮排,可以讓我們在程式執行完畢可以自動關閉檔案。 ```python with open('./demo.txt', 'a') as file_object: file_object.write('Python is awesom') ``` # 延伸閱讀 - [SQL 資料庫](https://hackmd.io/fQbgqRSYTDi2Ouis0fE3rg?both) - [Python 網頁爬蟲](https://hackmd.io/JOc4g8AjSZiokS6vZckFSw?view) - [Python 基礎語法](https://hackmd.io/4FH3w4_pQP6_dsandd45LA) - [Python 資料分析](https://hackmd.io/C5I9OzXzQCe6wJuZhwRV3Q) - [Python 資料科學與探索式資料分析](https://hackmd.io/qSceMWZWQcWsMIA9QiO1Nw?view) - [營收與使用者行為資料分析專案](https://hackmd.io/i6kRZN8JQsq57uDrKeKoLQ) - [Python專案實作 資料分析與爬蟲](https://hackmd.io/oh18KsFvSxe5Eh3ECHDJOA?view)