Bài 56: Thống kê và Ứng dụng hồi quy tuyến tính đa biến – dự báo chi tiêu của khách hàng

Bài học này Tui hướng dẫn các bạn xây dựng mô hình máy học sử dụng hồi quy tuyến tính đa biến để thống kê và dự báo chi tiêu của khách hàng, sử dụng cơ sở dữ liệu “lecturer_retails” có Số lượng dữ liệu gồm 99457 dòng, dữ liệu tải ở đây: https://tranduythanh.com/datasets/PurchaseDataset.rar

Chương trình được tách ra làm 2 nhóm chức năng chính:

  1. Chức năng thống kê:
    • (1.1) Thống kê tỉ lệ mua hàng theo giới tính
    • (1.2) Thống kê số lượng mua hàng theo độ tuổi
    • (1.3) Thống kê số lượng mua hàng theo danh mục sản phẩm
    • (1.4) Thống kê trị giá hàng hóa bán được theo danh mục
    • (1.5) Thống kê lượng hàn bán ra theo độ tuổi và danh mục
    • (1.6) Thống kê số lượng giao dịch theo phương thức thanh toán
    • (1.7) Thống kê tỉ lệ bán hàng theo Trung tâm thương mại (Shopping Mall)
    • (1.8) Thống kê trị giá hàng hóa bán được theo danh mục và giới tính
    • (1.9) Thống kê tần suất mua hàng theo độ tuổi và giới tính
    • (1.10) Thống kê biến động doanh thu theo tháng
    • (1.11) Thống kê biến động doanh thu theo tháng và theo năm
  2. Chức năng huấn luyện mô hình máy học:
    • Dự báo biến động giá theo giới tính và độ tuổi
    • Dự báo biến động giá theo giới tính, độ tuổi và phương thức thanh toán

Giao diện phần mềm cho Thống kê tương tự như dưới đây:

Giao diện phần mềm cho Máy học tương tự như dưới đây:

Dữ liệu “lecturer_retails“, được lưu trong MySQL Server có cấu trúc như mô tả chi tiết dưới đây:

Cấu trúc bảng purchasehistory như sau:

  • invoice_no
  • customer_id
  • gender
  • age
  • category
  • quantity
  • price
  • payment_method
  • invoice_date
  • shopping_mall

Số lượng dữ liệu gồm 99457 dòng. Dataset các bạn tải ở link sau, và cần import vào MySQL Server của bạn trước:

https://tranduythanh.com/datasets/PurchaseDataset.rar

Các bạn giải nén ra, trong MySQL Workbench tạo Schema tên “lecturer_retails“, sau đó import dataset vào Schema này. Cách import dataset các bạn xem lại bài học 47 cách import và export dataset

Tạo dự án “MLBAProject” có cấu trúc như dưới đây:

Vì bài này dài, và nó tổng hợp nhiều kiến thức nên Tui giải thích tổng quan như này:

  • (1) Thư mục “Assets” là thư mục tạo ra để lưu trữ các mô hình máy học được train thành công, dĩ nhiên người sử dụng có thể lưu ở bất cứ nơi nào, nhưng đề xuất lưu vào đây để dễ quản lý
  • (2) Thư mục “Connectors” là thư mục lưu thư viện kết nối và tương tác cơ sở dữ liệu MySQL Server
  • (3) Thư mục “Images” là thư mục lưu trữ các hình ảnh để sử dụng cho phần mềm
  • (4) Thư mục “Models” là thư mục chưa các lớp thư viện liên quan tới thống kê và máy học
  • (5) Thư mục “Tests” Là thư mục chứa các tập tin mã lệnh để thử nghiệm các hàm thống kê và máy học trước khi sử dụng thực tế. Thư mục này bạn có thể bỏ qua
  • (6) Thư mục “UI” là thư mục chứa các file thiết kế giao diện và generate python code cho giao diện, cũng như đồ thị
  • (7) Thư mục “Utils” là thư mục chưa thư viện xử lý tập tin, dùng để lưu mô hình máy học và tải mô hình máy học
  • (8) Cuối cùng là tập tin “App.py” để thực thi chương trình.

Vì không có nhiều thời gian, Tui không giải thích hết được các hàm trong các mã lệnh dưới đây, tuy nhiên Tui có đặt tên hàm và tên biến có ý nghĩa, nên các bạn đọc vào có thể hiểu ngay chức năng của nó. Đồng thời các thông số kết nối cơ sở dữ liệu các bạn cần đổi lại cho đúng với trên máy tính của mình.

Bước 1: Tạo và viết mã lệnh cho “Connector.py” trong thư mục “Connectors

#python -m pip install mysql-connector-python
import mysql.connector
import traceback
import pandas as pd
class Connector:
    def __init__(self,server=None, port=None, database=None, username=None, password=None):
        self.server=server
        self.port=port
        self.database=database
        self.username=username
        self.password=password
    def connect(self):
        try:
            self.conn = mysql.connector.connect(
                host=self.server,
                port=self.port,
                database=self.database,
                user=self.username,
                password=self.password)
            return self.conn
        except:
            self.conn=None
            traceback.print_exc()
        return None

    def disConnect(self):
        if self.conn != None:
            self.conn.close()

    def queryDataset(self, sql):
        try:
            cursor = self.conn.cursor()
            cursor.execute(sql)
            df = pd.DataFrame(cursor.fetchall())
            if not df.empty:
                df.columns=cursor.column_names
            return df
        except:
            traceback.print_exc()
        return None
    def getTablesName(self):
        cursor = self.conn.cursor()
        cursor.execute("Show tables;")
        results=cursor.fetchall()
        tablesName=[]
        for item in results:
            tablesName.append([tableName for tableName in item][0])
        return tablesName

Thư viện trên dùng để kết nối, đóng kết nối, truy vấn dữ liệu và danh sách các bảng trong cơ sở dữ liệu.

Bước 2: Trong thư mục “Models“, lần lượt tạo các tập tin mã lệnh Python sau:

Bước 2.1: Tạo lớp “MetricsResult.py” để lưu kết quả đánh giá mô hình, lớp này lưu các thông số: MAE, MSE, RMSE, R2_SCORE

class MetricsResult:
    def __init__(self,mae,mse,rmse,r2_score):
        self.MAE=mae
        self.MSE=mse
        self.RMSE=rmse
        self.R2_SCORE=r2_score
    def __str__(self):
        result="MAE=%s"%self.MAE+"\n"+"MSE=%s"%self.MSE+"\n"+"RMSE=%s"%self.RMSE+"\n"+"R2_SCORE=%s"%self.R2_SCORE+"\n"
        return result

Bước 2.2: Tạo lớp “TrainedModel.py” để lưu trữ mô hình máy học được trained (model) các dữ liệu của biến độc lập và biến phụ thuộc, cũng như tên các biến này. Mục đích để lưu và nạp lại mô hình theo chủ ý của ta.

class TrainedModel:
    def __init__(self,model=None,X_train=None,X_test=None,y_train=None,y_test=None,columns_input=None,column_target=None):
        self.model=model
        self.X_train = X_train
        self.X_test = X_test
        self.y_train = y_train
        self.y_test = y_test
        self.columns_input=columns_input
        self.column_target=column_target

Bước 2.3: Tạo lớp “PurchaseStatistic.py” lớp này phục vụ truy vấn và thống kê dữ liệu

from matplotlib import pyplot as plt
import seaborn as sns
import pandas as pd
class PurchaseStatistic:
    def __init__(self,connector=None):
        self.connector = connector
        self.lasted_df=None
    def execPurchaseHistory(self,tableName=None):
        if tableName==None:
            sql="select * from purchasehistory"
        else:
            sql = "select * from %s"%tableName
        self.df=self.connector.queryDataset(sql)
        self.lasted_df=self.df
        return self.df
    def printHead(self,row):
        print(self.df.head(row))
    def printTail(self,row):
        print(self.df.tail(row))
    def printInfo(self):
        print(self.df.info())
    def printDecsribe(self):
        print(self.df.describe())
    def dateProcessing(self):
        self.df['invoice_date'] = pd.to_datetime(self.df['invoice_date'] , format = '%d/%m/%Y')
        self.df['month'] = self.df['invoice_date'].dt.month
        self.df['year'] = self.df['invoice_date'].dt.year
        self.lasted_df = self.df
    def processGenderDistribution(self):
        self.dfGender = self.df.gender.value_counts().reset_index()
        self.lasted_df = self.dfGender
        return self.dfGender
    def processAgeDistribution(self):
        self.dfAges = self.df.age.value_counts().reset_index()
        self.dfAges.sort_values(by=['age'], ascending=True, inplace=True)
        self.lasted_df = self.dfAges
        return self.dfAges
    def processAgeDistribution(self,fromAge,toAge):
        self.dfAges = self.df[(self.df.age >= fromAge) & (self.df.age <= toAge)].age.value_counts().reset_index()
        self.dfAges.sort_values(by=['age'], ascending=True,inplace=True)
        self.lasted_df = self.dfAges
        return self.dfAges
    def visualizePieChart(self,df,columnLabel,columnStatistic,title,legend=True):
        explode=[0.1]
        for i in range(len(df[columnLabel])-1):
            explode.append(0)
        plt.figure(figsize=(8, 6))
        plt.pie(df[columnStatistic], labels=df[columnLabel], autopct='%1.2f%%',explode=explode)
        if legend:
            plt.legend(df[columnLabel])
        plt.title(title)
        plt.show()
    def visualizePlotChart(self,df,columnX,columnY,title):
        plt.figure(figsize=(8, 6))
        plt.plot(df[columnX], df[columnY])
        plt.legend([columnX,columnY])
        plt.title(title)
        plt.xlabel(columnX)
        plt.ylabel(columnY)
        plt.grid()
        plt.show()
    def processCategoryDistribution(self):
        self.dfCategory = self.df.category.value_counts().reset_index()
        self.lasted_df = self.dfCategory
        return self.dfCategory
    def processGenderAndCategoryCounter(self):
        self.df_gender_order = self.df[['gender', 'category']]\
                                   .groupby(['gender', 'category'])\
                                   .value_counts()\
                                   .reset_index(name="count")
        self.lasted_df = self.df_gender_order
        return self.df_gender_order
    def processCategorySpending(self):
        self.df_cate_spending = self.df.groupby(['category'])["price"].sum().reset_index(name="price")
        self.lasted_df = self.df_cate_spending
        return self.df_cate_spending
    def processGenderCategorySpending(self):
        self.df_gender_cate_spending = self.df.groupby(['gender','category'])["price"].sum().reset_index(name="price")
        self.lasted_df = self.df_gender_cate_spending
        return self.df_gender_cate_spending
    def visualizeCountPlot(self,df,columnX,columnY,hueColumn,title):
        plt.figure(figsize=(8, 6))
        ax=sns.countplot(x=columnX,hue=hueColumn,data=df)
        plt.title(title)
        plt.xlabel(columnX)
        plt.ylabel(columnY)
        plt.grid()
        plt.legend()
        plt.show()
    def visualizeBarPlot(self,df,columnX,columnY,hueColumn,title,alpha=0.8,width=0.6):
        plt.figure(figsize=(8, 6))
        plt.ticklabel_format(useOffset=False, style='plain')
        ax=sns.barplot(data=df,x=columnX,y=columnY,hue=hueColumn,alpha=alpha,width=width)
        plt.title(title)
        plt.xlabel(columnX)
        plt.ylabel(columnY)
        plt.grid()
        plt.legend()
        plt.show()
    def visualizeBarChart(self,df,columnX,columnY,title):
        plt.figure(figsize=(8, 6))
        plt.ticklabel_format(useOffset=False, style='plain')
        plt.bar(df[columnX],df[columnY])
        plt.title(title)
        plt.xlabel(columnX)
        plt.ylabel(columnY)
        plt.grid()
        plt.show()
    def visualizeScatterPlot(self,df,columnX,columnY,title):
        plt.figure(figsize=(8, 6))
        plt.ticklabel_format(useOffset=False, style='plain')
        sns.scatterplot(data=df,x= columnX,y=columnY)
        plt.title(title)
        plt.xlabel(columnX)
        plt.ylabel(columnY)
        plt.grid()
        plt.show()
    def processPaymentMethod(self):
        self.payment = self.df['payment_method'].value_counts().reset_index(name="count").rename(columns={"index": "payment_method"})
        self.lasted_df = self.payment
        return self.payment
    def processShoppingMall(self):
        self.dfShoppingMall = self.df['shopping_mall'].value_counts().reset_index(name="count").rename(columns={"index": "shopping_mall"})
        self.lasted_df = self.dfShoppingMall
        return self.dfShoppingMall
    def processAgeOrderFrequence(self):
        #self.dfAgeGender = self.df.groupby(['age', 'gender'])['age'].value_counts().reset_index(name="count")
        self.dfAgeGender = self.df[['age', 'gender']].groupby(['age', 'gender']).value_counts().reset_index(name="count")
        self.lasted_df = self.dfAgeGender
        return self.dfAgeGender
    def processAgeSalesAmount(self):
        self.dfSalesAmount = self.df.copy(deep=True)
        self.dfSalesAmount['sales_amount'] = self.dfSalesAmount['quantity'] * self.dfSalesAmount['price']
        self.lasted_df = self.dfSalesAmount
        return self.dfSalesAmount
    def processMonthlySalesAmount(self):
        self.dfMonthlySalesAmount=self.df.copy(deep=True)
        self.dfMonthlySalesAmount['sales_amount'] = self.dfMonthlySalesAmount['quantity'] * self.dfMonthlySalesAmount['price']

        self.dfMonthlySalesAmount['invoice_date'] = pd.to_datetime(self.dfMonthlySalesAmount['invoice_date'],
                                            format='%d/%m/%Y')  # convert invoice date to date time format

        self.dfMonthlySalesAmount['month'] = self.dfMonthlySalesAmount['invoice_date'].dt.month

        self.dfMonthlySalesAmount = self.dfMonthlySalesAmount.groupby('month')['sales_amount'].sum().reset_index()
        self.lasted_df = self.dfMonthlySalesAmount
        return self.dfMonthlySalesAmount
    def visualizeLinePlotChart(self,df,columnX,columnY,tile,hue=None):
        plt.figure(figsize=(8, 6))
        plt.ticklabel_format(useOffset=False,style="plain")
        sns.lineplot(data=df,x=columnX, y=columnY, marker='o', color='orange',hue=hue)
        plt.xlabel(columnX)
        plt.ylabel(columnY)
        plt.title(tile)
        plt.legend(loc='upper right')
        plt.xticks(range(1, 13), ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'])
        plt.show()
    def processMonthlyAndYearSalesAmount(self):
        self.dfMonthlyAndYearSalesAmount = self.df.copy(deep=True)
        self.dfMonthlyAndYearSalesAmount['invoice_date'] = pd.to_datetime(self.dfMonthlyAndYearSalesAmount['invoice_date'],
                                                                   format='%d/%m/%Y')
        self.dfMonthlyAndYearSalesAmount['month'] = self.dfMonthlyAndYearSalesAmount['invoice_date'].dt.month
        self.dfMonthlyAndYearSalesAmount['year'] = self.dfMonthlyAndYearSalesAmount['invoice_date'].dt.year
        self.dfMonthlyAndYearSalesAmount['sales_amount'] = self.dfMonthlyAndYearSalesAmount['quantity'] * self.dfMonthlyAndYearSalesAmount['price']
        self.dfMonthlyAndYearSalesAmount = self.dfMonthlyAndYearSalesAmount.groupby(['year', 'month'], as_index=False).agg({'sales_amount': 'sum'})
        self.lasted_df = self.dfMonthlyAndYearSalesAmount
        return self.dfMonthlyAndYearSalesAmount

Bước 2.4: Tạo lớp “PurchaseMLModel.py” lớp này cung cấp tiền xử lý và chuyển đổi dữ liệu (transformation) để phục vụ các mô hình máy học

# Features encoding
from sklearn.preprocessing import LabelEncoder
import seaborn as sns
from matplotlib import pyplot as plt

from Models.PurchaseStatistic import PurchaseStatistic


class PurchaseMLModel(PurchaseStatistic):
    def __init__(self,connector=None):
        super().__init__(connector)
        self.le = LabelEncoder()
    def processTransformByColumns(self,df,columns):
        for col in columns:
            x=df[col]
            df[col] = self.le.fit_transform(x)
    def processTransform(self):
        categorical_feature = ['gender', 'category', 'payment_method', 'shopping_mall']
        numerical_feature = ['age', 'quantity', 'month', 'year']
        dropping = ['customer_id', 'invoice_no', 'day', 'invoice_date']
        result = ['price']
        self.dfTransform=self.df.copy(deep=True)
        self.dfTransform[["day", "month", "year"]] = self.dfTransform["invoice_date"].str.split("/", expand=True)
        self.dfTransform.drop(dropping, axis=1, inplace=True)
        for col in categorical_feature:
            x=self.dfTransform[col]
            self.dfTransform[col] = self.le.fit_transform(x)
        return self.dfTransform
    def buildCorrelationMatrix(self,df):
        plt.figure(figsize=(8, 6))
        df_corr = df.corr(numeric_only=True)  # Generate correlation matrix
        ax = sns.heatmap(df_corr, annot=True)
        plt.show()
    

Bước 2.5: Tạo lớp “PurchaseLinearRegression.py” lớp này cung cấp chức năng train mô hình máy học với hồi quy đa biến:

import pandas as pd
from matplotlib import pyplot as plt

from sklearn.model_selection import train_test_split
#data modelling
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error, mean_squared_error,r2_score
from sklearn.preprocessing import StandardScaler, LabelEncoder

from Models.MetricsResult import MetricsResult
from Models.PurchaseMLModel import PurchaseMLModel
from Models.TrainedModel import TrainedModel
from Utils.FileUtil import FileUtil

class PurchaseLinearRegression(PurchaseMLModel):
    def __init__(self,connector=None):
        super().__init__(connector)
        self.le = LabelEncoder()
        self.sc_std = StandardScaler()
    def processTrain(self,columns_input,column_target,test_size,random_state):
        self.execPurchaseHistory()
        self.processTransform()
        print(self.dfTransform.columns)
        print(self.dfTransform.iloc[0])
        y = self.dfTransform[column_target]
        X = self.dfTransform[columns_input]
        print("X=",X)
        print("y=", y)
        self.X_train, self.X_test, self.y_train, self.y_test = train_test_split(X, y, test_size=test_size, random_state=random_state)
        self.trainedmodel=TrainedModel()
        self.trainedmodel.X_train=self.X_train
        self.trainedmodel.X_test=self.X_test
        self.trainedmodel.y_train=self.y_train
        self.trainedmodel.y_test=self.y_test
        self.trainedmodel.columns_input=columns_input
        self.trainedmodel.column_target=column_target
        #self.sc_std = StandardScaler()
        self.X_train = self.sc_std.fit_transform(self.X_train)
        self.X_test = self.sc_std.transform(self.X_test)
        self.lr = LinearRegression()
        self.model = self.lr.fit(self.X_train, self.y_train)
        self.trainedmodel.model=self.model
    def visualizeActualAndPredictResult(self):
        plt.figure(figsize=(8, 6))
        plt.scatter(self.lr.predict(self.X_train), self.y_train)
        plt.xlabel('Predicted value of Y')
        plt.ylabel('Real value of Y')
        plt.show()
    def evaluate(self):
        pred = self.model.predict(self.X_test)
        mae=mean_absolute_error(self.y_test, pred)
        mse = mean_squared_error(self.y_test, pred, squared=True)
        rmse = mean_squared_error(self.y_test, pred, squared=False)
        r2score=r2_score(self.y_test, pred)
        return MetricsResult(mae,mse,rmse,r2score)
    def predictPriceFromGenderAndAge(self,gender,age):
        data_gender = {'gender': ["Male", "Female"]}
        df_gender = pd.DataFrame(data=data_gender)
        df_gender_transform = self.le.fit_transform(df_gender)
        col_gender = 0
        if gender == 'Male':
            col_gender = 0
        else:
            col_gender = 1
        data = [[df_gender_transform[col_gender], age]]
        input_transform = self.sc_std.transform(data)
        pred = self.predict(input_transform)
        return pred
    def predictPriceFromGenderAndAgeAndPayment(self,gender,age,payment_method):
        data_gender= {'gender': ["Male", "Female"]}
        df_gender=pd.DataFrame(data=data_gender)
        df_gender_transform=self.le.fit_transform(df_gender)
        data_payment_method={"payment_method":["Credit Card","Debit Card","Cash"]}
        df_payment_method=pd.DataFrame(data=data_payment_method)
        df_payment_method_transform=self.le.fit_transform(df_payment_method)
        col_gender=0
        if gender == 'Male':
            col_gender=0
        else:
            col_gender = 1
        col_payment=0
        if payment_method=="Credit Card":
            col_payment=0
        elif payment_method=="Debit Card":
            col_payment=1
        else:
            col_payment = 2
        data = [[df_gender_transform[col_gender], age,df_payment_method_transform[col_payment]]]

        input_transform = self.sc_std.transform(data)
        pred = self.predict(input_transform)
        return pred
    def predict(self,columns_input):
        pred = self.model.predict(columns_input)
        return pred
    def saveModel(self,fileName):
        ret=FileUtil.saveModel(self.trainedmodel,fileName)
        return ret
    def loadModel(self,fileName):
        self.trainedmodel=FileUtil.loadModel(fileName)
        self.sc_std.fit_transform(self.trainedmodel.X_train)
        self.model=self.trainedmodel.model
        return self.model

Bước 3: Tạo các lớp test các thư viện ở bước 2 trong thư mục “Tests

Bước 3.1: Tạo “AppStatistic.py” để test các hàm thống kê. Ta có thể chạy tập tin này độc lập để test:

from Connectors.Connector import Connector
from Models.PurchaseStatistic import PurchaseStatistic

connector=Connector(server="localhost",port=3306,database="lecturer_retails",username="root",password="@Obama123")
connector.connect()
pm=PurchaseStatistic()
pm.connector=connector
pm.execPurchaseHistory()
dfGender=pm.processGenderDistribution()
print(dfGender)
pm.visualizePieChart(dfGender,"gender","count","Gender Distribution")

dfAge=pm.processAgeDistribution(30,50)
print(dfAge)
pm.visualizePlotChart(dfAge,"age","count","Age Distribution 30~50")

dfCategory=pm.processCategoryDistribution()
print(dfCategory)
pm.visualizePieChart(dfCategory,"category","count","Categories Distribution",legend=False)

dfCateSpending=pm.processCategorySpending()
print(dfCateSpending)
pm.visualizeBarChart(dfCateSpending,"category","price","Distribution category and Spending")


dfGenderCategory=pm.processGenderAndCategoryCounter()
print(dfGenderCategory)
pm.visualizeCountPlot(pm.df,"category","count","gender","Distribution gender and category")


dfPayment=pm.processPaymentMethod()
print(dfPayment)
pm.visualizePieChart(dfPayment,"payment_method","count","Payment Distribution",legend=False)

dfShoppingMall=pm.processShoppingMall()
print(dfShoppingMall)
pm.visualizePieChart(dfShoppingMall,"shopping_mall","count","Shopping Mall Distribution",legend=False)

dfGenderCateSpending=pm.processGenderCategorySpending()
print(dfGenderCateSpending)
pm.visualizeBarPlot(dfGenderCateSpending,"category","price","gender","Male and Female category Total Price Spend")

dfAgeGender=pm.processAgeOrderFrequence()
print(dfAgeGender)
pm.visualizeScatterPlot(dfAgeGender,"age","count","Age VS Order Frequence")

dfMonthlySalesAmount=pm.processMonthlySalesAmount()
print(dfMonthlySalesAmount)
pm.visualizeLinePlotChart(dfMonthlySalesAmount,"month","sales_amount","Monthly Variation in Sales Amount")

dfMonthlyAndYearSalesAmount=pm.processMonthlyAndYearSalesAmount()
print(dfMonthlyAndYearSalesAmount)
pm.visualizeLinePlotChart(dfMonthlyAndYearSalesAmount,"month","sales_amount","Monthly Variation in Sales Amount Over Years",hue="year")

Bước 3.2: Tạo “AppModel.py” để test tiền xử lý và transformation. Ta có thể chạy tập tin này độc lập để test:

from Connectors.Connector import Connector
from Models.PurchaseMLModel import PurchaseMLModel

connector=Connector(server="localhost",port=3306,database="lecturer_retails",username="root",password="@Obama123")
connector.connect()
pm=PurchaseMLModel(connector)
pm.execPurchaseHistory()

dfTransform=pm.processTransform()
print(dfTransform.head())
pm.buildCorrelationMatrix(dfTransform)

Bước 3.3: Tạo “AppLinearRegression.py” để test train mô hình máy học, cũng như prediction. Ta có thể chạy tập tin này độc lập để test:

from Connectors.Connector import Connector
from Models.PurchaseLinearRegression import PurchaseLinearRegression

connector=Connector(server="localhost",port=3306,database="lecturer_retails",username="root",password="@Obama123")
connector.connect()
pm=PurchaseLinearRegression(connector=connector)
pm.processTrain(["gender","age"],"price",0.2,0)
#pm.processTrain(["gender","age","payment_method"],"price")
#pm.visualizeActualAndPredictResult()

eresult=pm.evaluate()
print(eresult)

gender="Male"
age=61
pred=pm.predictPriceFromGenderAndAge(gender,age)
print("Gender=%s and Age=%s=>Price=%s"%(gender,age,pred))

gender="Female"
age=61
pred=pm.predictPriceFromGenderAndAge(gender,age)
print("Gender=%s and Age=%s=>Price=%s"%(gender,age,pred))

print("------------------"*10)
pm=PurchaseLinearRegression()
pm.connector=connector
pm.processTrain(["gender","age","payment_method"],"price",0.2,0)

eresult=pm.evaluate()
print(eresult)

gender="Male"
age=61
payment="Credit Card"
pred=pm.predictPriceFromGenderAndAgeAndPayment(gender,age,payment)
print("Gender=%s and Age=%s and payment=%s=>Price=%s"%(gender,age,payment,pred))

gender="Male"
age=61
payment="Debit Card"
pred=pm.predictPriceFromGenderAndAgeAndPayment(gender,age,payment)
print("Gender=%s and Age=%s and payment=%s=>Price=%s"%(gender,age,payment,pred))

gender="Male"
age=61
payment="Cash"
pred=pm.predictPriceFromGenderAndAgeAndPayment(gender,age,payment)
print("Gender=%s and Age=%s and payment=%s=>Price=%s"%(gender,age,payment,pred))

ret=pm.saveModel("../Assets/LR_mymodel.zip")
print("ret save model=%s"%ret)

Bước 3.4: Tạo “TestLoadModel.py” để nạp mô hình từ ổ cứng , cũng như prediction. Ta có thể chạy tập tin này độc lập để test:

from Models.PurchaseLinearRegression import PurchaseLinearRegression

pm=PurchaseLinearRegression()
pm.loadModel("../Assets/TrainedModel_GenderAgePayment.zip")

gender="Female"
age=61
payment="Cash"
pred=pm.predictPriceFromGenderAndAgeAndPayment(gender,age,payment)
print("Gender=%s and Age=%s and payment=%s=>Price=%s"%(gender,age,payment,pred))
#Gender=Female and Age=61 and payment=Cash=>Price=[692.98688316]

Bước 4: Tạo lớp tập tin “FileUtils.py” trong thư mục Utils để lưu mô hình máy học và nạp mô hình máy học:

import pickle
import traceback
class FileUtil:
    @staticmethod
    def saveModel(model,filename):
        try:
            pickle.dump(model,open(filename,'wb'))
            return True
        except:
            traceback.print_exc()
            return False
    @staticmethod
    def loadModel(filename):
        try:
            model=pickle.load(open(filename,'rb'))
            return model
        except:
            traceback.print_exc()
            return None

Bước 5: Tạo các lớp thư viện trong thư mục “UI”

Bước 5.1: Tạo “ChartType.py” để xử lý các loại Chart thống kê trong dự án

from enum import Enum

class ChartType(Enum):
    PieChart=1
    LinePlotChart=2
    BarChart=3
    BarPlot=4
    MultiBarChart=5
    ScatterPlot=6

Bước 5.2: Tạo “ChartHandle.py” để xử lý vẽ biểu đồ thống kê

import seaborn as sns
class ChartHandle:
    def getExplode(self,df,columnLabel):
        explode = [0.1]
        for i in range(len(df[columnLabel]) - 1):
            explode.append(0)
        return explode
    def visualizePieChart(self,figure,canvas,df,columnLabel,columnStatistic,title,legend=True):
        explode=self.getExplode(df,columnLabel)
        figure.clear()
        ax = figure.add_subplot(111)
        ax.pie(df[columnStatistic], labels=df[columnLabel], autopct='%1.2f%%', explode=explode)
        if legend:
            ax.legend(df[columnLabel],loc ='lower right')
        ax.set_title(title)
        canvas.draw()
    def visualizeLinePlotChart(self,figure,canvas,df,columnX,columnY,tile,hue=None,xticks=False):
        figure.clear()
        ax = figure.add_subplot(111)
        ax.ticklabel_format(useOffset=False,style="plain")
        ax.grid()
        sns.lineplot(data=df,x=columnX, y=columnY, marker='o', color='orange',hue=hue)
        ax.set_xlabel(columnX)
        ax.set_ylabel(columnY)
        ax.set_title(tile)
        ax.legend(loc ='lower right')
        #ax.set_xticks(range(1, 13), ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'])
        if xticks==True:
            ax.set_xticks(range(1, 13), ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'])
        canvas.draw()
    def visualizeBarChart(self,figure,canvas,df,columnX,columnY,title):
        figure.clear()
        ax = figure.add_subplot(111)
        ax.ticklabel_format(useOffset=False, style="plain")
        ax.grid()
        ax.bar(df[columnX],df[columnY])
        ax.set_title(title)
        ax.set_xlabel(columnX)
        ax.set_ylabel(columnY)
        canvas.draw()
    def visualizeBarPlot(self,figure,canvas,df,columnX,columnY,hueColumn,title,alpha=0.8,width=0.6):
        figure.clear()
        ax = figure.add_subplot(111)
        ax.ticklabel_format(useOffset=False, style="plain")
        ax.grid()
        ax=sns.barplot(data=df,x=columnX,y=columnY,hue=hueColumn,alpha=alpha,width=width)
        ax.set_title(title)
        ax.set_xlabel(columnX)
        ax.set_ylabel(columnY)
        canvas.draw()
    def visualizeMultiBarChart(self,figure,canvas,df,columnX,columnY,hueColumn,title):
        figure.clear()
        ax = figure.add_subplot(111)
        ax.ticklabel_format(useOffset=False, style="plain")
        ax.grid()
        sns.countplot(x=columnX, hue=hueColumn, data=df)
        ax.set_title(title)
        ax.set_xlabel(columnX)
        ax.set_ylabel(columnY)
        canvas.draw()
    def visualizeScatterPlot(self,figure,canvas,df,columnX,columnY,title):
        figure.clear()
        ax = figure.add_subplot(111)
        ax.ticklabel_format(useOffset=False, style="plain")
        ax.grid()
        sns.scatterplot(data=df,x= columnX,y=columnY)
        ax.set_title(title)
        ax.set_xlabel(columnX)
        ax.set_ylabel(columnY)
        canvas.draw()

Bước 5.3: Tạo giao diện kết nối cơ sở dữ liệu “DatabaseConnect.ui

Thiết kế giao diện và đặt tên các Widget như trên.

Bước 5.4: Generate Python code cho “DatabaseConnect.ui“, ta có dữ liệu mã lệnh “DatabaseConnect.py“:

# Form implementation generated from reading ui file 'E:\Elearning\MLBAProject\UI\DatabaseConnect.ui'
#
# Created by: PyQt6 UI code generator 6.6.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt6 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(359, 333)
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_logo.jpg"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        MainWindow.setWindowIcon(icon)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.groupBox = QtWidgets.QGroupBox(parent=self.centralwidget)
        self.groupBox.setGeometry(QtCore.QRect(20, 10, 321, 271))
        self.groupBox.setObjectName("groupBox")
        self.label = QtWidgets.QLabel(parent=self.groupBox)
        self.label.setGeometry(QtCore.QRect(20, 30, 51, 16))
        self.label.setObjectName("label")
        self.lineEditServer = QtWidgets.QLineEdit(parent=self.groupBox)
        self.lineEditServer.setGeometry(QtCore.QRect(80, 30, 201, 22))
        self.lineEditServer.setObjectName("lineEditServer")
        self.lineEditPort = QtWidgets.QLineEdit(parent=self.groupBox)
        self.lineEditPort.setGeometry(QtCore.QRect(80, 60, 201, 22))
        self.lineEditPort.setObjectName("lineEditPort")
        self.label_2 = QtWidgets.QLabel(parent=self.groupBox)
        self.label_2.setGeometry(QtCore.QRect(40, 60, 31, 16))
        self.label_2.setObjectName("label_2")
        self.lineEditDatabase = QtWidgets.QLineEdit(parent=self.groupBox)
        self.lineEditDatabase.setGeometry(QtCore.QRect(80, 100, 201, 22))
        self.lineEditDatabase.setObjectName("lineEditDatabase")
        self.label_3 = QtWidgets.QLabel(parent=self.groupBox)
        self.label_3.setGeometry(QtCore.QRect(10, 100, 61, 16))
        self.label_3.setObjectName("label_3")
        self.lineEditUser = QtWidgets.QLineEdit(parent=self.groupBox)
        self.lineEditUser.setGeometry(QtCore.QRect(80, 140, 201, 22))
        self.lineEditUser.setObjectName("lineEditUser")
        self.label_4 = QtWidgets.QLabel(parent=self.groupBox)
        self.label_4.setGeometry(QtCore.QRect(30, 140, 41, 16))
        self.label_4.setObjectName("label_4")
        self.lineEditPassword = QtWidgets.QLineEdit(parent=self.groupBox)
        self.lineEditPassword.setGeometry(QtCore.QRect(80, 180, 201, 22))
        self.lineEditPassword.setEchoMode(QtWidgets.QLineEdit.EchoMode.Password)
        self.lineEditPassword.setObjectName("lineEditPassword")
        self.label_5 = QtWidgets.QLabel(parent=self.groupBox)
        self.label_5.setGeometry(QtCore.QRect(10, 180, 61, 20))
        self.label_5.setObjectName("label_5")
        self.pushButtonConnect = QtWidgets.QPushButton(parent=self.groupBox)
        self.pushButtonConnect.setGeometry(QtCore.QRect(40, 220, 111, 31))
        icon1 = QtGui.QIcon()
        icon1.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_connect.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonConnect.setIcon(icon1)
        self.pushButtonConnect.setObjectName("pushButtonConnect")
        self.pushButtonClose = QtWidgets.QPushButton(parent=self.groupBox)
        self.pushButtonClose.setGeometry(QtCore.QRect(190, 220, 101, 31))
        icon2 = QtGui.QIcon()
        icon2.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_shutdown.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonClose.setIcon(icon2)
        self.pushButtonClose.setObjectName("pushButtonClose")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 359, 26))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        self.pushButtonClose.clicked.connect(MainWindow.close) # type: ignore
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Trần Duy Thanh"))
        self.groupBox.setTitle(_translate("MainWindow", "Connection Setting:"))
        self.label.setText(_translate("MainWindow", "Server:"))
        self.lineEditServer.setText(_translate("MainWindow", "localhost"))
        self.lineEditPort.setText(_translate("MainWindow", "3306"))
        self.label_2.setText(_translate("MainWindow", "Port:"))
        self.lineEditDatabase.setText(_translate("MainWindow", "lecturer_retails"))
        self.label_3.setText(_translate("MainWindow", "Database:"))
        self.lineEditUser.setText(_translate("MainWindow", "root"))
        self.label_4.setText(_translate("MainWindow", "User:"))
        self.lineEditPassword.setText(_translate("MainWindow", "@Obama123"))
        self.label_5.setText(_translate("MainWindow", "Password:"))
        self.pushButtonConnect.setText(_translate("MainWindow", "Connect"))
        self.pushButtonClose.setText(_translate("MainWindow", "Close"))
        self.pushButtonClose.setShortcut(_translate("MainWindow", "Esc"))

Bước 5.5: Tạo Python code kế “DatabaseConnectEx.py” thừa Ui_MainWindow của mã lệnh Generate Python code giao diện “DatabaseConnect.py” để xử lý sự kiện tương tác người dùng:

import traceback

from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QMessageBox

from Connectors.Connector import Connector
from UI.DatabaseConnect import Ui_MainWindow


class DatabaseConnectEx(Ui_MainWindow):
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.pushButtonConnect.clicked.connect(self.connectDatabase)
    def connectDatabase(self):
        try:
            self.connector=Connector()
            self.connector.server=self.lineEditServer.text()
            self.connector.port=(int)(self.lineEditPort.text())
            self.connector.database=self.lineEditDatabase.text()
            self.connector.username=self.lineEditUser.text()
            self.connector.password=self.lineEditPassword.text()
            self.connector.connect()
            self.msg=QMessageBox()
            self.msg.setText("Connect database successful!")
            self.msg.setWindowTitle("Info")
            #self.msg.show()
            self.MainWindow.close()
            if self.parent!=None:
                self.parent.checkEnableWidget(True)
        except:
            traceback.print_exc()
            self.msg = QMessageBox()
            self.msg.setText("Connect database failed")
            self.msg.setWindowTitle("Info")
            self.msg.show()
    def show(self):
        self.MainWindow.setWindowModality(Qt.WindowModality.ApplicationModal)
        self.MainWindow.show()

Bước 5.6: Tạo giao diện chính của dự án để thực hiện chức năng thống kê và máy học “MainWindow.ui“:

Thiết kế giao diện và đặt tên các Widget như trên.

Bước 5.7: Generate Python code cho “MainWindow.ui“, ta có dữ liệu mã lệnh “MainWindow.py“:

# Form implementation generated from reading ui file 'E:\Elearning\MLBAProject\UI\MainWindow.ui'
#
# Created by: PyQt6 UI code generator 6.6.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt6 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1279, 852)
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_logo.jpg"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        MainWindow.setWindowIcon(icon)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.tabWidget = QtWidgets.QTabWidget(parent=self.centralwidget)
        self.tabWidget.setObjectName("tabWidget")
        self.tab = QtWidgets.QWidget()
        self.tab.setObjectName("tab")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.tab)
        self.verticalLayout.setObjectName("verticalLayout")
        self.gridLayout = QtWidgets.QGridLayout()
        self.gridLayout.setObjectName("gridLayout")
        self.groupBox_3 = QtWidgets.QGroupBox(parent=self.tab)
        self.groupBox_3.setMinimumSize(QtCore.QSize(0, 200))
        self.groupBox_3.setMaximumSize(QtCore.QSize(16777215, 200))
        self.groupBox_3.setBaseSize(QtCore.QSize(0, 200))
        self.groupBox_3.setObjectName("groupBox_3")
        self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.groupBox_3)
        self.verticalLayout_4.setObjectName("verticalLayout_4")
        self.tableWidgetStatistic = QtWidgets.QTableWidget(parent=self.groupBox_3)
        self.tableWidgetStatistic.setMinimumSize(QtCore.QSize(0, 180))
        self.tableWidgetStatistic.setMaximumSize(QtCore.QSize(16777215, 180))
        self.tableWidgetStatistic.setBaseSize(QtCore.QSize(0, 180))
        self.tableWidgetStatistic.setObjectName("tableWidgetStatistic")
        self.tableWidgetStatistic.setColumnCount(2)
        self.tableWidgetStatistic.setRowCount(0)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidgetStatistic.setHorizontalHeaderItem(0, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidgetStatistic.setHorizontalHeaderItem(1, item)
        self.verticalLayout_4.addWidget(self.tableWidgetStatistic)
        self.gridLayout.addWidget(self.groupBox_3, 0, 1, 1, 1)
        self.groupBox_4 = QtWidgets.QGroupBox(parent=self.tab)
        self.groupBox_4.setObjectName("groupBox_4")
        self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.groupBox_4)
        self.verticalLayout_3.setObjectName("verticalLayout_3")
        self.verticalLayoutPlot = QtWidgets.QVBoxLayout()
        self.verticalLayoutPlot.setObjectName("verticalLayoutPlot")
        self.verticalLayout_3.addLayout(self.verticalLayoutPlot)
        self.gridLayout.addWidget(self.groupBox_4, 1, 1, 1, 1)
        self.groupBox = QtWidgets.QGroupBox(parent=self.tab)
        self.groupBox.setMinimumSize(QtCore.QSize(400, 0))
        self.groupBox.setMaximumSize(QtCore.QSize(400, 16777215))
        self.groupBox.setBaseSize(QtCore.QSize(400, 0))
        self.groupBox.setObjectName("groupBox")
        self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.groupBox)
        self.verticalLayout_6.setObjectName("verticalLayout_6")
        self.verticalLayoutFunctions = QtWidgets.QVBoxLayout()
        self.verticalLayoutFunctions.setObjectName("verticalLayoutFunctions")
        self.pushButtonPurchaseRatesByGender = QtWidgets.QPushButton(parent=self.groupBox)
        icon1 = QtGui.QIcon()
        icon1.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_statistic_gender.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonPurchaseRatesByGender.setIcon(icon1)
        self.pushButtonPurchaseRatesByGender.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonPurchaseRatesByGender.setObjectName("pushButtonPurchaseRatesByGender")
        self.verticalLayoutFunctions.addWidget(self.pushButtonPurchaseRatesByGender)
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.label_2 = QtWidgets.QLabel(parent=self.groupBox)
        self.label_2.setMaximumSize(QtCore.QSize(30, 16777215))
        self.label_2.setObjectName("label_2")
        self.horizontalLayout.addWidget(self.label_2)
        self.lineEditFromAge = QtWidgets.QLineEdit(parent=self.groupBox)
        self.lineEditFromAge.setMaximumSize(QtCore.QSize(30, 16777215))
        self.lineEditFromAge.setObjectName("lineEditFromAge")
        self.horizontalLayout.addWidget(self.lineEditFromAge)
        self.label = QtWidgets.QLabel(parent=self.groupBox)
        self.label.setMaximumSize(QtCore.QSize(30, 16777215))
        self.label.setObjectName("label")
        self.horizontalLayout.addWidget(self.label)
        self.lineEditToAge = QtWidgets.QLineEdit(parent=self.groupBox)
        self.lineEditToAge.setMaximumSize(QtCore.QSize(30, 16777215))
        self.lineEditToAge.setObjectName("lineEditToAge")
        self.horizontalLayout.addWidget(self.lineEditToAge)
        self.pushButtonPurchaseRatesByAgeGroup = QtWidgets.QPushButton(parent=self.groupBox)
        icon2 = QtGui.QIcon()
        icon2.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_statistic_age.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonPurchaseRatesByAgeGroup.setIcon(icon2)
        self.pushButtonPurchaseRatesByAgeGroup.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonPurchaseRatesByAgeGroup.setObjectName("pushButtonPurchaseRatesByAgeGroup")
        self.horizontalLayout.addWidget(self.pushButtonPurchaseRatesByAgeGroup)
        self.verticalLayoutFunctions.addLayout(self.horizontalLayout)
        self.pushButtonPurchaseCountingByCategory = QtWidgets.QPushButton(parent=self.groupBox)
        icon3 = QtGui.QIcon()
        icon3.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_statistic_category.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonPurchaseCountingByCategory.setIcon(icon3)
        self.pushButtonPurchaseCountingByCategory.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonPurchaseCountingByCategory.setObjectName("pushButtonPurchaseCountingByCategory")
        self.verticalLayoutFunctions.addWidget(self.pushButtonPurchaseCountingByCategory)
        self.pushButtonPurchaseValueByCategory = QtWidgets.QPushButton(parent=self.groupBox)
        icon4 = QtGui.QIcon()
        icon4.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_statistic_cate_value.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonPurchaseValueByCategory.setIcon(icon4)
        self.pushButtonPurchaseValueByCategory.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonPurchaseValueByCategory.setObjectName("pushButtonPurchaseValueByCategory")
        self.verticalLayoutFunctions.addWidget(self.pushButtonPurchaseValueByCategory)
        self.pushButtonPurchaseByCategoryAndGender = QtWidgets.QPushButton(parent=self.groupBox)
        icon5 = QtGui.QIcon()
        icon5.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_statistic_cate_gender.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonPurchaseByCategoryAndGender.setIcon(icon5)
        self.pushButtonPurchaseByCategoryAndGender.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonPurchaseByCategoryAndGender.setObjectName("pushButtonPurchaseByCategoryAndGender")
        self.verticalLayoutFunctions.addWidget(self.pushButtonPurchaseByCategoryAndGender)
        self.pushButtonPaymentMethod = QtWidgets.QPushButton(parent=self.groupBox)
        icon6 = QtGui.QIcon()
        icon6.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_statistic_payment.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonPaymentMethod.setIcon(icon6)
        self.pushButtonPaymentMethod.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonPaymentMethod.setObjectName("pushButtonPaymentMethod")
        self.verticalLayoutFunctions.addWidget(self.pushButtonPaymentMethod)
        self.pushButtonPurchaseRatesByShoppingMall = QtWidgets.QPushButton(parent=self.groupBox)
        icon7 = QtGui.QIcon()
        icon7.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_statistic_shopping_mall.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonPurchaseRatesByShoppingMall.setIcon(icon7)
        self.pushButtonPurchaseRatesByShoppingMall.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonPurchaseRatesByShoppingMall.setObjectName("pushButtonPurchaseRatesByShoppingMall")
        self.verticalLayoutFunctions.addWidget(self.pushButtonPurchaseRatesByShoppingMall)
        self.pushButtonProductSpendingByGender = QtWidgets.QPushButton(parent=self.groupBox)
        icon8 = QtGui.QIcon()
        icon8.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_statistic_spending_gender.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonProductSpendingByGender.setIcon(icon8)
        self.pushButtonProductSpendingByGender.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonProductSpendingByGender.setObjectName("pushButtonProductSpendingByGender")
        self.verticalLayoutFunctions.addWidget(self.pushButtonProductSpendingByGender)
        self.pushButtonPurchaseFrequenceByAge = QtWidgets.QPushButton(parent=self.groupBox)
        icon9 = QtGui.QIcon()
        icon9.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_statistic_frequence_age.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonPurchaseFrequenceByAge.setIcon(icon9)
        self.pushButtonPurchaseFrequenceByAge.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonPurchaseFrequenceByAge.setObjectName("pushButtonPurchaseFrequenceByAge")
        self.verticalLayoutFunctions.addWidget(self.pushButtonPurchaseFrequenceByAge)
        self.pushButtonSalesFluctuationsByMonth = QtWidgets.QPushButton(parent=self.groupBox)
        icon10 = QtGui.QIcon()
        icon10.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_statistic_sales_monthly.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonSalesFluctuationsByMonth.setIcon(icon10)
        self.pushButtonSalesFluctuationsByMonth.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonSalesFluctuationsByMonth.setObjectName("pushButtonSalesFluctuationsByMonth")
        self.verticalLayoutFunctions.addWidget(self.pushButtonSalesFluctuationsByMonth)
        self.pushButtonSalesFlucuationsByYearAndMonth = QtWidgets.QPushButton(parent=self.groupBox)
        icon11 = QtGui.QIcon()
        icon11.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_statistic_sales_yearly_monthly.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonSalesFlucuationsByYearAndMonth.setIcon(icon11)
        self.pushButtonSalesFlucuationsByYearAndMonth.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonSalesFlucuationsByYearAndMonth.setObjectName("pushButtonSalesFlucuationsByYearAndMonth")
        self.verticalLayoutFunctions.addWidget(self.pushButtonSalesFlucuationsByYearAndMonth)
        self.verticalLayout_6.addLayout(self.verticalLayoutFunctions)
        self.gridLayout.addWidget(self.groupBox, 0, 0, 2, 1)
        self.verticalLayout.addLayout(self.gridLayout)
        icon12 = QtGui.QIcon()
        icon12.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_statistic_tab.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.tabWidget.addTab(self.tab, icon12, "")
        self.tab_2 = QtWidgets.QWidget()
        self.tab_2.setObjectName("tab_2")
        self.groupBox_2 = QtWidgets.QGroupBox(parent=self.tab_2)
        self.groupBox_2.setGeometry(QtCore.QRect(20, 10, 521, 100))
        self.groupBox_2.setMinimumSize(QtCore.QSize(150, 100))
        self.groupBox_2.setObjectName("groupBox_2")
        self.radioButtonGenderAge = QtWidgets.QRadioButton(parent=self.groupBox_2)
        self.radioButtonGenderAge.setGeometry(QtCore.QRect(20, 30, 201, 20))
        self.radioButtonGenderAge.setChecked(True)
        self.radioButtonGenderAge.setObjectName("radioButtonGenderAge")
        self.radioButtonGenderAgePayment = QtWidgets.QRadioButton(parent=self.groupBox_2)
        self.radioButtonGenderAgePayment.setGeometry(QtCore.QRect(20, 60, 291, 20))
        self.radioButtonGenderAgePayment.setObjectName("radioButtonGenderAgePayment")
        self.groupBox_6 = QtWidgets.QGroupBox(parent=self.tab_2)
        self.groupBox_6.setGeometry(QtCore.QRect(20, 210, 521, 151))
        self.groupBox_6.setObjectName("groupBox_6")
        self.label_7 = QtWidgets.QLabel(parent=self.groupBox_6)
        self.label_7.setGeometry(QtCore.QRect(20, 20, 61, 16))
        self.label_7.setObjectName("label_7")
        self.label_8 = QtWidgets.QLabel(parent=self.groupBox_6)
        self.label_8.setGeometry(QtCore.QRect(20, 50, 61, 16))
        self.label_8.setObjectName("label_8")
        self.label_9 = QtWidgets.QLabel(parent=self.groupBox_6)
        self.label_9.setGeometry(QtCore.QRect(20, 80, 61, 16))
        self.label_9.setObjectName("label_9")
        self.label_10 = QtWidgets.QLabel(parent=self.groupBox_6)
        self.label_10.setGeometry(QtCore.QRect(20, 110, 81, 16))
        self.label_10.setObjectName("label_10")
        self.lineEditMAE = QtWidgets.QLineEdit(parent=self.groupBox_6)
        self.lineEditMAE.setGeometry(QtCore.QRect(110, 20, 271, 22))
        self.lineEditMAE.setObjectName("lineEditMAE")
        self.lineEditMSE = QtWidgets.QLineEdit(parent=self.groupBox_6)
        self.lineEditMSE.setGeometry(QtCore.QRect(110, 50, 271, 22))
        self.lineEditMSE.setObjectName("lineEditMSE")
        self.lineEditRMSE = QtWidgets.QLineEdit(parent=self.groupBox_6)
        self.lineEditRMSE.setGeometry(QtCore.QRect(110, 80, 271, 22))
        self.lineEditRMSE.setObjectName("lineEditRMSE")
        self.lineEditR2SCore = QtWidgets.QLineEdit(parent=self.groupBox_6)
        self.lineEditR2SCore.setGeometry(QtCore.QRect(110, 110, 271, 22))
        self.lineEditR2SCore.setObjectName("lineEditR2SCore")
        self.pushButtonEvaluate = QtWidgets.QPushButton(parent=self.groupBox_6)
        self.pushButtonEvaluate.setGeometry(QtCore.QRect(390, 60, 111, 41))
        icon13 = QtGui.QIcon()
        icon13.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_evaluate.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonEvaluate.setIcon(icon13)
        self.pushButtonEvaluate.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonEvaluate.setObjectName("pushButtonEvaluate")
        self.groupBox_7 = QtWidgets.QGroupBox(parent=self.tab_2)
        self.groupBox_7.setGeometry(QtCore.QRect(20, 120, 521, 81))
        self.groupBox_7.setObjectName("groupBox_7")
        self.lineEditRandomState = QtWidgets.QLineEdit(parent=self.groupBox_7)
        self.lineEditRandomState.setGeometry(QtCore.QRect(120, 50, 261, 20))
        self.lineEditRandomState.setObjectName("lineEditRandomState")
        self.label_5 = QtWidgets.QLabel(parent=self.groupBox_7)
        self.label_5.setGeometry(QtCore.QRect(20, 50, 91, 16))
        self.label_5.setObjectName("label_5")
        self.label_4 = QtWidgets.QLabel(parent=self.groupBox_7)
        self.label_4.setGeometry(QtCore.QRect(350, 20, 21, 16))
        self.label_4.setObjectName("label_4")
        self.label_3 = QtWidgets.QLabel(parent=self.groupBox_7)
        self.label_3.setGeometry(QtCore.QRect(20, 20, 61, 16))
        self.label_3.setObjectName("label_3")
        self.lineEditTestSize = QtWidgets.QLineEdit(parent=self.groupBox_7)
        self.lineEditTestSize.setGeometry(QtCore.QRect(120, 20, 221, 20))
        self.lineEditTestSize.setObjectName("lineEditTestSize")
        self.pushButtonTrainModel = QtWidgets.QPushButton(parent=self.groupBox_7)
        self.pushButtonTrainModel.setGeometry(QtCore.QRect(390, 30, 111, 41))
        icon14 = QtGui.QIcon()
        icon14.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_trainmodel.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonTrainModel.setIcon(icon14)
        self.pushButtonTrainModel.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonTrainModel.setObjectName("pushButtonTrainModel")
        self.groupBox_8 = QtWidgets.QGroupBox(parent=self.tab_2)
        self.groupBox_8.setGeometry(QtCore.QRect(20, 370, 521, 71))
        self.groupBox_8.setObjectName("groupBox_8")
        self.label_11 = QtWidgets.QLabel(parent=self.groupBox_8)
        self.label_11.setGeometry(QtCore.QRect(10, 30, 41, 16))
        self.label_11.setObjectName("label_11")
        self.lineEditPath = QtWidgets.QLineEdit(parent=self.groupBox_8)
        self.lineEditPath.setGeometry(QtCore.QRect(50, 30, 281, 22))
        self.lineEditPath.setObjectName("lineEditPath")
        self.pushButtonSavePath = QtWidgets.QPushButton(parent=self.groupBox_8)
        self.pushButtonSavePath.setGeometry(QtCore.QRect(340, 30, 41, 28))
        self.pushButtonSavePath.setObjectName("pushButtonSavePath")
        self.pushButtonSaveModel = QtWidgets.QPushButton(parent=self.groupBox_8)
        self.pushButtonSaveModel.setGeometry(QtCore.QRect(390, 20, 111, 41))
        icon15 = QtGui.QIcon()
        icon15.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_savemodel.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonSaveModel.setIcon(icon15)
        self.pushButtonSaveModel.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonSaveModel.setObjectName("pushButtonSaveModel")
        self.groupBox_9 = QtWidgets.QGroupBox(parent=self.tab_2)
        self.groupBox_9.setGeometry(QtCore.QRect(20, 450, 521, 71))
        self.groupBox_9.setObjectName("groupBox_9")
        self.pushButtonLoadModel = QtWidgets.QPushButton(parent=self.groupBox_9)
        self.pushButtonLoadModel.setGeometry(QtCore.QRect(10, 20, 111, 41))
        icon16 = QtGui.QIcon()
        icon16.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_loadmodel.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonLoadModel.setIcon(icon16)
        self.pushButtonLoadModel.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonLoadModel.setObjectName("pushButtonLoadModel")
        self.lineEditLocationLoadTrainedModel = QtWidgets.QLineEdit(parent=self.groupBox_9)
        self.lineEditLocationLoadTrainedModel.setGeometry(QtCore.QRect(130, 30, 371, 22))
        self.lineEditLocationLoadTrainedModel.setObjectName("lineEditLocationLoadTrainedModel")
        self.groupBox_10 = QtWidgets.QGroupBox(parent=self.tab_2)
        self.groupBox_10.setGeometry(QtCore.QRect(20, 530, 521, 201))
        self.groupBox_10.setObjectName("groupBox_10")
        self.label_12 = QtWidgets.QLabel(parent=self.groupBox_10)
        self.label_12.setGeometry(QtCore.QRect(20, 20, 101, 16))
        self.label_12.setObjectName("label_12")
        self.lineEditGender = QtWidgets.QLineEdit(parent=self.groupBox_10)
        self.lineEditGender.setGeometry(QtCore.QRect(130, 20, 241, 22))
        self.lineEditGender.setObjectName("lineEditGender")
        self.lineEditAge = QtWidgets.QLineEdit(parent=self.groupBox_10)
        self.lineEditAge.setGeometry(QtCore.QRect(130, 50, 241, 22))
        self.lineEditAge.setObjectName("lineEditAge")
        self.label_13 = QtWidgets.QLabel(parent=self.groupBox_10)
        self.label_13.setGeometry(QtCore.QRect(20, 50, 101, 16))
        self.label_13.setObjectName("label_13")
        self.lineEditPaymentMethod = QtWidgets.QLineEdit(parent=self.groupBox_10)
        self.lineEditPaymentMethod.setGeometry(QtCore.QRect(130, 80, 241, 22))
        self.lineEditPaymentMethod.setObjectName("lineEditPaymentMethod")
        self.label_14 = QtWidgets.QLabel(parent=self.groupBox_10)
        self.label_14.setGeometry(QtCore.QRect(20, 80, 101, 16))
        self.label_14.setObjectName("label_14")
        self.pushButtonPredict = QtWidgets.QPushButton(parent=self.groupBox_10)
        self.pushButtonPredict.setGeometry(QtCore.QRect(130, 110, 111, 41))
        icon17 = QtGui.QIcon()
        icon17.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_prediction.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonPredict.setIcon(icon17)
        self.pushButtonPredict.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonPredict.setObjectName("pushButtonPredict")
        self.lineEditPredictedPrice = QtWidgets.QLineEdit(parent=self.groupBox_10)
        self.lineEditPredictedPrice.setGeometry(QtCore.QRect(130, 160, 241, 22))
        self.lineEditPredictedPrice.setObjectName("lineEditPredictedPrice")
        self.label_15 = QtWidgets.QLabel(parent=self.groupBox_10)
        self.label_15.setGeometry(QtCore.QRect(20, 160, 101, 16))
        self.label_15.setObjectName("label_15")
        icon18 = QtGui.QIcon()
        icon18.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_machinelearning_tab.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.tabWidget.addTab(self.tab_2, icon18, "")
        self.verticalLayout_2.addWidget(self.tabWidget)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 1279, 26))
        self.menubar.setObjectName("menubar")
        self.menuSystem = QtWidgets.QMenu(parent=self.menubar)
        self.menuSystem.setAutoFillBackground(False)
        self.menuSystem.setTearOffEnabled(False)
        self.menuSystem.setSeparatorsCollapsible(True)
        self.menuSystem.setToolTipsVisible(True)
        self.menuSystem.setObjectName("menuSystem")
        self.menuHelp = QtWidgets.QMenu(parent=self.menubar)
        self.menuHelp.setObjectName("menuHelp")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)
        self.actionConnection = QtGui.QAction(parent=MainWindow)
        icon19 = QtGui.QIcon()
        icon19.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_connection.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.actionConnection.setIcon(icon19)
        self.actionConnection.setObjectName("actionConnection")
        self.actionSave_trained_Machine_Learning_Model = QtGui.QAction(parent=MainWindow)
        icon20 = QtGui.QIcon()
        icon20.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_save.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.actionSave_trained_Machine_Learning_Model.setIcon(icon20)
        self.actionSave_trained_Machine_Learning_Model.setObjectName("actionSave_trained_Machine_Learning_Model")
        self.actionLoad_trained_Machine_Learning_Model = QtGui.QAction(parent=MainWindow)
        icon21 = QtGui.QIcon()
        icon21.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_load.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.actionLoad_trained_Machine_Learning_Model.setIcon(icon21)
        self.actionLoad_trained_Machine_Learning_Model.setObjectName("actionLoad_trained_Machine_Learning_Model")
        self.actionExit = QtGui.QAction(parent=MainWindow)
        icon22 = QtGui.QIcon()
        icon22.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_shutdown.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.actionExit.setIcon(icon22)
        self.actionExit.setObjectName("actionExit")
        self.actionInstructions = QtGui.QAction(parent=MainWindow)
        icon23 = QtGui.QIcon()
        icon23.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_instruction.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.actionInstructions.setIcon(icon23)
        self.actionInstructions.setObjectName("actionInstructions")
        self.actionAbout = QtGui.QAction(parent=MainWindow)
        icon24 = QtGui.QIcon()
        icon24.addPixmap(QtGui.QPixmap("E:\\Elearning\\MLBAProject\\UI\\../Images/ic_about.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.actionAbout.setIcon(icon24)
        self.actionAbout.setObjectName("actionAbout")
        self.menuSystem.addAction(self.actionConnection)
        self.menuSystem.addSeparator()
        self.menuSystem.addAction(self.actionSave_trained_Machine_Learning_Model)
        self.menuSystem.addSeparator()
        self.menuSystem.addAction(self.actionLoad_trained_Machine_Learning_Model)
        self.menuSystem.addSeparator()
        self.menuSystem.addAction(self.actionExit)
        self.menuHelp.addAction(self.actionInstructions)
        self.menuHelp.addSeparator()
        self.menuHelp.addAction(self.actionAbout)
        self.menubar.addAction(self.menuSystem.menuAction())
        self.menubar.addAction(self.menuHelp.menuAction())

        self.retranslateUi(MainWindow)
        self.tabWidget.setCurrentIndex(0)
        self.actionExit.triggered.connect(MainWindow.close) # type: ignore
        QtCore.QMetaObject.connectSlotsByName(MainWindow)
        MainWindow.setTabOrder(self.tabWidget, self.pushButtonPurchaseRatesByGender)
        MainWindow.setTabOrder(self.pushButtonPurchaseRatesByGender, self.tableWidgetStatistic)
        MainWindow.setTabOrder(self.tableWidgetStatistic, self.lineEditFromAge)
        MainWindow.setTabOrder(self.lineEditFromAge, self.lineEditToAge)
        MainWindow.setTabOrder(self.lineEditToAge, self.pushButtonPurchaseRatesByAgeGroup)
        MainWindow.setTabOrder(self.pushButtonPurchaseRatesByAgeGroup, self.pushButtonPurchaseCountingByCategory)
        MainWindow.setTabOrder(self.pushButtonPurchaseCountingByCategory, self.pushButtonPurchaseValueByCategory)
        MainWindow.setTabOrder(self.pushButtonPurchaseValueByCategory, self.pushButtonPurchaseByCategoryAndGender)
        MainWindow.setTabOrder(self.pushButtonPurchaseByCategoryAndGender, self.pushButtonPaymentMethod)
        MainWindow.setTabOrder(self.pushButtonPaymentMethod, self.pushButtonPurchaseRatesByShoppingMall)
        MainWindow.setTabOrder(self.pushButtonPurchaseRatesByShoppingMall, self.pushButtonProductSpendingByGender)
        MainWindow.setTabOrder(self.pushButtonProductSpendingByGender, self.pushButtonPurchaseFrequenceByAge)
        MainWindow.setTabOrder(self.pushButtonPurchaseFrequenceByAge, self.pushButtonSalesFluctuationsByMonth)
        MainWindow.setTabOrder(self.pushButtonSalesFluctuationsByMonth, self.pushButtonSalesFlucuationsByYearAndMonth)
        MainWindow.setTabOrder(self.pushButtonSalesFlucuationsByYearAndMonth, self.lineEditTestSize)
        MainWindow.setTabOrder(self.lineEditTestSize, self.lineEditRandomState)
        MainWindow.setTabOrder(self.lineEditRandomState, self.pushButtonTrainModel)
        MainWindow.setTabOrder(self.pushButtonTrainModel, self.lineEditMAE)
        MainWindow.setTabOrder(self.lineEditMAE, self.lineEditMSE)
        MainWindow.setTabOrder(self.lineEditMSE, self.lineEditRMSE)
        MainWindow.setTabOrder(self.lineEditRMSE, self.lineEditR2SCore)
        MainWindow.setTabOrder(self.lineEditR2SCore, self.pushButtonEvaluate)
        MainWindow.setTabOrder(self.pushButtonEvaluate, self.lineEditPath)
        MainWindow.setTabOrder(self.lineEditPath, self.pushButtonSavePath)
        MainWindow.setTabOrder(self.pushButtonSavePath, self.pushButtonSaveModel)
        MainWindow.setTabOrder(self.pushButtonSaveModel, self.pushButtonLoadModel)
        MainWindow.setTabOrder(self.pushButtonLoadModel, self.lineEditLocationLoadTrainedModel)
        MainWindow.setTabOrder(self.lineEditLocationLoadTrainedModel, self.lineEditGender)
        MainWindow.setTabOrder(self.lineEditGender, self.lineEditAge)
        MainWindow.setTabOrder(self.lineEditAge, self.lineEditPaymentMethod)
        MainWindow.setTabOrder(self.lineEditPaymentMethod, self.pushButtonPredict)
        MainWindow.setTabOrder(self.pushButtonPredict, self.lineEditPredictedPrice)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Trần Duy Thanh"))
        self.groupBox_3.setTitle(_translate("MainWindow", "List of Data:"))
        item = self.tableWidgetStatistic.horizontalHeaderItem(0)
        item.setText(_translate("MainWindow", "Gender"))
        item = self.tableWidgetStatistic.horizontalHeaderItem(1)
        item.setText(_translate("MainWindow", "Number Sales"))
        self.groupBox_4.setTitle(_translate("MainWindow", "Chart Visualization:"))
        self.groupBox.setTitle(_translate("MainWindow", "Functions:"))
        self.pushButtonPurchaseRatesByGender.setText(_translate("MainWindow", "(1) Purchase rates by gender"))
        self.label_2.setText(_translate("MainWindow", "From:"))
        self.lineEditFromAge.setText(_translate("MainWindow", "30"))
        self.label.setText(_translate("MainWindow", "To:"))
        self.lineEditToAge.setText(_translate("MainWindow", "50"))
        self.pushButtonPurchaseRatesByAgeGroup.setText(_translate("MainWindow", "(2) Purchase rates by age group"))
        self.pushButtonPurchaseCountingByCategory.setText(_translate("MainWindow", "(3) Purchase counting by category"))
        self.pushButtonPurchaseValueByCategory.setText(_translate("MainWindow", "(4) Purchase value by category"))
        self.pushButtonPurchaseByCategoryAndGender.setText(_translate("MainWindow", "(5) Purchases by category and gender"))
        self.pushButtonPaymentMethod.setText(_translate("MainWindow", "(6) Payment method: Cash, Credit, Debit card"))
        self.pushButtonPurchaseRatesByShoppingMall.setText(_translate("MainWindow", "(7) Purchase rates by Shopping Mall"))
        self.pushButtonProductSpendingByGender.setText(_translate("MainWindow", "(8) Products spending by gender"))
        self.pushButtonPurchaseFrequenceByAge.setText(_translate("MainWindow", "(9) Purchase frequency by age"))
        self.pushButtonSalesFluctuationsByMonth.setText(_translate("MainWindow", "(10) Statistics on sales fluctuations by month"))
        self.pushButtonSalesFlucuationsByYearAndMonth.setText(_translate("MainWindow", "(11) Statistics on sales fluctuations by year and month"))
        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("MainWindow", "Statistics:"))
        self.groupBox_2.setTitle(_translate("MainWindow", "Choose Independent and Dependent variables:"))
        self.radioButtonGenderAge.setText(_translate("MainWindow", "Gender, Age => Price"))
        self.radioButtonGenderAgePayment.setText(_translate("MainWindow", "Gender, Age, Payment Method=>Price"))
        self.groupBox_6.setTitle(_translate("MainWindow", "Evaluation"))
        self.label_7.setText(_translate("MainWindow", "MAE:"))
        self.label_8.setText(_translate("MainWindow", "MSE:"))
        self.label_9.setText(_translate("MainWindow", "RMSE:"))
        self.label_10.setText(_translate("MainWindow", "R2_SCORE:"))
        self.pushButtonEvaluate.setText(_translate("MainWindow", "Evaluate"))
        self.groupBox_7.setTitle(_translate("MainWindow", "Train Model:"))
        self.lineEditRandomState.setText(_translate("MainWindow", "0"))
        self.label_5.setText(_translate("MainWindow", "Random State:"))
        self.label_4.setText(_translate("MainWindow", "%"))
        self.label_3.setText(_translate("MainWindow", "Test Size:"))
        self.lineEditTestSize.setText(_translate("MainWindow", "20"))
        self.pushButtonTrainModel.setText(_translate("MainWindow", "Train Model"))
        self.groupBox_8.setTitle(_translate("MainWindow", "Save Trained Model"))
        self.label_11.setText(_translate("MainWindow", "Path:"))
        self.pushButtonSavePath.setText(_translate("MainWindow", "..."))
        self.pushButtonSaveModel.setText(_translate("MainWindow", "Save model"))
        self.groupBox_9.setTitle(_translate("MainWindow", "Load trained Model:"))
        self.pushButtonLoadModel.setText(_translate("MainWindow", "Load Model"))
        self.groupBox_10.setTitle(_translate("MainWindow", "Prediction:"))
        self.label_12.setText(_translate("MainWindow", "Gender:"))
        self.label_13.setText(_translate("MainWindow", "Age:"))
        self.label_14.setText(_translate("MainWindow", "Payment Method:"))
        self.pushButtonPredict.setText(_translate("MainWindow", "Predict"))
        self.label_15.setText(_translate("MainWindow", "Predicted Price:"))
        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("MainWindow", "Machine Learning"))
        self.menuSystem.setTitle(_translate("MainWindow", "System"))
        self.menuHelp.setTitle(_translate("MainWindow", "Help"))
        self.actionConnection.setText(_translate("MainWindow", "Connect Database"))
        self.actionConnection.setShortcut(_translate("MainWindow", "Ctrl+D"))
        self.actionSave_trained_Machine_Learning_Model.setText(_translate("MainWindow", "Save trained ML Model"))
        self.actionSave_trained_Machine_Learning_Model.setShortcut(_translate("MainWindow", "Ctrl+S"))
        self.actionLoad_trained_Machine_Learning_Model.setText(_translate("MainWindow", "Load trained ML Model"))
        self.actionLoad_trained_Machine_Learning_Model.setShortcut(_translate("MainWindow", "Ctrl+O"))
        self.actionExit.setText(_translate("MainWindow", "Exit"))
        self.actionExit.setShortcut(_translate("MainWindow", "Esc"))
        self.actionInstructions.setText(_translate("MainWindow", "Instructions"))
        self.actionAbout.setText(_translate("MainWindow", "About"))

Bước 5.8: Tạo Python code kế “MainWindowEx.py” thừa Ui_MainWindow của mã lệnh Generate Python code giao diện “MainWindow.py” để xử lý sự kiện tương tác người dùng:

import random
from random import random
import plotly.graph_objects as go

from PyQt6 import QtGui, QtCore
from PyQt6.QtCore import Qt, QSize
from PyQt6.QtGui import QAction, QIcon, QPixmap
from PyQt6.QtWidgets import QMessageBox, QTableWidgetItem, QMainWindow, QDialog, QComboBox, QPushButton, QCheckBox, \
    QListWidgetItem, QFileDialog
from matplotlib import pyplot as plt
import seaborn as sns
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar

from Connectors.Connector import Connector
from Models.PurchaseLinearRegression import PurchaseLinearRegression
from Models.PurchaseStatistic import PurchaseStatistic
from UI.ChartHandle import ChartHandle
from UI.DatabaseConnectEx import DatabaseConnectEx
from UI.MainWindow import Ui_MainWindow
import traceback


import matplotlib

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from matplotlib.figure import Figure


from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import random


class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        self.purchaseLinearRegression = PurchaseLinearRegression()
        self.databaseConnectEx=DatabaseConnectEx()
        self.databaseConnectEx.parent=self
        self.chartHandle= ChartHandle()
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.verticalLayoutFunctions.setAlignment(Qt.AlignmentFlag.AlignTop)
        self.setupPlot()

        self.actionConnection.triggered.connect(self.openDatabaseConnectUI)

        self.pushButtonPurchaseRatesByGender.clicked.connect(self.showPurchaseRatesByGender)
        self.pushButtonSalesFlucuationsByYearAndMonth.clicked.connect(self.showSalesFlucuationsByYearAndMonth)
        self.pushButtonPurchaseCountingByCategory.clicked.connect(self.showPurchaseCountingByCategory)
        self.pushButtonPurchaseRatesByAgeGroup.clicked.connect(self.showPurchaseRatesByAgeGroup)
        self.pushButtonPurchaseCountingByCategory.clicked.connect(self.showPurchaseCountingByCategory)
        self.pushButtonPurchaseValueByCategory.clicked.connect(self.showPurchaseValueByCategory)
        self.pushButtonPurchaseByCategoryAndGender.clicked.connect(self.showPurchaseByCategoryAndGender)
        self.pushButtonPaymentMethod.clicked.connect(self.showPaymentMethod)
        self.pushButtonPurchaseRatesByShoppingMall.clicked.connect(self.showPurchaseRatesByShoppingMall)
        self.pushButtonProductSpendingByGender.clicked.connect(self.showProductSpendingByGender)
        self.pushButtonPurchaseFrequenceByAge.clicked.connect(self.showShowPurchaseFrequenceByAge)
        self.pushButtonSalesFluctuationsByMonth.clicked.connect(self.showpushButtonSalesFluctuationsByMonth)
        self.checkEnableWidget(False)

        self.pushButtonTrainModel.clicked.connect(self.processTrainModel)
        self.pushButtonEvaluate.clicked.connect(self.processEvaluateTrainedModel)
        self.pushButtonSavePath.clicked.connect(self.processPickSavePath)
        self.pushButtonSaveModel.clicked.connect(self.processSaveTrainedModel)
        self.pushButtonLoadModel.clicked.connect(self.processLoadTrainedModel)
        self.pushButtonPredict.clicked.connect(self.processPrediction)
    def show(self):
        self.MainWindow.show()
    def checkEnableWidget(self,flag=True):
        self.pushButtonPurchaseRatesByGender.setEnabled(flag)
        self.pushButtonPurchaseRatesByAgeGroup.setEnabled(flag)
        self.pushButtonPurchaseCountingByCategory.setEnabled(flag)
        self.pushButtonPurchaseValueByCategory.setEnabled(flag)
        self.pushButtonPurchaseByCategoryAndGender.setEnabled(flag)
        self.pushButtonPaymentMethod.setEnabled(flag)
        self.pushButtonPurchaseRatesByShoppingMall.setEnabled(flag)

        self.pushButtonProductSpendingByGender.setEnabled(flag)
        self.pushButtonPurchaseFrequenceByAge.setEnabled(flag)
        self.pushButtonSalesFluctuationsByMonth.setEnabled(flag)
        self.pushButtonSalesFlucuationsByYearAndMonth.setEnabled(flag)

    def setupPlot(self):
        self.figure = plt.figure()
        self.canvas = FigureCanvas(self.figure)
        self.toolbar = NavigationToolbar(self.canvas, self.MainWindow)

        # adding tool bar to the layout
        self.verticalLayoutPlot.addWidget(self.toolbar)
        # adding canvas to the layout
        self.verticalLayoutPlot.addWidget(self.canvas)
    def openDatabaseConnectUI(self):
        dbwindow = QMainWindow()
        self.databaseConnectEx.setupUi(dbwindow)
        self.databaseConnectEx.show()
    def showDataIntoTableWidget(self,df):
        self.tableWidgetStatistic.setRowCount(0)
        self.tableWidgetStatistic.setColumnCount(len(df.columns))
        for i in range(len(df.columns)):
            columnHeader = df.columns[i]
            self.tableWidgetStatistic.setHorizontalHeaderItem(i, QTableWidgetItem(columnHeader))
        row = 0
        for item in df.iloc:
            arr = item.values.tolist()
            self.tableWidgetStatistic.insertRow(row)
            j=0
            for data in arr:
                self.tableWidgetStatistic.setItem(row, j, QTableWidgetItem(str(data)))
                j=j+1
            row = row + 1

    def showPurchaseCountingByCategory(self):
        self.purchaseLinearRegression.connector = self.databaseConnectEx.connector
        self.purchaseLinearRegression.execPurchaseHistory()
        self.purchaseLinearRegression.processCategoryDistribution()
        print(self.purchaseLinearRegression.dfCategory)

        df = self.purchaseLinearRegression.dfCategory

        self.showDataIntoTableWidget(df)

        columnLabel = "category"
        columnStatistic = "count"
        title = "Categories Distribution"
        legend = False
        #self.visualizePieChart(df, columnLabel, columnStatistic, title, legend)
        self.chartHandle.visualizePieChart(self.figure,self.canvas,df, columnLabel, columnStatistic, title, legend)

    def showPurchaseRatesByGender(self):
        self.purchaseLinearRegression.connector = self.databaseConnectEx.connector
        self.purchaseLinearRegression.execPurchaseHistory()
        self.purchaseLinearRegression.processGenderDistribution()
        print(self.purchaseLinearRegression.dfGender)

        df = self.purchaseLinearRegression.dfGender

        self.showDataIntoTableWidget(df)

        columnLabel = "gender"
        columnStatistic = "count"
        title = "Gender Distribution"
        legend = True
        self.chartHandle.visualizePieChart(self.figure,self.canvas,df, columnLabel, columnStatistic, title, legend)
        #self.visualizePieChart(df, columnLabel, columnStatistic, title, legend)

    def showSalesFlucuationsByYearAndMonth(self):
        self.purchaseLinearRegression.connector = self.databaseConnectEx.connector
        self.purchaseLinearRegression.execPurchaseHistory()
        self.purchaseLinearRegression.processMonthlyAndYearSalesAmount()
        print(self.purchaseLinearRegression.dfMonthlyAndYearSalesAmount)
        df = self.purchaseLinearRegression.dfMonthlyAndYearSalesAmount
        self.showDataIntoTableWidget(df)
        self.chartHandle.visualizeLinePlotChart(self.figure,self.canvas, self.purchaseLinearRegression.dfMonthlyAndYearSalesAmount, "month", "sales_amount",
                                    "Monthly Variation in Sales Amount Over Years", hue="year", xticks=True)
        #self.visualizeLinePlotChart(self.purchaseLinearRegression.dfMonthlyAndYearSalesAmount, "month", "sales_amount",
        #                            "Monthly Variation in Sales Amount Over Years", hue="year", xticks=True)
    def showPurchaseRatesByAgeGroup(self):
        self.purchaseLinearRegression.connector = self.databaseConnectEx.connector
        self.purchaseLinearRegression.execPurchaseHistory()
        fromAge=int(self.lineEditFromAge.text())
        toAge=int(self.lineEditToAge.text())
        self.purchaseLinearRegression.processAgeDistribution(fromAge,toAge)
        print(self.purchaseLinearRegression.dfAges)

        df = self.purchaseLinearRegression.dfAges

        self.showDataIntoTableWidget(df)
        columnLabel= "age"
        columnStatistic ="count"
        title= "Age Distribution %s~%s"%(fromAge,toAge)
        hue = None
        self.chartHandle.visualizeLinePlotChart(self.figure,self.canvas,df, columnLabel, columnStatistic,title, hue)
        #self.visualizeLinePlotChart(df, columnLabel, columnStatistic,title, hue)
    def showPurchaseCountingByCategory(self):
        self.purchaseLinearRegression.connector = self.databaseConnectEx.connector
        self.purchaseLinearRegression.execPurchaseHistory()
        df = self.purchaseLinearRegression.processCategoryDistribution()
        self.showDataIntoTableWidget(df)
        columnLabel = "category"
        columnStatistic = "count"
        title = "Categories Distribution"
        legend = False
        hue=None
        self.chartHandle.visualizeLinePlotChart(self.figure,self.canvas,df, columnLabel, columnStatistic, title, hue)
        #self.visualizePieChart(df, columnLabel, columnStatistic, title, legend)
    def showPurchaseValueByCategory(self):
        # self.purchaseLinearRegression.connector = self.databaseConnectEx.connector
        # self.purchaseLinearRegression.execPurchaseHistory()
        df = self.purchaseLinearRegression.processCategorySpending()
        self.showDataIntoTableWidget(df)
        columnLabel = "category"
        columnStatistic = "price"
        title = "Distribution category and Spending"
        self.chartHandle.visualizeBarChart(self.figure,self.canvas,df,columnLabel,columnStatistic,title)
        #self.visualizeBarChart(df,columnLabel,columnStatistic,title)
    def showPurchaseByCategoryAndGender(self):
        # self.purchaseLinearRegression.connector = self.databaseConnectEx.connector
        # self.purchaseLinearRegression.execPurchaseHistory()
        df = self.purchaseLinearRegression.processGenderAndCategoryCounter()
        self.showDataIntoTableWidget(df)
        df=self.purchaseLinearRegression.df
        columnLabel = "category"
        columnStatistic = "count"
        hue="gender"
        title = "Distribution gender and category"
        self.chartHandle.visualizeMultiBarChart(self.figure,self.canvas,df, columnLabel, columnStatistic,hue, title)
        #self.visualizeMultiBarChart(df, columnLabel, columnStatistic,hue, title)
    def showPaymentMethod(self):
        # self.purchaseLinearRegression.connector = self.databaseConnectEx.connector
        # self.purchaseLinearRegression.execPurchaseHistory()
        df = self.purchaseLinearRegression.processPaymentMethod()
        self.showDataIntoTableWidget(df)
        columnLabel = "payment_method"
        columnStatistic = "count"
        title = "Payment Distribution"
        legend = False
        self.chartHandle.visualizePieChart(self.figure,self.canvas,df, columnLabel, columnStatistic, title, legend)
        #self.visualizePieChart(df, columnLabel, columnStatistic, title, legend)
    def showPurchaseRatesByShoppingMall(self):
        # self.purchaseLinearRegression.connector = self.databaseConnectEx.connector
        # self.purchaseLinearRegression.execPurchaseHistory()
        df = self.purchaseLinearRegression.processShoppingMall()
        self.showDataIntoTableWidget(df)
        columnLabel = "shopping_mall"
        columnStatistic = "count"
        title = "Shopping Mall Distribution"
        legend = False
        self.chartHandle.visualizePieChart(self.figure,self.canvas,df, columnLabel, columnStatistic, title, legend)
        #self.visualizePieChart(df, columnLabel, columnStatistic, title, legend)
    def showProductSpendingByGender(self):
        # self.purchaseLinearRegression.connector = self.databaseConnectEx.connector
        # self.purchaseLinearRegression.execPurchaseHistory()
        df = self.purchaseLinearRegression.processGenderCategorySpending()
        self.showDataIntoTableWidget(df)
        columnLabel = "category"
        columnStatistic = "price"
        hue="gender"
        title = "Male and Female category Total Price Spend"
        legend = False
        self.chartHandle.visualizeBarPlot(self.figure,self.canvas,df, columnLabel, columnStatistic,hue, title)
        #self.visualizeBarPlot(df, columnLabel, columnStatistic,hue, title)

    def showShowPurchaseFrequenceByAge(self):
        # self.purchaseLinearRegression.connector = self.databaseConnectEx.connector
        # self.purchaseLinearRegression.execPurchaseHistory()
        df = self.purchaseLinearRegression.processAgeOrderFrequence()
        self.showDataIntoTableWidget(df)
        columnLabel = "age"
        columnStatistic = "count"
        title = "Age VS Order Frequence"
        self.chartHandle.visualizeScatterPlot(self.figure,self.canvas,df, columnLabel,columnStatistic, title)
        #self.visualizeScatterPlot(df, columnLabel,columnStatistic, title)

    def showpushButtonSalesFluctuationsByMonth(self):
        # self.purchaseLinearRegression.connector = self.databaseConnectEx.connector
        # self.purchaseLinearRegression.execPurchaseHistory()

        df=self.purchaseLinearRegression.processMonthlySalesAmount()
        print(df)

        self.showDataIntoTableWidget(df)
        columnLabel = "month"
        columnStatistic = "sales_amount"
        title = "Monthly Variation in Sales Amount"
        hue = None
        self.chartHandle.visualizeLinePlotChart(self.figure,self.canvas,df, columnLabel, columnStatistic, title, hue)
    def processTrainModel(self):
        columns_input=["gender","age"]
        column_target="price"
        if self.radioButtonGenderAgePayment.isChecked():
            columns_input=["gender","age","payment_method"]
        test_size=float(self.lineEditTestSize.text())/100
        random_state=int(self.lineEditRandomState.text())
        self.purchaseLinearRegression = PurchaseLinearRegression()
        self.purchaseLinearRegression.connector = self.databaseConnectEx.connector
        self.purchaseLinearRegression.processTrain(
            columns_input,
            column_target,
            test_size,
            random_state)
        dlg = QMessageBox(self.MainWindow)
        dlg.setWindowTitle("Info")
        dlg.setIcon(QMessageBox.Icon.Information)
        dlg.setText("Train machine learning model successful!")
        buttons = QMessageBox.StandardButton.Yes
        dlg.setStandardButtons(buttons)
        button = dlg.exec()
    def processEvaluateTrainedModel(self):
        result = self.purchaseLinearRegression.evaluate()
        self.lineEditMAE.setText(str(result.MAE))
        self.lineEditMSE.setText(str(result.MSE))
        self.lineEditRMSE.setText(str(result.RMSE))
        self.lineEditR2SCore.setText(str(result.R2_SCORE))
    def processPickSavePath(self):
        filters = "trained model file (*.zip);;All files(*)"
        filename, selected_filter = QFileDialog.getSaveFileName(
            self.MainWindow,
            filter=filters,
        )
        self.lineEditPath.setText(filename)
    def processSaveTrainedModel(self):
        trainedModelPath=self.lineEditPath.text()
        if trainedModelPath=="":
            return
        ret = self.purchaseLinearRegression.saveModel(trainedModelPath)
        dlg = QMessageBox(self.MainWindow)
        dlg.setWindowTitle("Info")
        dlg.setIcon(QMessageBox.Icon.Information)
        dlg.setText(f"Saved Trained machine learning model successful at [{trainedModelPath}]!")
        buttons = QMessageBox.StandardButton.Yes
        dlg.setStandardButtons(buttons)
        button = dlg.exec()
    def processLoadTrainedModel(self):
        # setup for QFileDialog
        filters = "trained model file (*.zip);;All files(*)"
        filename, selected_filter = QFileDialog.getOpenFileName(
            self.MainWindow,
            filter=filters,
        )
        if filename=="":
            return
        self.lineEditLocationLoadTrainedModel.setText(filename)
        self.purchaseLinearRegression.loadModel(filename)
        dlg = QMessageBox(self.MainWindow)
        dlg.setWindowTitle("Info")
        dlg.setIcon(QMessageBox.Icon.Information)
        dlg.setText(f"Load Trained machine learning model successful from [{filename}]!")
        buttons = QMessageBox.StandardButton.Yes
        dlg.setStandardButtons(buttons)
        button = dlg.exec()
    def processPrediction(self):
        gender = self.lineEditGender.text()
        age = int(self.lineEditAge.text())
        payment = self.lineEditPaymentMethod.text()
        if len(self.purchaseLinearRegression.trainedmodel.columns_input)==3:
            predicted_price = self.purchaseLinearRegression.predictPriceFromGenderAndAgeAndPayment(gender, age, payment)
        else:
            predicted_price = self.purchaseLinearRegression.predictPriceFromGenderAndAge(gender, age)
        self.lineEditPredictedPrice.setText(str(predicted_price[0]))

Bước 6: Tạo file thực thi “App.py

from PyQt6.QtWidgets import QApplication, QMainWindow

from UI.MainWindowEx import MainWindowEx

qApp=QApplication([])
qmainWindow=QMainWindow()
window=MainWindowEx()
window.setupUi(qmainWindow)
window.show()
qApp.exec()

Chạy App.py ta có kết quả:

Vào menu System chọn Connect Database:

Nhập thông số kết nối:

Bấm “Connect” để kết nối, nếu kết nối thành công ta có giao diện như dưới đây:

Các bạn thử 11 chức năng thông kế sẽ có các kết quả như mong muốn

(1.1) Thống kê tỉ lệ mua hàng theo giới tính

(1.2) Thống kê số lượng mua hàng theo độ tuổi


(1.3) Thống kê số lượng mua hàng theo danh mục sản phẩm


(1.4) Thống kê trị giá hàng hóa bán được theo danh mục


(1.5) Thống kê lượng hàn bán ra theo độ tuổi và danh mục


(1.6) Thống kê số lượng giao dịch theo phương thức thanh toán


(1.7) Thống kê tỉ lệ bán hàng theo Trung tâm thương mại (Shopping Mall)


(1.8) Thống kê trị giá hàng hóa bán được theo danh mục và giới tính


(1.9) Thống kê tần suất mua hàng theo độ tuổi và giới tính


(1.10) Thống kê biến động doanh thu theo tháng


(1.11) Thống kê biến động doanh thu theo tháng và theo năm

Về thử nghiệm chức năng máy học:

Như vậy tới đây Tui đã trình bày hoàn chỉnh dự án MLBAProject. dự án phục vụ thống kê và train mô hình máy học, dự án kết nối cơ sở dữ liệu, tổng hợp nhiều kiến thức.

Các bạn nhớ thực hành nhiều lần để hiểu rõ hơn về dự án.

Source code dự án đầy đủ các bạn tham khảo tại đây:

https://www.mediafire.com/file/e4nzjt8aaadr7f4/MLBAProject.rar/file

Bài học sau Tui hướng dẫn các bạn sử dụng K-Means để gom cụm dữ liệu, các bạn chú ý theo dõi. Chúc các bạn thành công

Bài 52: Kỹ thuật ORM trong Python với Cơ sở dữ liệu MySQL Server (p3)

Như vậy các bạn đã sử dụng thành thạo kỹ thuật ORM trong Python với Cơ sở dữ liệu MySQL Server thông qua bài 50bài 51. Toàn bộ các chức năng CRUD trên các bảng dữ liệu các bạn đã có thể xem, thêm, sửa xóa với ORM technique, cũng như xử lý dữ liệu dạng Master-Details với các mối quan hệ has_manybelongs_to.

Bài này Tui tiếp tục hướng dẫn các bạn cách sử dụng ORM để tương tác dữ liệu với giao diện người dùng trong cơ sở dữ liệu Sakila đã đề cập ở bài học trước, chúng ta sẽ sử dụng bảng Customer, bảng Address và bảng Rental. Sakila là cơ sở dữ liệu mẫu có nhiều relationship khi chúng ta cài đặt My SQL Server, nên các bạn có thể sử dụng trực tiếp từ máy của bạn. Giao diện tương tác được thiết kế như đưới đây:

Phần mềm trên bao gồm các chức năng sử dụng kỹ thuật ORM được liệt kê như dưới đây:

(1) Nạp danh sách Customer vào QTableWidget

(2) Xem chi tiết Customer, khi người dùng nhấn chọn Customer nào trong QTableWidget thì thông tin chi tiết của Customer sẽ hiển thị và các ô QLineEdit ở bên phải màn hình

(3) Xem danh sách Rentals của Customer, khi người dùng nhấn chọn Customer nào trong QTableWidget thì danh sách Rentals của Customer ngày sẽ được nạp vào QTableWidget ở bên dưới màn hình.

(4) Chức năng “Clear”: Khi người dùng nhấn vào nút lệnh này thì toàn bộ dữ liệu trong phần Customer Details sẽ được xóa rỗng để người sử dụng nhập mới dữ liệu Customer

(5) Chức năng “Insert“: Khi người dùng nhấn vào nút lệnh này thì Customer sẽ được thêm mới vào trong cơ sở dữ liệu Sakila.

(6) Chức năng “Update“: Khi người dùng nhấn vào nút lệnh này thì Customer sẽ được cập nhật dữ liệu vào cơ sở dữ liệu Sakila.

(7) Chức năng “Delete“: Khi người dùng nhấn vào nút lệnh này thì Customer sẽ được xóa khỏi cơ sở dữ liệu Sakila. Có xác nhận xóa hay không

(8) Chức năng “Exit”: Xác nhận thoát phần mềm hay không.

Dưới đây là 3 bảng Customer, Address và Rental trong cơ sở dữ liệu mẫu Sakila khi bạn cài MySQL Server:

Lưu ý các mã lệnh cấu hình chuỗi kết nối cơ sở dữ liệu nó lệ thuộc vào máy tính của bạn, và cũng tương tự như các bài học trước.

Ta tiến hành thực hiện các bước sau để hoàn tất dự án:

Bước 0: Tạo dự án “sakila_orm_gui” trong Pycharm có cấu trúc như dưới đây:

Mô tả cho các thư mục, tập tin của dự án như sau:

  • Thư mục “Classes“: Chứa các tập tin các mã lệnh để kết nối cơ sở dữ liệu (DatabaseConnection.py), tạo các classes mapping cho các bảng Customer, Address và Rental (ClassMapping.py).
  • Thư mục “Images“: Thư mục chứa hình ảnh, icon cho phần mềm
  • Thư mục “UI“: Thư mục chứa các giao diện thiết kế phần mềm (MainWindow.ui), code generate Python cho giao diện(MainWindow.py), code Python kế thừa để xử lý tương tác người dùng(MainWindowEx.py)
  • Tập tin “MyApp.py“: Tập tin chứa mã lệnh để thực thi chương trình

Bước 1: Viết mã lệnh “DatabaseConnection.py

from orm import Table

#configuration JSON for connection MySQL
CONFIG={
    'host': 'localhost',
    'port': 3306,
    'user': 'root',
    'password': '@Obama123',
    'database': 'sakila'
}
#connect to database from JSON configuration
Table.connect(config_dict=CONFIG)

Mã lệnh trên cấu hình chuỗi kết nối tới MySQL Server, tùy thuộc vào sự cài đặt và cấu hình phần mềm của bạn mà chuỗi kết nối này sẽ khác nhau.

Sau khi có chuỗi kết nối, ta gọi phương thức connect () của Table.

Bước 2: Tạo các classes mapping, chúng được khai báo và tạo các relationship trong “ClassMapping.py“:

from orm import Table, has_many, belongs_to

class Customer(Table):
    table_name = 'customer'

    relations = [
        has_many(name='rentals', _class='Rental', foreign_key='customer_id'),
        has_many(name='addresses', _class='Address', foreign_key='address_id')
    ]
class Rental(Table):
    table_name = 'rental'

    relations = [
        belongs_to(name='customer', _class='Customer', foreign_key='customer_id',primary_key="customer_id")
    ]
class Address(Table):
    table_name = 'address'

Bảng customer được khai báo Mapping thành lớp Customer, bảng address được khai báo Mapping thành lớp Address. Customer và Address có mối quan hệ: 1 Customer có nhiều Address theo thiết kế. Trong lớp Customer các bạn thấy Tui khai báo relations has_many:

has_many(name='addresses', _class='Address', foreign_key='address_id')
  • name “addresses”: Đây chính là phương thức addresses() trả về danh sách Address
  • _class=”Address”: Tức là khi gọi hàm addresses() chương trình sẽ trả về danh sách đối tượng có kiểu Address
  • foreign_key=”address_id”: Dựa vào khóa ngoại này để ứng với 1 Customer sẽ truy suất ra được danh sách Address, và theo quan sát dữ liệu Sakila thì tuy thiết kế 1 Customer có nhiều Address nhưng dữ liệu chỉ có 1 Address, tức là mảng addresses() khi gọi hàm này thì ta lấy phần tử đầu tiên chính là 1 Address của Customer mà ta quan tâm.

Ngoài ra Customer cũng có mối quan hệ với Rental. 1 Customer có nhiều Rentals, và 1 Rental thuộc về một Customer.

Ta thấy relations khai báo trong Customer liên quan tới Rental:

has_many(name='rentals', _class='Rental', foreign_key='customer_id')
  • name “rentals”: Đây chính là phương thức rentals() trả về danh sách Rentals
  • _class=”Rental”: Tức là khi gọi hàm rentals() chương trình sẽ trả về danh sách đối tượng có kiểu Rental
  • foreign_key=”customer_id”: Dựa vào khóa ngoại này để ứng với 1 Customer sẽ truy suất ra được danh sách Rentals.

Lớp Rental cũng có relations thể hiện: 1 Rental thuộc về một Customer, ta có khai báo mối quan hệ belongs_to:

belongs_to(name='customer', _class='Customer', foreign_key='customer_id',primary_key="customer_id")
  • name “customer”: Đây chính là phương thức customer() tả 1 một đối tượng Customer
  • _class=”Customer”: Tức là khi gọi hàm customer() chương trình sẽ trả về đối tượng có kiểu Customer
  • foreign_key=”customer_id”: Dựa vào khóa ngoại này để ứng với 1 Customer sẽ truy suất ra được danh sách Rentals.
  • primary_key=”customer_id”: Dựa vào khóa này, thì từ Rental sẽ suy ra Customer đang sở hữu Rental này.

Bước 3: Thiết kế giao diện tương tác người dùng MainWindow.ui, sử dụng chức năng tích hợp PyQt6, Qt Designer trong Pycharm để tạo giao diện MainWindow.ui trong thư mục UI này, cấu trúc như sau:

Các bạn thiết kế giao diện và đặt tên cho các Widget như hình minh họa ở trên.

Bước 4: Generate Python code MainWindow.py cho giao diện MainWindow.ui, Chức năng Generate đã được học ở những bài đầu tiên, bạn chưa biết thì nhớ xem lại từ đầu:

# Form implementation generated from reading ui file 'E:\Elearning\sakila_orm_gui\UI\MainWindow.ui'
#
# Created by: PyQt6 UI code generator 6.6.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt6 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(727, 682)
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap("E:\\Elearning\\sakila_orm_gui\\UI\\../Images/ic_logo.jpg"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        MainWindow.setWindowIcon(icon)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.groupBox = QtWidgets.QGroupBox(parent=self.centralwidget)
        self.groupBox.setGeometry(QtCore.QRect(10, 50, 391, 281))
        self.groupBox.setStyleSheet("background-color: rgb(255, 239, 240);")
        self.groupBox.setObjectName("groupBox")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox)
        self.verticalLayout.setObjectName("verticalLayout")
        self.tableWidgetCustomer = QtWidgets.QTableWidget(parent=self.groupBox)
        self.tableWidgetCustomer.setObjectName("tableWidgetCustomer")
        self.tableWidgetCustomer.setColumnCount(3)
        self.tableWidgetCustomer.setRowCount(0)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidgetCustomer.setHorizontalHeaderItem(0, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidgetCustomer.setHorizontalHeaderItem(1, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidgetCustomer.setHorizontalHeaderItem(2, item)
        self.verticalLayout.addWidget(self.tableWidgetCustomer)
        self.groupBox_2 = QtWidgets.QGroupBox(parent=self.centralwidget)
        self.groupBox_2.setGeometry(QtCore.QRect(10, 400, 701, 241))
        self.groupBox_2.setStyleSheet("background-color: rgb(244, 246, 255);")
        self.groupBox_2.setObjectName("groupBox_2")
        self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.groupBox_2)
        self.verticalLayout_3.setObjectName("verticalLayout_3")
        self.tableWidgetRental = QtWidgets.QTableWidget(parent=self.groupBox_2)
        self.tableWidgetRental.setObjectName("tableWidgetRental")
        self.tableWidgetRental.setColumnCount(7)
        self.tableWidgetRental.setRowCount(0)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidgetRental.setHorizontalHeaderItem(0, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidgetRental.setHorizontalHeaderItem(1, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidgetRental.setHorizontalHeaderItem(2, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidgetRental.setHorizontalHeaderItem(3, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidgetRental.setHorizontalHeaderItem(4, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidgetRental.setHorizontalHeaderItem(5, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidgetRental.setHorizontalHeaderItem(6, item)
        self.verticalLayout_3.addWidget(self.tableWidgetRental)
        self.groupBox_3 = QtWidgets.QGroupBox(parent=self.centralwidget)
        self.groupBox_3.setGeometry(QtCore.QRect(410, 50, 301, 281))
        self.groupBox_3.setStyleSheet("background-color: rgb(226, 255, 254);")
        self.groupBox_3.setObjectName("groupBox_3")
        self.label = QtWidgets.QLabel(parent=self.groupBox_3)
        self.label.setGeometry(QtCore.QRect(20, 30, 81, 16))
        self.label.setObjectName("label")
        self.lineEditCustomerId = QtWidgets.QLineEdit(parent=self.groupBox_3)
        self.lineEditCustomerId.setGeometry(QtCore.QRect(100, 30, 191, 22))
        self.lineEditCustomerId.setStyleSheet("background-color: rgb(255, 255, 0);")
        self.lineEditCustomerId.setObjectName("lineEditCustomerId")
        self.lineEditFirstName = QtWidgets.QLineEdit(parent=self.groupBox_3)
        self.lineEditFirstName.setGeometry(QtCore.QRect(100, 60, 191, 22))
        self.lineEditFirstName.setStyleSheet("background-color: rgb(255, 255, 0);")
        self.lineEditFirstName.setObjectName("lineEditFirstName")
        self.label_2 = QtWidgets.QLabel(parent=self.groupBox_3)
        self.label_2.setGeometry(QtCore.QRect(20, 60, 71, 16))
        self.label_2.setObjectName("label_2")
        self.lineEditLastName = QtWidgets.QLineEdit(parent=self.groupBox_3)
        self.lineEditLastName.setGeometry(QtCore.QRect(100, 90, 191, 22))
        self.lineEditLastName.setStyleSheet("background-color: rgb(255, 255, 0);")
        self.lineEditLastName.setObjectName("lineEditLastName")
        self.label_3 = QtWidgets.QLabel(parent=self.groupBox_3)
        self.label_3.setGeometry(QtCore.QRect(20, 90, 71, 16))
        self.label_3.setObjectName("label_3")
        self.lineEditEmail = QtWidgets.QLineEdit(parent=self.groupBox_3)
        self.lineEditEmail.setGeometry(QtCore.QRect(100, 120, 191, 22))
        self.lineEditEmail.setStyleSheet("background-color: rgb(255, 255, 0);")
        self.lineEditEmail.setObjectName("lineEditEmail")
        self.label_4 = QtWidgets.QLabel(parent=self.groupBox_3)
        self.label_4.setGeometry(QtCore.QRect(20, 120, 61, 16))
        self.label_4.setObjectName("label_4")
        self.lineEditAddress = QtWidgets.QLineEdit(parent=self.groupBox_3)
        self.lineEditAddress.setGeometry(QtCore.QRect(100, 150, 191, 22))
        self.lineEditAddress.setStyleSheet("background-color: rgb(255, 255, 0);")
        self.lineEditAddress.setObjectName("lineEditAddress")
        self.label_5 = QtWidgets.QLabel(parent=self.groupBox_3)
        self.label_5.setGeometry(QtCore.QRect(20, 150, 71, 16))
        self.label_5.setObjectName("label_5")
        self.checkBoxActive = QtWidgets.QCheckBox(parent=self.groupBox_3)
        self.checkBoxActive.setGeometry(QtCore.QRect(100, 190, 81, 20))
        self.checkBoxActive.setObjectName("checkBoxActive")
        self.lineEditCreateDate = QtWidgets.QLineEdit(parent=self.groupBox_3)
        self.lineEditCreateDate.setGeometry(QtCore.QRect(100, 220, 191, 22))
        self.lineEditCreateDate.setStyleSheet("background-color: rgb(255, 255, 0);")
        self.lineEditCreateDate.setObjectName("lineEditCreateDate")
        self.label_6 = QtWidgets.QLabel(parent=self.groupBox_3)
        self.label_6.setGeometry(QtCore.QRect(20, 220, 81, 16))
        self.label_6.setObjectName("label_6")
        self.lineEditLastUpdate = QtWidgets.QLineEdit(parent=self.groupBox_3)
        self.lineEditLastUpdate.setGeometry(QtCore.QRect(100, 250, 191, 22))
        self.lineEditLastUpdate.setStyleSheet("background-color: rgb(255, 255, 0);")
        self.lineEditLastUpdate.setObjectName("lineEditLastUpdate")
        self.label_7 = QtWidgets.QLabel(parent=self.groupBox_3)
        self.label_7.setGeometry(QtCore.QRect(20, 250, 81, 16))
        self.label_7.setObjectName("label_7")
        self.groupBox_4 = QtWidgets.QGroupBox(parent=self.centralwidget)
        self.groupBox_4.setGeometry(QtCore.QRect(10, 330, 701, 71))
        self.groupBox_4.setStyleSheet("background-color: rgb(255, 248, 239);")
        self.groupBox_4.setObjectName("groupBox_4")
        self.pushButtonClear = QtWidgets.QPushButton(parent=self.groupBox_4)
        self.pushButtonClear.setGeometry(QtCore.QRect(20, 20, 93, 41))
        self.pushButtonClear.setStyleSheet("background-color: rgb(255, 170, 255);")
        icon1 = QtGui.QIcon()
        icon1.addPixmap(QtGui.QPixmap("E:\\Elearning\\sakila_orm_gui\\UI\\../Images/ic_clear.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonClear.setIcon(icon1)
        self.pushButtonClear.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonClear.setObjectName("pushButtonClear")
        self.pushButtonInsert = QtWidgets.QPushButton(parent=self.groupBox_4)
        self.pushButtonInsert.setGeometry(QtCore.QRect(140, 20, 93, 41))
        self.pushButtonInsert.setStyleSheet("background-color: rgb(255, 170, 255);")
        icon2 = QtGui.QIcon()
        icon2.addPixmap(QtGui.QPixmap("E:\\Elearning\\sakila_orm_gui\\UI\\../Images/ic_save.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonInsert.setIcon(icon2)
        self.pushButtonInsert.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonInsert.setObjectName("pushButtonInsert")
        self.pushButtonUpdate = QtWidgets.QPushButton(parent=self.groupBox_4)
        self.pushButtonUpdate.setGeometry(QtCore.QRect(270, 20, 93, 41))
        self.pushButtonUpdate.setStyleSheet("background-color: rgb(255, 170, 255);")
        icon3 = QtGui.QIcon()
        icon3.addPixmap(QtGui.QPixmap("E:\\Elearning\\sakila_orm_gui\\UI\\../Images/ic_update.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonUpdate.setIcon(icon3)
        self.pushButtonUpdate.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonUpdate.setObjectName("pushButtonUpdate")
        self.pushButtonDelete = QtWidgets.QPushButton(parent=self.groupBox_4)
        self.pushButtonDelete.setGeometry(QtCore.QRect(390, 20, 93, 41))
        self.pushButtonDelete.setStyleSheet("background-color: rgb(255, 170, 255);")
        icon4 = QtGui.QIcon()
        icon4.addPixmap(QtGui.QPixmap("E:\\Elearning\\sakila_orm_gui\\UI\\../Images/ic_delete.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonDelete.setIcon(icon4)
        self.pushButtonDelete.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonDelete.setObjectName("pushButtonDelete")
        self.pushButtonExit = QtWidgets.QPushButton(parent=self.groupBox_4)
        self.pushButtonExit.setGeometry(QtCore.QRect(600, 20, 93, 41))
        self.pushButtonExit.setStyleSheet("background-color: rgb(255, 170, 255);")
        icon5 = QtGui.QIcon()
        icon5.addPixmap(QtGui.QPixmap("E:\\Elearning\\sakila_orm_gui\\UI\\../Images/ic_shutdown.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonExit.setIcon(icon5)
        self.pushButtonExit.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonExit.setObjectName("pushButtonExit")
        self.label_8 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_8.setGeometry(QtCore.QRect(210, 10, 381, 31))
        font = QtGui.QFont()
        font.setFamily("MS Shell Dlg 2")
        font.setPointSize(15)
        font.setBold(False)
        font.setItalic(False)
        font.setWeight(50)
        self.label_8.setFont(font)
        self.label_8.setStyleSheet("color: rgb(0, 0, 255);\n"
"font: 15pt \"MS Shell Dlg 2\";")
        self.label_8.setObjectName("label_8")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 727, 26))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)
        MainWindow.setTabOrder(self.tableWidgetCustomer, self.lineEditCustomerId)
        MainWindow.setTabOrder(self.lineEditCustomerId, self.lineEditFirstName)
        MainWindow.setTabOrder(self.lineEditFirstName, self.lineEditLastName)
        MainWindow.setTabOrder(self.lineEditLastName, self.lineEditEmail)
        MainWindow.setTabOrder(self.lineEditEmail, self.lineEditAddress)
        MainWindow.setTabOrder(self.lineEditAddress, self.checkBoxActive)
        MainWindow.setTabOrder(self.checkBoxActive, self.lineEditCreateDate)
        MainWindow.setTabOrder(self.lineEditCreateDate, self.lineEditLastUpdate)
        MainWindow.setTabOrder(self.lineEditLastUpdate, self.pushButtonClear)
        MainWindow.setTabOrder(self.pushButtonClear, self.pushButtonInsert)
        MainWindow.setTabOrder(self.pushButtonInsert, self.pushButtonUpdate)
        MainWindow.setTabOrder(self.pushButtonUpdate, self.pushButtonDelete)
        MainWindow.setTabOrder(self.pushButtonDelete, self.pushButtonExit)
        MainWindow.setTabOrder(self.pushButtonExit, self.tableWidgetRental)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Trần Duy Thanh - Sakila - ORM"))
        self.groupBox.setTitle(_translate("MainWindow", "List of Customers"))
        item = self.tableWidgetCustomer.horizontalHeaderItem(0)
        item.setText(_translate("MainWindow", "Customer ID"))
        item = self.tableWidgetCustomer.horizontalHeaderItem(1)
        item.setText(_translate("MainWindow", "First Name"))
        item = self.tableWidgetCustomer.horizontalHeaderItem(2)
        item.setText(_translate("MainWindow", "Last Name"))
        self.groupBox_2.setTitle(_translate("MainWindow", "List of Rental"))
        item = self.tableWidgetRental.horizontalHeaderItem(0)
        item.setText(_translate("MainWindow", "Rental ID"))
        item = self.tableWidgetRental.horizontalHeaderItem(1)
        item.setText(_translate("MainWindow", "Rental Date"))
        item = self.tableWidgetRental.horizontalHeaderItem(2)
        item.setText(_translate("MainWindow", "Inventory ID"))
        item = self.tableWidgetRental.horizontalHeaderItem(3)
        item.setText(_translate("MainWindow", "Customer ID"))
        item = self.tableWidgetRental.horizontalHeaderItem(4)
        item.setText(_translate("MainWindow", "Return Date"))
        item = self.tableWidgetRental.horizontalHeaderItem(5)
        item.setText(_translate("MainWindow", "Staff ID"))
        item = self.tableWidgetRental.horizontalHeaderItem(6)
        item.setText(_translate("MainWindow", "Last Update"))
        self.groupBox_3.setTitle(_translate("MainWindow", "Customer Details:"))
        self.label.setText(_translate("MainWindow", "Customer Id:"))
        self.label_2.setText(_translate("MainWindow", "First Name:"))
        self.label_3.setText(_translate("MainWindow", "Last Name:"))
        self.label_4.setText(_translate("MainWindow", "Email:"))
        self.label_5.setText(_translate("MainWindow", "Address:"))
        self.checkBoxActive.setText(_translate("MainWindow", "Is Active"))
        self.label_6.setText(_translate("MainWindow", "Create Date:"))
        self.label_7.setText(_translate("MainWindow", "Last Update:"))
        self.groupBox_4.setTitle(_translate("MainWindow", "Actions for Customer:"))
        self.pushButtonClear.setText(_translate("MainWindow", "Clear"))
        self.pushButtonInsert.setText(_translate("MainWindow", "Insert"))
        self.pushButtonUpdate.setText(_translate("MainWindow", "Update"))
        self.pushButtonDelete.setText(_translate("MainWindow", "Delete"))
        self.pushButtonExit.setText(_translate("MainWindow", "Exit"))
        self.label_8.setText(_translate("MainWindow", "Sakila  - ORM  Demonstration"))

Bước 5: Viết mã lệnh kế thừa để xử lý sự kiện tương tác người dùng cho giao diện ở trên, đặt tên “MainWindowEx.py“:

Bước 5.1: Tạo MainWindowEx kế thừa từ Ui_MainWindow được generate ra ở bước trước, và Khai báo các thư viện:

from datetime import datetime
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QTableWidgetItem, QMessageBox
from UI.MainWindow import Ui_MainWindow
import Classes.DatabaseConnection
from Classes.ClassMapping import Customer, Rental, Address

class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        pass

Các bạn quan sát các thư viện sử dụng ở trên, đặc biệt cần khai báo DatabaseConnection trước ClasssMapping. Vì cần kết nối Cơ sở dữ liệu trước khi tạo các mapping và mối quan hệ.

Bước 5.2: Khai báo hàm setupUi() để thiết lập giao diện cho phần mềm, đồng thời viết các signals và slots cho các Widget:

def setupUi(self, MainWindow):
    super().setupUi(MainWindow)
    self.MainWindow=MainWindow
    self.showAllCustomerOnQTableWidget()
    self.tableWidgetCustomer.itemSelectionChanged.connect(self.processItemSelection)
    self.pushButtonClear.clicked.connect(self.processClear)
    self.pushButtonInsert.clicked.connect(self.processInsert)
    self.pushButtonUpdate.clicked.connect(self.processUpdate)
    self.pushButtonDelete.clicked.connect(self.processDelete)
    self.pushButtonExit.clicked.connect(self.processExit)
def showWindow(self):
    self.MainWindow.show()

Bước 5.3: Viết hàm nạp toàn bộ Customer lên QTableWidget bằng hàm showAllCustomerOnQTableWidget(), khi khởi động phần mềm, chương trình sẽ dùng ORM để truy vấn và mapping hướng đối tượng Customer, sau đó chúng ta nạp lên giao diện:

def showAllCustomerOnQTableWidget(self):
    customers = Customer.all()
    self.tableWidgetCustomer.setRowCount(0)
    row = 0
    for cust in customers:
        row = self.tableWidgetCustomer.rowCount()
        self.tableWidgetCustomer.insertRow(row)
        self.tableWidgetCustomer.setItem(row, 0, QTableWidgetItem(str(cust.customer_id)))
        self.tableWidgetCustomer.setItem(row, 1, QTableWidgetItem(cust.first_name))
        self.tableWidgetCustomer.setItem(row, 2, QTableWidgetItem(cust.last_name))
        if cust.active==0:
            self.tableWidgetCustomer.item(row,0).setBackground(Qt.GlobalColor.red)
            self.tableWidgetCustomer.item(row, 1).setBackground(Qt.GlobalColor.red)
            self.tableWidgetCustomer.item(row, 2).setBackground(Qt.GlobalColor.red)

Hàm trên đọc danh sách customer bằng cách gọi hàm Customer.all()

sau đó dùng vòng lặp để nạp lên giao diện QTableWidget, và những Customer nào có active=0 thì tô nền đỏ.

Nếu sau khi hoàn thành phần mềm, ta chạy mã lệnh này lên ta có kết quả như giao diện dưới đây:

Quan sát giao diện trên ta thấy, Customer ID=16, First Name là SANDRA, LastName là MARTIN có tô nền đỏ vì Active=0.

Bước 5.4: Viết hàm xử lý sự kiện khi người dùng nhấn chọn Customer trong QTableWidget, chương trình sẽ thực hiện 2 nhiệm vụ:

  • Nhiệm vụ 1: Hiển thị chi tiết Customer và mục Customer Details ở bên phải màn hình
  • Nhiệm vụ 2: Hiển thị danh sách Rentals của Customer vào QTableWidget ở bên dưới màn hình
def processItemSelection(self):
    row = self.tableWidgetCustomer.currentRow()
    if row == -1:
        return
    customer_id=int(self.tableWidgetCustomer.item(row,0).text())
    #process for selected Customer details
    customer = Customer.find(customer_id)
    self.lineEditCustomerId.setText(str(customer.customer_id))
    self.lineEditFirstName.setText(customer.first_name)
    self.lineEditLastName.setText(customer.last_name)
    self.lineEditEmail.setText(customer.email)
    if customer.active==1:
        self.checkBoxActive.setChecked(True)
    else:
        self.checkBoxActive.setChecked(False)
    self.lineEditCreateDate.setText(str(customer.create_date))
    self.lineEditLastUpdate.setText(str(customer.last_update))
    #process for address table
    addresses=customer.addresses()
    address=addresses[0]
    self.lineEditAddress.setText(address.address)
    #process for rentals
    self.showAllRentalOnQTableWidget(customer)

Dưới đây là hàm showAllRentalOnQTableWidget(customer):

def showAllRentalOnQTableWidget(self,customer):
    rentals = customer.rentals()
    self.tableWidgetRental.setRowCount(0)
    row = 0
    for rental in rentals:
        row = self.tableWidgetRental.rowCount()
        self.tableWidgetRental.insertRow(row)
        self.tableWidgetRental.setItem(row, 0, QTableWidgetItem(str(rental.rental_id)))
        self.tableWidgetRental.setItem(row, 1, QTableWidgetItem(str(rental.rental_date)))
        self.tableWidgetRental.setItem(row, 2, QTableWidgetItem(str(rental.inventory_id)))
        self.tableWidgetRental.setItem(row, 3, QTableWidgetItem(str(rental.customer_id)))
        self.tableWidgetRental.setItem(row, 4, QTableWidgetItem(str(rental.return_date)))
        self.tableWidgetRental.setItem(row, 5, QTableWidgetItem(str(rental.staff_id)))
        self.tableWidgetRental.setItem(row, 6, QTableWidgetItem(str(rental.last_update)))

Chạy các mã lệnh này lên ta sẽ có giao diện như dưới đây:

Hình trên minh họa, người sử dụng chọn Customer có ID là 18, thì dữ liệu chi tiết của Customer sẽ được hiển thị ở mục Customer Details, đồng thời danh sách Rentals của Customer này cũng được hiển thị vào mục List of Rental QTableWidget ở bên dưới màn hình.

Bước 5.5: Viết hàm xử lý sự kiện khi nhấn vào nút “Clear”

def processClear(self):
    self.lineEditCustomerId.setText("")
    self.lineEditFirstName.setText("")
    self.lineEditLastName.setText("")
    self.lineEditEmail.setText("")
    self.checkBoxActive.setChecked(False)
    self.lineEditCreateDate.setText("")
    self.lineEditLastUpdate.setText("")
    self.lineEditAddress.setText("")
    self.lineEditCustomerId.setFocus()

Dữ liệu trong phần Customer details sẽ bị xóa trống, và người dùng có thể nhập dữ liệu mới.

Bước 5.6: Viết hàm xử lý sự kiện khi nhấn vào nút “Insert

def processInsert(self):
    # Insert new Student Object:
    new_customer = Customer()
    new_customer.first_name=self.lineEditFirstName.text()
    new_customer.last_name = self.lineEditLastName.text()
    new_customer.email = self.lineEditEmail.text()
    if self.checkBoxActive.isChecked():
        new_customer.active=1
    else:
        new_customer.active = 0
    new_customer.store_id=1
    new_customer.address_id=1
    new_customer.save()
    self.lineEditCustomerId.setText(str(new_customer.customer_id))
    self.showAllCustomerOnQTableWidget()

Sau khi người dùng nhập liệu và nhấn nút “Insert” thì dữ liệu Customer được lưu thành công và hiển thị lại lên giao diện.

Coding ở trên vì Sakila design store_id và address_id là not null, nên Tui tạm thời để là 1. Dưới đây là minh họa chức năng INSERT khi chạy phần mềm:

Vì Customer mới nên Rentals không có do đó QTableWidget List of Rental rỗng.

Và bạn cũng thấy Last Update đang None vì mới Insert chưa có Update.

Bước 5.7: Viết hàm xử lý sự kiện khi nhấn vào nút “Update

def processUpdate(self):
    customer_id = int(self.lineEditCustomerId.text())
    # process for selected Customer details
    customer = Customer.find(customer_id)
    fname = self.lineEditFirstName.text()
    lname = self.lineEditLastName.text()
    email = self.lineEditEmail.text()
    if self.checkBoxActive.isChecked():
        active = 1
    else:
        active = 0
    customer.update(first_name=fname,last_name=lname,email=email,active=active,last_update=datetime.now())
    self.showAllCustomerOnQTableWidget()

Mã lệnh ở trên sẽ cập nhật dữ liệu Customer theo ORM. Khi thực hiện thành công chương trình sẽ cập nhật lại giao diện. Kết quả minh họa:

Ta thấy nếu dùng chức năng Update, thì Last Update sẽ có giá trị.

Bước 5.8: Viết hàm xử lý sự kiện khi nhấn vào nút “Delete“, chương trình sẽ xóa Customer theo primary customer_id:

def processDelete(self):
    customer_id = int(self.lineEditCustomerId.text())
    # process for selected Customer details
    customer = Customer.find(customer_id)
    dlg = QMessageBox(self.MainWindow)
    dlg.setWindowTitle("Confirmation Deleting")
    dlg.setIcon(QMessageBox.Icon.Critical)
    dlg.setText("Are you sure you want to delete?")
    buttons = QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
    dlg.setStandardButtons(buttons)
    button = dlg.exec()
    if button == QMessageBox.StandardButton.Yes:
        customer.destroy()
        self.processClear()
        self.showAllCustomerOnQTableWidget()

Chương trình yêu cầu xác nhận có muốn xóa hay không, nếu đồng ý thì sẽ xóa.

mã lệnh ở trên ta thực hiện:

  • Xóa Customer hiện tại đang chọn khỏi cơ sở dữ liệu
  • Xóa trống các dữ liệu của Customer vừa xóa ra khỏi giao diện Details
  • Nạp lại dánh sách Customer cho QTableWidget phần List Customers

Chạy thử nghiệm ta có:

  • Trường hợp 1: Nếu xóa Customer đã có Rental, chương trình sẽ báo lỗi, vì chúng ta cần xóa hết Rentals của Customer này đã
  • Trường hợp 2: Xóa Customer vừa thêm vào, sẽ thành công

Các bạn tự xử lý thêm mã lệnh

Cuối cùng ta vào chức năng thoát phần mềm:

Bước 5.9: Viết hàm xử lý sự kiện thoát phần mềm:

def processExit(self):
    dlg = QMessageBox(self.MainWindow)
    dlg.setWindowTitle("Confirmation Exit")
    dlg.setText("Are you sure you want to Exit?")
    dlg.setIcon(QMessageBox.Icon.Question)
    buttons = QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
    dlg.setStandardButtons(buttons)
    button = dlg.exec()
    if button == QMessageBox.StandardButton.Yes:
       exit()

Chương trình sẽ xác nhận người dùng muốn thoát hay không, nếu đồng ý sẽ thoát:

Dưới đây là mã lệnh tổng hợp của MainWindowEx.py:

from datetime import datetime
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QTableWidgetItem, QMessageBox
from UI.MainWindow import Ui_MainWindow
import Classes.DatabaseConnection
from Classes.ClassMapping import Customer, Rental, Address

class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        pass
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.showAllCustomerOnQTableWidget()
        self.tableWidgetCustomer.itemSelectionChanged.connect(self.processItemSelection)
        self.pushButtonClear.clicked.connect(self.processClear)
        self.pushButtonInsert.clicked.connect(self.processInsert)
        self.pushButtonUpdate.clicked.connect(self.processUpdate)
        self.pushButtonDelete.clicked.connect(self.processDelete)
        self.pushButtonExit.clicked.connect(self.processExit)
    def showWindow(self):
        self.MainWindow.show()
    def showAllCustomerOnQTableWidget(self):
        customers = Customer.all()
        self.tableWidgetCustomer.setRowCount(0)
        row = 0
        for cust in customers:
            row = self.tableWidgetCustomer.rowCount()
            self.tableWidgetCustomer.insertRow(row)
            self.tableWidgetCustomer.setItem(row, 0, QTableWidgetItem(str(cust.customer_id)))
            self.tableWidgetCustomer.setItem(row, 1, QTableWidgetItem(cust.first_name))
            self.tableWidgetCustomer.setItem(row, 2, QTableWidgetItem(cust.last_name))
            if cust.active==0:
                self.tableWidgetCustomer.item(row,0).setBackground(Qt.GlobalColor.red)
                self.tableWidgetCustomer.item(row, 1).setBackground(Qt.GlobalColor.red)
                self.tableWidgetCustomer.item(row, 2).setBackground(Qt.GlobalColor.red)
    def processItemSelection(self):
        row = self.tableWidgetCustomer.currentRow()
        if row == -1:
            return
        customer_id=int(self.tableWidgetCustomer.item(row,0).text())
        #process for selected Customer details
        customer = Customer.find(customer_id)
        self.lineEditCustomerId.setText(str(customer.customer_id))
        self.lineEditFirstName.setText(customer.first_name)
        self.lineEditLastName.setText(customer.last_name)
        self.lineEditEmail.setText(customer.email)
        if customer.active==1:
            self.checkBoxActive.setChecked(True)
        else:
            self.checkBoxActive.setChecked(False)
        self.lineEditCreateDate.setText(str(customer.create_date))
        self.lineEditLastUpdate.setText(str(customer.last_update))
        #process for address table
        addresses=customer.addresses()
        address=addresses[0]
        self.lineEditAddress.setText(address.address)
        #process for rentals
        self.showAllRentalOnQTableWidget(customer)
    def showAllRentalOnQTableWidget(self,customer):
        rentals = customer.rentals()
        self.tableWidgetRental.setRowCount(0)
        row = 0
        for rental in rentals:
            row = self.tableWidgetRental.rowCount()
            self.tableWidgetRental.insertRow(row)
            self.tableWidgetRental.setItem(row, 0, QTableWidgetItem(str(rental.rental_id)))
            self.tableWidgetRental.setItem(row, 1, QTableWidgetItem(str(rental.rental_date)))
            self.tableWidgetRental.setItem(row, 2, QTableWidgetItem(str(rental.inventory_id)))
            self.tableWidgetRental.setItem(row, 3, QTableWidgetItem(str(rental.customer_id)))
            self.tableWidgetRental.setItem(row, 4, QTableWidgetItem(str(rental.return_date)))
            self.tableWidgetRental.setItem(row, 5, QTableWidgetItem(str(rental.staff_id)))
            self.tableWidgetRental.setItem(row, 6, QTableWidgetItem(str(rental.last_update)))
    def processClear(self):
        self.lineEditCustomerId.setText("")
        self.lineEditFirstName.setText("")
        self.lineEditLastName.setText("")
        self.lineEditEmail.setText("")
        self.checkBoxActive.setChecked(False)
        self.lineEditCreateDate.setText("")
        self.lineEditLastUpdate.setText("")
        self.lineEditAddress.setText("")
        self.lineEditCustomerId.setFocus()

    def processInsert(self):
        # Insert new Student Object:
        new_customer = Customer()
        new_customer.first_name=self.lineEditFirstName.text()
        new_customer.last_name = self.lineEditLastName.text()
        new_customer.email = self.lineEditEmail.text()
        if self.checkBoxActive.isChecked():
            new_customer.active=1
        else:
            new_customer.active = 0
        new_customer.store_id=1
        new_customer.address_id=1
        new_customer.save()
        self.lineEditCustomerId.setText(str(new_customer.customer_id))
        self.showAllCustomerOnQTableWidget()
    def processUpdate(self):
        customer_id = int(self.lineEditCustomerId.text())
        # process for selected Customer details
        customer = Customer.find(customer_id)
        fname = self.lineEditFirstName.text()
        lname = self.lineEditLastName.text()
        email = self.lineEditEmail.text()
        if self.checkBoxActive.isChecked():
            active = 1
        else:
            active = 0
        customer.update(first_name=fname,last_name=lname,email=email,active=active,last_update=datetime.now())
        self.showAllCustomerOnQTableWidget()
    def processDelete(self):
        customer_id = int(self.lineEditCustomerId.text())
        # process for selected Customer details
        customer = Customer.find(customer_id)
        dlg = QMessageBox(self.MainWindow)
        dlg.setWindowTitle("Confirmation Deleting")
        dlg.setIcon(QMessageBox.Icon.Critical)
        dlg.setText("Are you sure you want to delete?")
        buttons = QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
        dlg.setStandardButtons(buttons)
        button = dlg.exec()
        if button == QMessageBox.StandardButton.Yes:
            customer.destroy()
            self.processClear()
            self.showAllCustomerOnQTableWidget()
    def processExit(self):
        dlg = QMessageBox(self.MainWindow)
        dlg.setWindowTitle("Confirmation Exit")
        dlg.setText("Are you sure you want to Exit?")
        dlg.setIcon(QMessageBox.Icon.Question)
        buttons = QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
        dlg.setStandardButtons(buttons)
        button = dlg.exec()
        if button == QMessageBox.StandardButton.Yes:
           exit()

Bước 6: Cuối cùng ta khai báo MyApp.py để thực thi chương trình

from PyQt6.QtWidgets import QApplication, QMainWindow

from UI.MainWindowEx import MainWindowEx

qApplication=QApplication([])
qMainWindow=QMainWindow()
myWindow=MainWindowEx()
myWindow.setupUi(qMainWindow)
myWindow.showWindow()
qApplication.exec()

Thực thi mã lệnh trên, ta có phần mềm như mong muốn:

Như vậy, Tui đã hướng dẫn xong chi tiết cách sử dụng kỹ thuật ORM để lập trình xử lý giao diện tương tác người dùng cho cơ sở dữ liệu Sakila, làm việc trên 3 bảng có mối quan hệ: Customer, Address, Rental.

Đã minh họa đầy đủ các chức năng CRUD tương ứng với kỹ thuật ORM, các bạn chú ý làm lại nhiều lần để hiểu hơn về dự án.

Source code full của dự án các bạn tải ở đây:

https://www.mediafire.com/file/bntg4ywrmyjw02d/sakila_orm_gui.rar/file

Chúc các bạn thành công

Bài 51: Kỹ thuật ORM trong Python với Cơ sở dữ liệu MySQL Server (p2)

Trong bài 50, chúng ta đã biết cách sử dụng kỹ thuật lập trình ORM để tương tác cơ sở dữ liệu MySQL Server bằng Python. Tuy nhiên chúng ta chưa đề cập tới vấn đề cả bảng dữ liệu có mối quan hệ dạng Master-Details.

Ví dụ như trong Quản lý Sản phẩm ta có Danh mục, ta có sản phẩm. 1 Danh mục có nhiều sản phẩm, và 1 sản phẩm thuộc về 1 danh mục. Vậy với trường hợp có relationship master-details như thế này thì chúng ta lập trình ORM ra sao? và trong thực tế chúng ta luôn luôn gặp các relationship master-details này.

Hay ngay trong quá trình cài đặt MySQL Server, chúng ta cũng được cung cấp một cơ sở dữ liệu mẫu “sakila“, là một cơ sở dữ liệu liên quan tới quản lý cho mượn Film, và cũng có nhiều relationship Master-Details, chúng ta sẽ lập trình ORM trên cơ sở dữ liệu mẫu này luôn (ta làm trên bảng Customer và bảng Rental):

Ta xem Relationship giữa bảng Customer và bảng Rental:

  • Một Customer có nhiều Rental (dựa vào khóa ngoại customer_id trong bảng rental, khi có 1 Customer ta sẽ biết được Customer này có bao nhiêu Rental)
  • Một Rental thuộc về một Customer (từ bảng rental ta có thuộc tính customer_id để suy ngược ra thông tin chi tiết Customer của Rental)

Đây chính là Relationship 1-many.

Ta tạo dự án “sakila_orm” trong Pycharm có cấu trúc như dưới đây:

Trong dự án này Tui chủ ý tách ra làm 3 file mã lệnh Python độc lập để có thể dễ dàng tái sử dụng:

  • File “DatabaseConnection.py“: Là file mã lệnh để kế nối Cơ sở dữ liệu, nó phải được thực hiện trước các câu lệnh khác trong dự án
  • File “ClassMapping.py“: Là file mã lệnh khai báo class mapping và relationship
  • File “TestRelationShipORM.py“: Là file mã lệnh để thử nghiệm kỹ thuật ORM với các bảng có Relationship

Bước 1:

Bây giờ ta xem mã lệnh của DatabaseConnection.py:

from orm import Table

#configuration JSON for connection MySQL
CONFIG={
    'host': 'localhost',
    'port': 3306,
    'user': 'root',
    'password': '@Obama123',
    'database': 'sakila'
}
#connect to database from JSON configuration
Table.connect(config_dict=CONFIG)

Mã lệnh kết nối cơ sở dữ liệu được tách độc lập ra 1 file khác, khi có sự thay đổi về thông tin kết nối thì bạn chỉ cần vào file này chỉnh sửa sẽ không bị rối.

Bước 2:

Tiếp theo là mã lệnh File “ClasssMapping.py“:

Dựa vào cấu trúc bảng Customer và Rental, Ta có 1 Customer có nhiều Rental, nên ở đây trong lớp Customer ta dùng hàm has_many. Còn đối với lớp Rental thì ứng với 1 đối tượng Rental ta biết được Rental này của Customer nào nên ta dùng hàm belongs_to:

from orm import Table, has_many, belongs_to

class Customer(Table):
    table_name = 'customer'

    relations = [
        has_many(name='rentals', _class='Rental', foreign_key='customer_id')
    ]
class Rental(Table):
    table_name = 'rental'

    relations = [
        belongs_to(name='customer', _class='Customer', foreign_key='customer_id',primary_key="customer_id")
    ]

hàm has_many:

Hàm has_many cho biết đối tượng này chứa nhiều đối tượng khác.

+ name=’rentals’: Đây là phương thức rentals() khi gọi phương thức này nó sẽ trả về danh sách đối tượng có kiểu Rental được khai báo trong _class

+_class: Đây là tham số định nghĩa kiểu dữ liệu đối tượng trả về cho hàm “rentals”

+foreign_key=’customer_id’: Cho biết relationship giữa Customer và Rental, khi truy vấn thì dựa vào thuộc tính này chương trình sẽ lọc ra danh sách các Rental theo foreign_key

hàm belongs_to:

hàm belongs_to cho biết đối tượng này thuộc về đối tượng khác

+name=’customer’: Là là phương thức customer() trả về đối tượng Customer của Rental đang xét. Kiểu dữ liệu trả về được khai báo trong _class là ‘Customer’

+_class=’Customer’: Đầy là tham số định nghĩa kiểu dữ liệu đối tượng trả về cho hàm customer()

+foreign_key=’customer_id’: Cho biết relationship giữa Customer và Rental, khi truy vấn thì dựa vào thuộc tính này thì chương trình sẽ cho biết mã tham chiếu tới customer hiện tại của Rental là customer_id

+primary_key=’customer_id’: Khi gọi hàm customer() sẽ truy vấn ngược lại bảng Customer dựa vào primary_key customer_id này.

Bước 3:

Cuối cùng ta viết mã lệnh cho file “TestRelationshipORM.py“:

Đâu tiên chúng ta cần import DatabaseConnection, sau đó tới MappingClass:

import DatabaseConnection
from ClassMapping import Customer, Rental

Lưu ý cần theo đúng trình tự Import ở trên, vì chương trình cần kết nối cơ sở dữ liệu trước sau đó mới tới Mapping các Class cho Table.

Tiếp theo, để truy vấn toàn bộ Customer, ta viết nối tiếp mã lệnh như sau:

#Get all Customer
customers = Customer.all()
align='{0:<6} {1:<10} {2:<10} {3:<10}'
print(align.format('Id', 'First Name','Last Name',"Email"))
for cust in customers:
    print(align.format(cust.customer_id,cust.first_name,cust.last_name,cust.email))

Chạy mã lệnh trên ta có kết quả:

Để truy vấn lấy thông tin chi tiết của Customer khi biết Primary Key:

#Query 1 Customer by primary key customer_id=1:
customer = Customer.find(1)
print(f"Id={customer.customer_id}")
print(f"First Name={customer.first_name}")
print(f"Last Name={customer.last_name}")
print(f"Email={customer.email}")

Chạy mã lệnh trên ta có kết quả:

Để truy vấn lấy danh sách Rental của Customer này ta viết lệnh:

#Get all rentals for this customer:
rentals=customer.rentals()
align='{0:<10} {1:<20} {2:<20} {3:<10} {4:<10}'
print(align.format('Rental Id', 'Rental Date','Return Date',"Staff Id","Customer Id"))
for rental in rentals:
    print(align.format(
        rental.rental_id,
        str(rental.rental_date),
        str(rental.return_date),
        rental.staff_id,
        rental.customer_id))

Thực thi mã lệnh ORM ở trên ta có kết quả danh sách Rentals của Customer như dưới đây:

Để truy vấn lấy thông tin chi tiết của Rental khi biết primary key rental_id ta làm như sau:

#Query Rental detail by primary key rental_id=2
rental=Rental.find(2)
print(align.format('Rental Id', 'Rental Date','Return Date',"Staff Id","Customer Id"))
print(align.format(
        rental.rental_id,
        str(rental.rental_date),
        str(rental.return_date),
        rental.staff_id,
        rental.customer_id))

Mã lệnh ở trên là truy vấn thông tin chi tiết của Rental khi biết rental_id=2

Thực thi lệnh trên ta có kết quả:

Bây giờ, để in thông tin chi tiết Customer của Rental này ta viết mã lệnh:

# Get Customer of this Rental:
customer=rental.customer()
print(f"Id={customer.customer_id}")
print(f"First Name={customer.first_name}")
print(f"Last Name={customer.last_name}")
print(f"Email={customer.email}")

Thực thi lệnh trên ta có kết quả:

Như vậy Tui đã minh họa xong kỹ thuật ORM để xử lý các bảng có Relationship. Đây là bài học rất quan trọng và cần thiết vì nó minh họa trường hợp trong thực tế mà ta thường gặp, hầu hết các bảng dữ liệu đều có các mối quan hệ này.

Dưới đây là mã lệnh đầy đủ của TestRelationshipORM.py:

import DatabaseConnection
from ClassMapping import Customer, Rental

#Get all Customer
customers = Customer.all()
align='{0:<6} {1:<10} {2:<10} {3:<10}'
print(align.format('Id', 'First Name','Last Name',"Email"))
for cust in customers:
    print(align.format(cust.customer_id,cust.first_name,cust.last_name,cust.email))

#Query 1 Customer by primary key customer_id=1:
customer = Customer.find(1)
print(f"Id={customer.customer_id}")
print(f"First Name={customer.first_name}")
print(f"Last Name={customer.last_name}")
print(f"Email={customer.email}")

#Get all rentals for this customer:
rentals=customer.rentals()
align='{0:<10} {1:<20} {2:<20} {3:<10} {4:<10}'
print(align.format('Rental Id', 'Rental Date','Return Date',"Staff Id","Customer Id"))
for rental in rentals:
    print(align.format(
        rental.rental_id,
        str(rental.rental_date),
        str(rental.return_date),
        rental.staff_id,
        rental.customer_id))

#Query Rental detail by primary key rental_id=2
rental=Rental.find(2)
print(align.format('Rental Id', 'Rental Date','Return Date',"Staff Id","Customer Id"))
print(align.format(
        rental.rental_id,
        str(rental.rental_date),
        str(rental.return_date),
        rental.staff_id,
        rental.customer_id))

# Get Customer of this Rental:
customer=rental.customer()
print(f"Id={customer.customer_id}")
print(f"First Name={customer.first_name}")
print(f"Last Name={customer.last_name}")
print(f"Email={customer.email}")

Ngoài ra bạn có thể tải mã nguồn của toàn bộ dự án ở đây:

https://www.mediafire.com/file/v67quhs1j3srmnz/sakila_orm.rar/file

Các chức năng ORM khác như: Thêm, sửa, Xóa thì các bạn tự xử lý theo bài 50 đã được học.

Bài học sau Tui sẽ minh họa ORM trên giao diện tương tác người dùng PyQt6 – Qt Designer.

Chúc các bạn thành công

Bài 50: Kỹ thuật ORM trong Python với Cơ sở dữ liệu MySQL Server

Bài 48bài 49 các bạn đã lập trình thành thạo Python với MySQL Server, đã thực hiện đầy đủ các chức năng CRUD liên quan tới phần mềm quản lý.

Tuy nhiên, các cách lập trình ấy có một vài khuyết điểm như: Tốn thời gian viết các mã lệnh tương tác cơ sở dữ liệu, tốn thời gian mô hình hóa hướng đối tượng.

Các kỹ sư phần mềm họ đã phát triển một kỹ thuật ORM rất lợi hại để mô hình hóa đối tượng với các bảng dữ liệu một cách nhanh chóng.

ORM (Object Relational Mapping) là kỹ thuật trong lập trình phần mềm giúp ánh xạ dữ liệu từ cơ sở dữ liệu quan hệ vào các đối tượng trong mã nguồn. Điều này giúp giảm sự phụ thuộc vào cấu trúc của cơ sở dữ liệu, đồng thời tạo ra một cách tiếp cận linh hoạt hơn trong việc quản lý dữ liệu:

Tóm tắt một số Ưu điểm chính của ORM (hình nguồn medium):

  1. ORM giúp người lập trình tập trung hơn vào việc lập trình hướng đối tượng
  2. Cho phép truy cập vào code nghiệp vụ thay vì database
  3. Hạn chế những lỗi ngữ pháp trong SQL
  4. Quản lý Transaction và tạo key tự động
  5. Đơn giản và dễ sử dụng
  6. Ẩn chi tiết của những truy vấn SQL từ Object Oriented logic (OO-logic)
  7. Đem lại năng suất cao hơn cho lập trình viên
  8. Nâng cao tính độc lập
  9. Năng suất hơn nhờ việc viết code ít hơn
  10. Cho phép lập trình viên sử dụng lại code
  11. ORM Framework cho phép truy xuất nhanh hơn bằng cách cache dữ liệu
  12. Tự động thực hiện những thao tác với dữ liệu

Python cũng giống như các công ngữ lập trình C#, Java, nó cũng được cung cấp các thư viện khác nhau để thực hiện ORM:

Thông qua thư viện Python thì cơ sở dữ liệu sẽ được mapping hướng đối tượng theo nguyên tắc(hình nguồn medium) :

  • Tên bảng <-> Tên lớp
  • Các cột trong bảng<-> Thuộc tính của lớp
  • Từng dòng dữ liệu trong bảng <-> Các đối tượng của lớp

Ví dụ: Giả sử ta có bảng Person dưới đây, bảng này có 4 cột ID (Auto Increment), First_Name, Last_Name và Phone. Khi ORM ta có:

Tạo mới đối tượng ta không cần ID vì ID auto increment và tự động phát sinh giá trị khi chúng ta lưu thành công, khi truy vấn thì nó tự động có ID có giá trị được mapping lên.

Chú ý là bài học này ta vẫn sử dụng “Student Management” đã tạo trước đó. Nên để học tốt bài này thì các bạn cần học bài 47, bài 48bài 49 trước.

Chuỗi bài học này sẽ hướng dẫn các bạn sử dụng orm-mysql

Bước 0: Chuẩn bị cơ sở dữ liệu Student Management ở bài học trước:

Bước 1: cài đặt orm-mysql

pip install orm-mysql

Mở command line và chạy lệnh trên, ta có kết quả:

Bước 2: Tạo dự án “LearnMySQLORM” trong Pycharm, trong dự án này tạo file “TestORM.py” và tiến hành viết mã lệnh theo các bước sau:

Bước 3: Tạo file cấu hình kết nối cơ sở dữ liệu và tiến hành kết nối

from orm import Table, get_table

#configuration JSON for connection MySQL
CONFIG={
    'host': 'localhost',
    'port': 3306,
    'user': 'root',
    'password': '@Obama123',
    'database': 'studentmanagement'
}
#connect to database from JSON configuration
Table.connect(config_dict=CONFIG)

Ở trên ta dùng phương thức connect của Table để tiến hành kết nối cơ sở dữ liệu

Bước 4: Kết nối và mapping bảng dữ liệu, ví dụ ta muốn ORM cho bảng student:

#mapping student table
Student = get_table('student')

Bước 5: Truy vấn toàn bộ dữ liệu Sinh viên

#Query all students:
students = Student.all()
align='{0:<3} {1:<6} {2:<18} {3:<10}'
print(align.format('ID', 'Code','Name',"Age"))
for stu in students:
    print(align.format(stu.ID,stu.Code,stu.Name,stu.Age))

lệnh trên ta gọi hàm get_table(“student”), hàm này giúp ta mapping bảng student vào lớp đối tượng Student.

phương thức all() dùng để truy vấn toàn bộ dữ liệu Sinh viên

Kết quả thi khi thực hiện lệnh truy vấn toàn bộ Sinh viên:

Bước 6: Để tìm Student theo ID (tìm chi tiết, ta thường sử dụng)

#Find student detail by ID (eg: 2)
student = Student.find(2)
print("Id=",student.ID)
print("code=",student.Code)
print("name=",student.Name)
print("age=",student.Age)

Chạy lệnh trên ta có kết quả là chi tiết của Sinh viên có ID=2 được xuất ra màn hình:

Bước 7: Để thêm mới một Student:

#Insert new Student Object:
new_student = Student(Code="SV300",Name='Đông Tà',Age=25)

new_student.save()

Khi chạy mã lệnh trên, chương trình sẽ tự động thực hiện câu truy vấn:

SQL: INSERT INTO student (ID, Code, Name, Age, Avatar, Intro) VALUES (NULL, 'SV300', 'Đông Tà', 25, NULL, NULL); 

cột nào không có giá trị nó sẽ tự động có giá trị NULL

Thực hiện lệnh trên thành công thì MySQL Server sẽ có dữ liệu mới với thông tin được cung cấp ở trên.

Bước 8: Để chỉnh sử dữ liệu cho đối tượng Student:

#Update student detail by ID 3
student = Student.find(3)
student.update(Name="Nguyễn Thị Lung Linh",Age=23)

Coding trên minh họa việc chỉnh tên và tuổi của Student có mã Id=3

Chạy lệnh trên ta có kết quả:

Bước 9: Để xóa Student ta viết:

#Remove student  by ID 15
student = Student.find(15)
student.destroy()

Chạy mã lệnh, ta không còn thấy Sinh viên Đông Tà có mã 15 nữa.

Toàn bộ Coding ORM cho chức năng CRUD Tui tổng hợp ở đây:

from orm import Table, get_table

#configuration JSON for connection MySQL
CONFIG={
    'host': 'localhost',
    'port': 3306,
    'user': 'root',
    'password': '@Obama123',
    'database': 'studentmanagement'
}
#connect to database from JSON configuration
Table.connect(config_dict=CONFIG)
#mapping student table
Student = get_table('student')
#Query all students:
students = Student.all()
align='{0:<3} {1:<6} {2:<18} {3:<10}'
print(align.format('ID', 'Code','Name',"Age"))
for stu in students:
    print(align.format(stu.ID,stu.Code,stu.Name,stu.Age))
#Find student detail by ID (eg: 2)
student = Student.find(2)
print("Id=",student.ID)
print("code=",student.Code)
print("name=",student.Name)
print("age=",student.Age)

#Insert new Student Object:
new_student = Student(Code="SV300",Name='Đông Tà',Age=25)

new_student.save()

#Update student detail by ID 3
student = Student.find(3)
student.update(Name="Nguyễn Thị Lung Linh",Age=23)

#Remove student  by ID 15
student = Student.find(15)
student.destroy()

Như vậy tới đây Tui đã trình bày xong ORM liên quan tới 1 bảng dữ liệu, đủ các chức năng CRUD. Các bạn chú ý thực hành nhiều lần để hiểu về nó, thành thạo về ORM.

Bài học sau Tui sẽ tiếp tục hướng dẫn ORM , tuy nhiên nâng cao hơn ở chỗ thực hiện ORM với các bảng có mối quan hệ, ví dụ Sinh viên – Môn học. Các bạn chú ý theo dõi, đây là các bài học quan trọng và thực tế

Các bạn tải mã nguồn đầy đủ của bài này ở đây:

https://www.mediafire.com/file/s5qfcwke9bikdki/LearnMySQLORM.rar/file

Chúc các bạn thành công

Bài 49: Tương tác Python với Cơ sở dữ liệu MySQL Server -Quản lý Sinh viên

Trong bài 48 các bạn đã biết cách sử dụng ngôn ngữ lập trình Python để tương tác cơ sở dữ liệu MySQL Server “studentmanagement”, Các bạn đã lập trình nhuần nhuyễn CRUD để thêm mới dữ liệu, truy vấn dữ liệu, sắp xếp dữ liệu, cập nhật dữ liệu, xóa dữ liệu, phân trang dữ liệu….

Bài học này chúng ta sẽ ứng dụng để thiết kế giao diện tương tác người dùng, giao diện như dưới đây:

Phần mềm sử dụng cơ sở dữ liệu và các mã lệnh đã học ở các bài trước.

Phần mềm gồm có các chức năng sau:

(1) Tải toàn bộ dữ liệu từ bảng student lên QTableWidget

(2) “New”: Xóa dữ liệu trong các Widget để cho nhập mới dữ liệu

(3) “Insert”: Thêm mới student vào cơ sở dữ liệu MySQL

(4) “Update”: Chỉnh sữa dữ liệu student

(5) “Remove”: Xóa Student khỏi cơ sở dữ liệu MySQL

(6) “Avatar”/”Remove Avatar”: Chọn và xóa Avatar. Hình Ảnh sẽ được mã hóa thành base64string

(7) Xem chi tiết student: Nhấn chọn student trong QTableWidget, thông tin chi tiết của Student sẽ hiển thị vào các Widget cơ bản

Ta đi vào chi tiết:

Bước 1: Tạo cấu trúc thư mục và tập tin cho dự án “StudentManagement” như dưới đây:

  • images: Thư mục này lưu các hình ảnh, icon của phần mềm, tùy bạn lựa chọn
  • MainWindow.ui: Tập tin giao diện phần mềm
  • MainWindow.py: Tập tin python gencode của giao diện
  • MainWindowEx.py: Tập tin kết thừa lớp giao diện được tạo ra từ MainWindow.py
  • MyApp.py: Tập tin thực thi phần mềm

Bước 2: Bạn thiết kế giao diện như hình dưới đây:

Thiết kế giao diện, đặt tên các Widget như hình trên.

Bước 3: Sau đó Gencode “MainWindow.py”:

# Form implementation generated from reading ui file 'E:\Elearning\StudentManagement\MainWindow.ui'
#
# Created by: PyQt6 UI code generator 6.7.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt6 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(456, 552)
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap("E:\\Elearning\\StudentManagement\\images/ic_logo.jpg"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        MainWindow.setWindowIcon(icon)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.label = QtWidgets.QLabel(parent=self.centralwidget)
        self.label.setGeometry(QtCore.QRect(20, 0, 371, 41))
        font = QtGui.QFont()
        font.setPointSize(12)
        font.setBold(True)
        font.setWeight(75)
        self.label.setFont(font)
        self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
        self.label.setObjectName("label")
        self.groupBox = QtWidgets.QGroupBox(parent=self.centralwidget)
        self.groupBox.setGeometry(QtCore.QRect(20, 450, 411, 61))
        self.groupBox.setStyleSheet("background-color: rgb(236, 255, 199);")
        self.groupBox.setObjectName("groupBox")
        self.pushButtonRemove = QtWidgets.QPushButton(parent=self.groupBox)
        self.pushButtonRemove.setGeometry(QtCore.QRect(300, 20, 91, 28))
        self.pushButtonRemove.setStyleSheet("background-color: rgb(255, 170, 255);")
        icon1 = QtGui.QIcon()
        icon1.addPixmap(QtGui.QPixmap("E:\\Elearning\\StudentManagement\\images/ic_delete.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonRemove.setIcon(icon1)
        self.pushButtonRemove.setIconSize(QtCore.QSize(24, 24))
        self.pushButtonRemove.setObjectName("pushButtonRemove")
        self.pushButtonInsert = QtWidgets.QPushButton(parent=self.groupBox)
        self.pushButtonInsert.setGeometry(QtCore.QRect(100, 20, 81, 28))
        self.pushButtonInsert.setStyleSheet("background-color: rgb(255, 170, 255);")
        icon2 = QtGui.QIcon()
        icon2.addPixmap(QtGui.QPixmap("E:\\Elearning\\StudentManagement\\images/ic_save.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonInsert.setIcon(icon2)
        self.pushButtonInsert.setIconSize(QtCore.QSize(24, 24))
        self.pushButtonInsert.setObjectName("pushButtonInsert")
        self.pushButtonNew = QtWidgets.QPushButton(parent=self.groupBox)
        self.pushButtonNew.setGeometry(QtCore.QRect(10, 20, 71, 28))
        self.pushButtonNew.setStyleSheet("background-color: rgb(255, 170, 255);")
        icon3 = QtGui.QIcon()
        icon3.addPixmap(QtGui.QPixmap("E:\\Elearning\\StudentManagement\\images/ic_new.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonNew.setIcon(icon3)
        self.pushButtonNew.setIconSize(QtCore.QSize(24, 24))
        self.pushButtonNew.setObjectName("pushButtonNew")
        self.pushButtonUpdate = QtWidgets.QPushButton(parent=self.groupBox)
        self.pushButtonUpdate.setGeometry(QtCore.QRect(200, 20, 81, 28))
        self.pushButtonUpdate.setStyleSheet("background-color: rgb(255, 170, 255);")
        icon4 = QtGui.QIcon()
        icon4.addPixmap(QtGui.QPixmap("E:\\Elearning\\StudentManagement\\images/ic_update.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonUpdate.setIcon(icon4)
        self.pushButtonUpdate.setIconSize(QtCore.QSize(24, 24))
        self.pushButtonUpdate.setObjectName("pushButtonUpdate")
        self.groupBox_2 = QtWidgets.QGroupBox(parent=self.centralwidget)
        self.groupBox_2.setGeometry(QtCore.QRect(20, 240, 411, 201))
        self.groupBox_2.setStyleSheet("background-color: rgb(255, 201, 243);")
        self.groupBox_2.setObjectName("groupBox_2")
        self.label_4 = QtWidgets.QLabel(parent=self.groupBox_2)
        self.label_4.setGeometry(QtCore.QRect(20, 110, 41, 16))
        self.label_4.setObjectName("label_4")
        self.label_3 = QtWidgets.QLabel(parent=self.groupBox_2)
        self.label_3.setGeometry(QtCore.QRect(20, 80, 41, 16))
        self.label_3.setObjectName("label_3")
        self.lineEditName = QtWidgets.QLineEdit(parent=self.groupBox_2)
        self.lineEditName.setGeometry(QtCore.QRect(70, 80, 171, 22))
        self.lineEditName.setStyleSheet("background-color: rgb(251, 255, 206);")
        self.lineEditName.setObjectName("lineEditName")
        self.lineEditAge = QtWidgets.QLineEdit(parent=self.groupBox_2)
        self.lineEditAge.setGeometry(QtCore.QRect(70, 110, 171, 22))
        self.lineEditAge.setStyleSheet("background-color: rgb(251, 255, 206);")
        self.lineEditAge.setObjectName("lineEditAge")
        self.label_2 = QtWidgets.QLabel(parent=self.groupBox_2)
        self.label_2.setGeometry(QtCore.QRect(20, 50, 41, 16))
        self.label_2.setObjectName("label_2")
        self.lineEditCode = QtWidgets.QLineEdit(parent=self.groupBox_2)
        self.lineEditCode.setGeometry(QtCore.QRect(70, 50, 171, 22))
        self.lineEditCode.setStyleSheet("background-color: rgb(251, 255, 206);")
        self.lineEditCode.setObjectName("lineEditCode")
        self.label_5 = QtWidgets.QLabel(parent=self.groupBox_2)
        self.label_5.setGeometry(QtCore.QRect(20, 150, 41, 16))
        self.label_5.setObjectName("label_5")
        self.lineEditIntro = QtWidgets.QLineEdit(parent=self.groupBox_2)
        self.lineEditIntro.setGeometry(QtCore.QRect(70, 140, 171, 41))
        self.lineEditIntro.setStyleSheet("background-color: rgb(251, 255, 206);")
        self.lineEditIntro.setObjectName("lineEditIntro")
        self.labelAvatar = QtWidgets.QLabel(parent=self.groupBox_2)
        self.labelAvatar.setGeometry(QtCore.QRect(250, 10, 151, 141))
        self.labelAvatar.setStyleSheet("background-color: rgb(170, 255, 255);\n"
"border-color: rgb(85, 85, 0);")
        self.labelAvatar.setText("")
        self.labelAvatar.setPixmap(QtGui.QPixmap("E:\\Elearning\\StudentManagement\\images/ic_no_avatar.png"))
        self.labelAvatar.setScaledContents(True)
        self.labelAvatar.setObjectName("labelAvatar")
        self.pushButtonAvatar = QtWidgets.QPushButton(parent=self.groupBox_2)
        self.pushButtonAvatar.setGeometry(QtCore.QRect(250, 160, 61, 23))
        self.pushButtonAvatar.setStyleSheet("background-color: rgb(170, 255, 127);")
        self.pushButtonAvatar.setObjectName("pushButtonAvatar")
        self.label_6 = QtWidgets.QLabel(parent=self.groupBox_2)
        self.label_6.setGeometry(QtCore.QRect(20, 20, 41, 16))
        self.label_6.setObjectName("label_6")
        self.lineEditId = QtWidgets.QLineEdit(parent=self.groupBox_2)
        self.lineEditId.setEnabled(False)
        self.lineEditId.setGeometry(QtCore.QRect(70, 20, 81, 22))
        self.lineEditId.setStyleSheet("background-color: rgb(251, 255, 206);")
        self.lineEditId.setReadOnly(True)
        self.lineEditId.setObjectName("lineEditId")
        self.pushButtonRemoveAvatar = QtWidgets.QPushButton(parent=self.groupBox_2)
        self.pushButtonRemoveAvatar.setGeometry(QtCore.QRect(320, 160, 81, 23))
        self.pushButtonRemoveAvatar.setStyleSheet("background-color: rgb(170, 255, 127);")
        self.pushButtonRemoveAvatar.setObjectName("pushButtonRemoveAvatar")
        self.groupBox_3 = QtWidgets.QGroupBox(parent=self.centralwidget)
        self.groupBox_3.setGeometry(QtCore.QRect(20, 40, 411, 191))
        self.groupBox_3.setStyleSheet("background-color: rgb(251, 255, 206);")
        self.groupBox_3.setObjectName("groupBox_3")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox_3)
        self.verticalLayout.setObjectName("verticalLayout")
        self.tableWidgetStudent = QtWidgets.QTableWidget(parent=self.groupBox_3)
        self.tableWidgetStudent.setStyleSheet("background-color: rgb(207, 255, 235);")
        self.tableWidgetStudent.setObjectName("tableWidgetStudent")
        self.tableWidgetStudent.setColumnCount(4)
        self.tableWidgetStudent.setRowCount(0)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidgetStudent.setHorizontalHeaderItem(0, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidgetStudent.setHorizontalHeaderItem(1, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidgetStudent.setHorizontalHeaderItem(2, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidgetStudent.setHorizontalHeaderItem(3, item)
        self.verticalLayout.addWidget(self.tableWidgetStudent)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 456, 22))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)
        MainWindow.setTabOrder(self.tableWidgetStudent, self.lineEditCode)
        MainWindow.setTabOrder(self.lineEditCode, self.lineEditName)
        MainWindow.setTabOrder(self.lineEditName, self.lineEditAge)
        MainWindow.setTabOrder(self.lineEditAge, self.pushButtonNew)
        MainWindow.setTabOrder(self.pushButtonNew, self.pushButtonInsert)
        MainWindow.setTabOrder(self.pushButtonInsert, self.pushButtonRemove)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Trần Duy Thanh - Student Management"))
        self.label.setText(_translate("MainWindow", "Student Management"))
        self.groupBox.setTitle(_translate("MainWindow", "Actions:"))
        self.pushButtonRemove.setText(_translate("MainWindow", "Remove"))
        self.pushButtonInsert.setText(_translate("MainWindow", "Insert"))
        self.pushButtonNew.setText(_translate("MainWindow", "New"))
        self.pushButtonUpdate.setText(_translate("MainWindow", "Update"))
        self.groupBox_2.setTitle(_translate("MainWindow", "Student Details:"))
        self.label_4.setText(_translate("MainWindow", "Age:"))
        self.label_3.setText(_translate("MainWindow", "Name:"))
        self.label_2.setText(_translate("MainWindow", "Code:"))
        self.label_5.setText(_translate("MainWindow", "Intro:"))
        self.pushButtonAvatar.setText(_translate("MainWindow", "Avatar"))
        self.label_6.setText(_translate("MainWindow", "ID:"))
        self.pushButtonRemoveAvatar.setText(_translate("MainWindow", "Remove Avatar"))
        self.groupBox_3.setTitle(_translate("MainWindow", "List of Students:"))
        item = self.tableWidgetStudent.horizontalHeaderItem(0)
        item.setText(_translate("MainWindow", "ID"))
        item = self.tableWidgetStudent.horizontalHeaderItem(1)
        item.setText(_translate("MainWindow", "Code"))
        item = self.tableWidgetStudent.horizontalHeaderItem(2)
        item.setText(_translate("MainWindow", "Name"))
        item = self.tableWidgetStudent.horizontalHeaderItem(3)
        item.setText(_translate("MainWindow", "Age"))

Bước 4: Tạo MainWindowEx.py

Bước 4.1: Coding kế thừa Ui_MainWindow, và định nghĩa các biến như bên dưới:

import base64
import traceback
import mysql.connector
from PyQt6.QtGui import QPixmap
from PyQt6.QtWidgets import QTableWidgetItem, QFileDialog, QMessageBox
from MainWindow import Ui_MainWindow

class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.default_avatar="images/ic_no_avatar.png"
        self.id = None
        self.code = None
        self.name = None
        self.age = None
        self.avatar = None
        self.intro = None

Bước 4.2: Override phương thức setupUI:

def setupUi(self, MainWindow):
    super().setupUi(MainWindow)
    self.MainWindow=MainWindow
    self.tableWidgetStudent.itemSelectionChanged.connect(self.processItemSelection)
    self.pushButtonAvatar.clicked.connect(self.pickAvatar)
    self.pushButtonRemoveAvatar.clicked.connect(self.removeAvatar)
    self.pushButtonInsert.clicked.connect(self.processInsert)
    self.pushButtonUpdate.clicked.connect(self.processUpdate)
    self.pushButtonRemove.clicked.connect(self.processRemove)
def show(self):
    self.MainWindow.show()

Hàm trên, ta thiết lập hàm nạp giao diện và khai báo các signals + slots

Bước 4.3: Viết mã lệnh kết nối MySQL Server, cơ sở dữ liệu studentmanagement (lưu ý nó là CSDL ở bài trước)

def connectMySQL(self):
    server = "localhost"
    port = 3306
    database = "studentmanagement"
    username = "root"
    password = "@Obama123"

    self.conn = mysql.connector.connect(
        host=server,
        port=port,
        database=database,
        user=username,
        password=password)

Bước 4.4: Viết lệnh truy vấn toàn bộ Student và hiển thị lên QTableWidget

def selectAllStudent(self):
    cursor = self.conn.cursor()
    # query all students
    sql = "select * from student"
    cursor.execute(sql)
    dataset = cursor.fetchall()
    self.tableWidgetStudent.setRowCount(0)
    row=0
    for item in dataset:
        row = self.tableWidgetStudent.rowCount()
        self.tableWidgetStudent.insertRow(row)

        self.id = item[0]
        self.code = item[1]
        self.name = item[2]
        self.age = item[3]
        self.avatar = item[4]
        self.intro = item[5]

        self.tableWidgetStudent.setItem(row, 0, QTableWidgetItem(str(self.id)))
        self.tableWidgetStudent.setItem(row, 1, QTableWidgetItem(self.code))
        self.tableWidgetStudent.setItem(row, 2, QTableWidgetItem(self.name))
        self.tableWidgetStudent.setItem(row, 3, QTableWidgetItem(str(self.age)))

    cursor.close()

Bước 4.5: Viết hàm hiển thị thông tin chi tiết student khi nhấn vào từng dòng dữ liệu trong QTableWidget

def processItemSelection(self):
    row=self.tableWidgetStudent.currentRow()
    if row ==-1:
        return
    try:
        code = self.tableWidgetStudent.item(row, 1).text()
        cursor = self.conn.cursor()
        # query all students
        sql = "select * from student where code=%s"
        val = (code,)
        cursor.execute(sql, val)
        item = cursor.fetchone()
        if item != None:
            self.id = item[0]
            self.code = item[1]
            self.name = item[2]
            self.age = item[3]
            self.avatar = item[4]
            self.intro = item[5]
            self.lineEditId.setText(str(self.id))
            self.lineEditCode.setText(self.code)
            self.lineEditName.setText(self.name)
            self.lineEditAge.setText(str(self.age))
            self.lineEditIntro.setText(self.intro)
            # self.labelAvatar.setPixmap(None)
            if self.avatar != None:
                imgdata = base64.b64decode(self.avatar)
                pixmap = QPixmap()
                pixmap.loadFromData(imgdata)
                self.labelAvatar.setPixmap(pixmap)
            else:
                pixmap = QPixmap("images/ic_no_avatar.png")

                self.labelAvatar.setPixmap(pixmap)
        else:
            print("Not Found")
        cursor.close()
    except:
        traceback.print_exc()

Bước 4.6: Viết lệnh chọn Avatar và xóa Avatar, có tạo base64string

def pickAvatar(self):
    filters = "Picture PNG (*.png);;All files(*)"
    filename, selected_filter = QFileDialog.getOpenFileName(
        self.MainWindow,
        filter=filters,
    )
    if filename=='':
        return
    pixmap = QPixmap(filename)
    self.labelAvatar.setPixmap(pixmap)

    with open(filename, "rb") as image_file:
        self.avatar = base64.b64encode(image_file.read())
        print(self.avatar)
    pass
def removeAvatar(self):
    self.avatar=None
    pixmap = QPixmap(self.default_avatar)
    self.labelAvatar.setPixmap(pixmap)

Bước 4.7: Viết hàm thêm mới Student:

def processInsert(self):
    try:
        cursor = self.conn.cursor()
        # query all students
        sql = "insert into student(Code,Name,Age,Avatar,Intro) values(%s,%s,%s,%s,%s)"

        self.code = self.lineEditCode.text()
        self.name = self.lineEditName.text()
        self.age = int(self.lineEditAge.text())
        if not hasattr(self, 'avatar'):
            avatar = None
        intro = self.lineEditIntro.text()
        val = (self.code, self.name, self.age, self.avatar, self.intro)

        cursor.execute(sql, val)

        self.conn.commit()

        print(cursor.rowcount, " record inserted")
        self.lineEditId.setText(str(cursor.lastrowid))

        cursor.close()
        self.selectAllStudent()
    except:
        traceback.print_exc()

Bước 4.8: Viết hàm cập nhật Student

def processUpdate(self):
    cursor = self.conn.cursor()
    # query all students
    sql = "update student set Code=%s,Name=%s,Age=%s,Avatar=%s,Intro=%s" \
          " where Id=%s"
    self.id=int(self.lineEditId.text())
    self.code = self.lineEditCode.text()
    self.name = self.lineEditName.text()
    self.age = int(self.lineEditAge.text())
    if not hasattr(self, 'avatar'):
        self.avatar = None
    self.intro = self.lineEditIntro.text()

    val = (self.code,self.name,self.age,self.avatar ,self.intro,self.id )

    cursor.execute(sql, val)

    self.conn.commit()

    print(cursor.rowcount, " record updated")
    cursor.close()
    self.selectAllStudent()

Bước 4.9: Viết hàm xóa Student:

def processRemove(self):
    dlg = QMessageBox(self.MainWindow)
    dlg.setWindowTitle("Confirmation Deleting")
    dlg.setText("Are you sure you want to delete?")
    dlg.setIcon(QMessageBox.Icon.Question)
    buttons = QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
    dlg.setStandardButtons(buttons)
    button = dlg.exec()
    if button == QMessageBox.StandardButton.No:
        return
    cursor = self.conn.cursor()
    # query all students
    sql = "delete from student "\
          " where Id=%s"

    val = (self.lineEditId.text(),)

    cursor.execute(sql, val)

    self.conn.commit()

    print(cursor.rowcount, " record removed")

    cursor.close()
    self.selectAllStudent()
    self.clearData()

Bước 4.10: Viết hàm xóa dữ liệu trên giao diện của phần chi tiết

def clearData(self):
    self.lineEditId.setText("")
    self.lineEditCode.setText("")
    self.lineEditName.setText("")
    self.lineEditAge.setText("")
    self.lineEditIntro.setText("")
    self.avatar=None

Dưới đây là mã lệnh đầy đủ của MainWindowEx.py:

import base64
import traceback
import mysql.connector
from PyQt6.QtGui import QPixmap
from PyQt6.QtWidgets import QTableWidgetItem, QFileDialog, QMessageBox
from MainWindow import Ui_MainWindow

class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.default_avatar="images/ic_no_avatar.png"
        self.id = None
        self.code = None
        self.name = None
        self.age = None
        self.avatar = None
        self.intro = None
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.tableWidgetStudent.itemSelectionChanged.connect(self.processItemSelection)
        self.pushButtonAvatar.clicked.connect(self.pickAvatar)
        self.pushButtonRemoveAvatar.clicked.connect(self.removeAvatar)
        self.pushButtonInsert.clicked.connect(self.processInsert)
        self.pushButtonUpdate.clicked.connect(self.processUpdate)
        self.pushButtonRemove.clicked.connect(self.processRemove)
    def show(self):
        self.MainWindow.show()
    def connectMySQL(self):
        server = "localhost"
        port = 3306
        database = "studentmanagement"
        username = "root"
        password = "@Obama123"

        self.conn = mysql.connector.connect(
            host=server,
            port=port,
            database=database,
            user=username,
            password=password)
    def selectAllStudent(self):
        cursor = self.conn.cursor()
        # query all students
        sql = "select * from student"
        cursor.execute(sql)
        dataset = cursor.fetchall()
        self.tableWidgetStudent.setRowCount(0)
        row=0
        for item in dataset:
            row = self.tableWidgetStudent.rowCount()
            self.tableWidgetStudent.insertRow(row)

            self.id = item[0]
            self.code = item[1]
            self.name = item[2]
            self.age = item[3]
            self.avatar = item[4]
            self.intro = item[5]

            self.tableWidgetStudent.setItem(row, 0, QTableWidgetItem(str(self.id)))
            self.tableWidgetStudent.setItem(row, 1, QTableWidgetItem(self.code))
            self.tableWidgetStudent.setItem(row, 2, QTableWidgetItem(self.name))
            self.tableWidgetStudent.setItem(row, 3, QTableWidgetItem(str(self.age)))

        cursor.close()

    def processItemSelection(self):
        row=self.tableWidgetStudent.currentRow()
        if row ==-1:
            return
        try:
            code = self.tableWidgetStudent.item(row, 1).text()
            cursor = self.conn.cursor()
            # query all students
            sql = "select * from student where code=%s"
            val = (code,)
            cursor.execute(sql, val)
            item = cursor.fetchone()
            if item != None:
                self.id = item[0]
                self.code = item[1]
                self.name = item[2]
                self.age = item[3]
                self.avatar = item[4]
                self.intro = item[5]
                self.lineEditId.setText(str(self.id))
                self.lineEditCode.setText(self.code)
                self.lineEditName.setText(self.name)
                self.lineEditAge.setText(str(self.age))
                self.lineEditIntro.setText(self.intro)
                # self.labelAvatar.setPixmap(None)
                if self.avatar != None:
                    imgdata = base64.b64decode(self.avatar)
                    pixmap = QPixmap()
                    pixmap.loadFromData(imgdata)
                    self.labelAvatar.setPixmap(pixmap)
                else:
                    pixmap = QPixmap("images/ic_no_avatar.png")

                    self.labelAvatar.setPixmap(pixmap)
            else:
                print("Not Found")
            cursor.close()
        except:
            traceback.print_exc()

    def pickAvatar(self):
        filters = "Picture PNG (*.png);;All files(*)"
        filename, selected_filter = QFileDialog.getOpenFileName(
            self.MainWindow,
            filter=filters,
        )
        if filename=='':
            return
        pixmap = QPixmap(filename)
        self.labelAvatar.setPixmap(pixmap)

        with open(filename, "rb") as image_file:
            self.avatar = base64.b64encode(image_file.read())
            print(self.avatar)
        pass
    def removeAvatar(self):
        self.avatar=None
        pixmap = QPixmap(self.default_avatar)
        self.labelAvatar.setPixmap(pixmap)
    def processInsert(self):
        try:
            cursor = self.conn.cursor()
            # query all students
            sql = "insert into student(Code,Name,Age,Avatar,Intro) values(%s,%s,%s,%s,%s)"

            self.code = self.lineEditCode.text()
            self.name = self.lineEditName.text()
            self.age = int(self.lineEditAge.text())
            if not hasattr(self, 'avatar'):
                avatar = None
            intro = self.lineEditIntro.text()
            val = (self.code, self.name, self.age, self.avatar, self.intro)

            cursor.execute(sql, val)

            self.conn.commit()

            print(cursor.rowcount, " record inserted")
            self.lineEditId.setText(str(cursor.lastrowid))

            cursor.close()
            self.selectAllStudent()
        except:
            traceback.print_exc()

    def processUpdate(self):
        cursor = self.conn.cursor()
        # query all students
        sql = "update student set Code=%s,Name=%s,Age=%s,Avatar=%s,Intro=%s" \
              " where Id=%s"
        self.id=int(self.lineEditId.text())
        self.code = self.lineEditCode.text()
        self.name = self.lineEditName.text()
        self.age = int(self.lineEditAge.text())
        if not hasattr(self, 'avatar'):
            self.avatar = None
        self.intro = self.lineEditIntro.text()

        val = (self.code,self.name,self.age,self.avatar ,self.intro,self.id )

        cursor.execute(sql, val)

        self.conn.commit()

        print(cursor.rowcount, " record updated")
        cursor.close()
        self.selectAllStudent()
    def processRemove(self):
        dlg = QMessageBox(self.MainWindow)
        dlg.setWindowTitle("Confirmation Deleting")
        dlg.setText("Are you sure you want to delete?")
        dlg.setIcon(QMessageBox.Icon.Question)
        buttons = QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
        dlg.setStandardButtons(buttons)
        button = dlg.exec()
        if button == QMessageBox.StandardButton.No:
            return
        cursor = self.conn.cursor()
        # query all students
        sql = "delete from student "\
              " where Id=%s"

        val = (self.lineEditId.text(),)

        cursor.execute(sql, val)

        self.conn.commit()

        print(cursor.rowcount, " record removed")

        cursor.close()
        self.selectAllStudent()
        self.clearData()
    def clearData(self):
        self.lineEditId.setText("")
        self.lineEditCode.setText("")
        self.lineEditName.setText("")
        self.lineEditAge.setText("")
        self.lineEditIntro.setText("")
        self.avatar=None

Bước 5: Viết MyApp.py để thực thi phần mềm:

from PyQt6.QtWidgets import QApplication, QMainWindow

from MainWindowEx import MainWindowEx

app=QApplication([])
myWindow=MainWindowEx()
myWindow.setupUi(QMainWindow())
myWindow.connectMySQL()
myWindow.selectAllStudent()
myWindow.show()
app.exec()

Thực thi phần mềm ta có kết quả như mong muốn.

Đây là Case Study rất hay và quan trọng, làm nền tảng để làm các dự án khác, các bạn cố gắng thực hiện.

Source code đầy đủ của dự án bạn tải ở đây:

https://www.mediafire.com/file/c7x331c2pq5j4kp/StudentManagement.rar/file

Bài học tiếp theo Tui sẽ hướng dẫn các bạn cách mô hình hóa hướng đối tượng khi tương tác cơ sở dữ liệu

Chúc các bạn thành công

Bài 48: Tương tác Python với Cơ sở dữ liệu MySQL Server -Phần 1

Ở các bài học trước, các bạn đã biết cách cài đặtsử dụng MySQL Server, MySQL WorkBench. Biết cách tạo Cơ sở dữ liệu bảng, thuộc tính, các thao tác trong bảng dữ liệu như Xem, thêm, sửa, xóa, sắp xếp… ngay trong công cụ MySQL Workbench.

Bài học này Chúng ta bắt đầu sử dụng ngôn ngữ lập trình Python để tương tác cơ sở dữ liệu MySQL Server “studentmanagement” đã thực hiện ở bài trước. Việc tương tác này có nhiều cấp độ, bài học này ta sẽ nghiên cứu các thư viện và mã lệnh để kết nối và tương tác dữ liệu trực tiếp bằng Python, cũng như phân trang dữ liệu. Các bài sau chúng ta sẽ nghiên cứu sâu hơn, chẳng hạn như mô hình hóa hướng đối tượng, cấu trúc các mã lệnh thành các thư viện để tối ưu hóa hệ thống phần mềm. Trong bài này, Tui hướng dẫn lập trình các chức năng sau:

(1) Lập trình Python kết nối MySQL Server

(2) Lập trình Python truy vấn dữ liệu MySQL Server

(3) Lập trình Python thêm mới dữ liệu MySQL Server

(4) Lập trình Python cập nhật dữ liệu MySQL Server

(5) Lập trình Python xóa dữ liệu MySQL Server

Trước tiên, chúng ta cần cài đặt thư viện kết nối và tương tác MySQL Server, có nhiều thư viện nhưng ở đây chúng ta thống nhất sử dụng thư viện mysql.connector và thư viện này sẽ được sử dụng xuyên suốt các bài học còn lại, cũng như ứng dụng trong thống kê và máy học.

Ta sử dụng lệnh sau để cài đặt:

python -m pip install mysql-connector-python

(1) Lập trình Python kết nối MySQL Server

Nếu bạn nào chưa có dữ liệu “studentmanagement” có thể dùng chức năng Import/Export được học ở bài trước, để import dữ liệu:

https://tranduythanh.com/datasets/studentmanagement.rar

Các thông số server, port, database, user, password… ta đã cài đặt ở các bài học trước, nên bài này ta sử dụng. Tùy vào máy tính của bạn lúc cài đặt phần mềm như thế nào thì khai báo các thông số cho chính xác. Tui đã chủ ý khai báo các biến riêng biệt để bạn chỉ cần thay thế giá trị theo máy tính mà mình đã cài đặt MySQL Server. Để kết nối tới cơ sở dữ liệu MySQL ta tạo file “TestQueryMySQL.py” và viết mã lệnh như sau:

import mysql.connector

server="localhost"
port=3306
database="studentmanagement"
username="root"
password="@Obama123"

conn = mysql.connector.connect(
                host=server,
                port=port,
                database=database,
                user=username,
                password=password)

Lệnh connect() ở trên trả về một MySQLConnection. Nếu kết nối thất bại chương trình sẽ thông báo lỗi. Trong giới hạn của bài học này, để cho nó đơn giản, dễ hiểu thì Tui bỏ qua hết các handle exception, tập trung vào các mã lệnh cốt lõi mà ở đó ta đã kết nối tới MySQL Server thành công. Các bài học sau Tui sẽ bổ sung thêm các checked erros này.

Bây giờ ta sử dụng đối tượng conn ở trên để thực hiện các tương tác CRUD dưới đây:

(2) Lập trình Python truy vấn dữ liệu MySQL Server

(2.1) Truy vấn toàn bộ Sinh viên:

cursor = conn.cursor()

sql="select * from student"
cursor.execute(sql)

dataset=cursor.fetchall()
align='{0:<3} {1:<6} {2:<15} {3:<10}'
print(align.format('ID', 'Code','Name',"Age"))
for item in dataset:
    id=item[0]
    code=item[1]
    name=item[2]
    age=item[3]
    avatar=item[4]
    intro=item[5]
    print(align.format(id,code,name,age))

cursor.close()

Thực thi lệnh trên ta có kết quả:

(2.2) Truy vấn các Sinh viên có độ tuổi từ 22 tới 26:

cursor = conn.cursor()
sql="SELECT * FROM student where Age>=22 and Age<=26"
cursor.execute(sql)

dataset=cursor.fetchall()
align='{0:<3} {1:<6} {2:<15} {3:<10}'
print(align.format('ID', 'Code','Name',"Age"))
for item in dataset:
    id=item[0]
    code=item[1]
    name=item[2]
    age=item[3]
    avatar=item[4]
    intro=item[5]
    print(align.format(id,code,name,age))

cursor.close()

Thực thi lệnh trên ta có kết quả:

(2.3) Truy vấn toàn bộ sinh viên và sắp xếp theo tuổi tăng dần:

cursor = conn.cursor()
sql="SELECT * FROM student " \
    "order by Age asc"
cursor.execute(sql)

dataset=cursor.fetchall()
align='{0:<3} {1:<6} {2:<15} {3:<10}'
print(align.format('ID', 'Code','Name',"Age"))
for item in dataset:
    id=item[0]
    code=item[1]
    name=item[2]
    age=item[3]
    avatar=item[4]
    intro=item[5]
    print(align.format(id,code,name,age))

cursor.close()

Thực thi lệnh trên ta có kết quả:

(2.4) Truy vấn các Sinh viên có độ tuổi từ 22 tới 26 và sắp xếp theo tuổi giảm dần:

cursor = conn.cursor()
sql="SELECT * FROM student " \
    "where Age>=22 and Age<=26 " \
    "order by Age desc "
cursor.execute(sql)

dataset=cursor.fetchall()
align='{0:<3} {1:<6} {2:<15} {3:<10}'
print(align.format('ID', 'Code','Name',"Age"))
for item in dataset:
    id=item[0]
    code=item[1]
    name=item[2]
    age=item[3]
    avatar=item[4]
    intro=item[5]
    print(align.format(id,code,name,age))

cursor.close()

Thực thi lệnh trên ta có kết quả:

(2.5) Truy vấn chi tiết thông tin Sinh viên khi biết Id:

Ví dụ lấy chi tiết Sinh viên khi biết ID=1

cursor = conn.cursor()
sql="SELECT * FROM student " \
    "where ID=1 "

cursor.execute(sql)

dataset=cursor.fetchone()
if dataset!=None:
    id,code,name,age,avatar,intro=dataset
    print("Id=",id)
    print("code=",code)
    print("name=",name)
    print("age=",age)

cursor.close()

Thực thi lệnh trên ta có kết quả:

(2.6) Truy vấn dạng phân trang Student:

Trong trường hợp dữ liệu nhiều, ta có thể phân thành nhiều đợt truy vấn dữ liệu thông qua từ khóa limit và offset, nó còn được gọi là paging.

Ví dụ: Dữ liệu trong bảng Sinh viên có 6 phần tử, ta muốn truy vấn 2 lần, mỗi lần 3 phần tử:

Lần thứ nhất truy vấn 3 dòng dữ liệu đầu tiên (các ID 1, 2, 3) thì câu SQL viết như sau:

sql="SELECT * FROM student LIMIT 3 OFFSET 0"

Ta thử nghiệm chi tiết:

cursor = conn.cursor()
sql="SELECT * FROM student LIMIT 3 OFFSET 0"
cursor.execute(sql)

dataset=cursor.fetchall()
align='{0:<3} {1:<6} {2:<15} {3:<10}'
print(align.format('ID', 'Code','Name',"Age"))
for item in dataset:
    id=item[0]
    code=item[1]
    name=item[2]
    age=item[3]
    avatar=item[4]
    intro=item[5]
    print(align.format(id,code,name,age))

cursor.close()

Thực thi coding ở trên ta có kết quả:

Lần thứ nhì truy vấn 3 dòng dữ liệu còn lại (các ID 4, 5, 6) thì câu SQL viết như sau:

sql="SELECT * FROM student LIMIT 3 OFFSET 3"

Ta thử nghiệm chi tiết:

cursor = conn.cursor()
sql="SELECT * FROM student LIMIT 3 OFFSET 3"
cursor.execute(sql)

dataset=cursor.fetchall()
align='{0:<3} {1:<6} {2:<15} {3:<10}'
print(align.format('ID', 'Code','Name',"Age"))
for item in dataset:
    id=item[0]
    code=item[1]
    name=item[2]
    age=item[3]
    avatar=item[4]
    intro=item[5]
    print(align.format(id,code,name,age))

cursor.close()

Thực thi coding ở trên ta có kết quả:

Tức là LIMIT là số phần tử mà ta muốn truy vấn.

OFFSET là vị trí ta bắt đầu truy vấn.

Giả sử ta có N dòng Sinh viên, mỗi lần truy vấn là 3 sinh viên, hãy viết lệnh SQL để chương trình Paging toàn bộ dữ liệu N dòng này:

print("PAGING!!!!!")
cursor = conn.cursor()
sql="SELECT count(*) FROM student"
cursor.execute(sql)
dataset=cursor.fetchone()
rowcount=dataset[0]

limit=3
step=3
for offset in range(0,rowcount,step):
    sql=f"SELECT * FROM student LIMIT {limit} OFFSET {offset}"
    cursor.execute(sql)

    dataset=cursor.fetchall()
    align='{0:<3} {1:<6} {2:<15} {3:<10}'
    print(align.format('ID', 'Code','Name',"Age"))
    for item in dataset:
        id=item[0]
        code=item[1]
        name=item[2]
        age=item[3]
        avatar=item[4]
        intro=item[5]
        print(align.format(id,code,name,age))

cursor.close()

Thực thi lệnh PAGING trên ta có kết quả:

Mã lệnh ở trên hay ở chỗ nào?

Hay ở chỗ nếu như ta muốn phân trang bao nhiêu phần tử thì chỉ cần đổi limit và offset là được. Ví dụ như muốn phân trang mỗi lần chạy truy vấn 50 Sinh viên thì đổi limit=50 và step=50 (Bạn thấy Gmail không? email rất nhiều, nhưng mỗi lần họ cho truy vấn xem 50 email, muốn xem trước sau thì bấm nút. Đó là minh họa PAGING)

Source code dầy đủ cho phần kết nối và truy vấn dữ liệu:

https://www.mediafire.com/file/zf7qlx2rm4tgqri/TestQueryMySQL.py/file

(3) Lập trình Python thêm mới dữ liệu MySQL Server:

(3.1) Thêm mới 1 Student

Chúng ta sẽ viết mã lệnh để thêm mới 1 Student theo cú pháp như dưới đây:

cursor = conn.cursor()

sql="insert into student (code,name,age) values (%s,%s,%s)"

val=("sv07","Trần Duy Thanh",45)

cursor.execute(sql,val)

conn.commit()

print(cursor.rowcount," record inserted")

cursor.close()

Ở trên ta thấy, val là 1 tuple chưa thông tin của Student. Lưu ý rằng câu lệnh insert ở trên chỉ thêm code, name, age (không thấy ID vì nó tự động tăng, đồng thời avatar và intro Tui cũng không thêm mới tức là nó sẽ có giá trị null tự động)

Câu lệnh insert SQL dùng 3 biến, thì val sẽ cần 1 tuple có 3 thành phần như code minh họa.

Khi gọi lệnh cursor.execute(sql,val) thì chương trình sẽ tự động mapping giá trị trong tuple cho các %s

lệnh conn.commit() để xác thực là sẽ lưu mới dữ liệu

lệnh cursor.rowcount cho chúng ta biết có bao nhiêu dòng dữ liệu được thay đổi trong cơ sở dữ liệu.

Thực thi lệnh trên ta có kết quả:

(3.2) Thêm mới nhiều Student:

cursor = conn.cursor()

sql="insert into student (code,name,age) values (%s,%s,%s)"

val=[
    ("sv08","Trần Quyết Chiến",19),
    ("sv09","Hồ Thắng",22),
    ("sv10","Hoàng Hà",25),
     ]

cursor.executemany(sql,val)

conn.commit()

print(cursor.rowcount," record inserted")

cursor.close()

Mã lệnh trên có 2 chỗ khác biệt:

  • Chỗ thứ nhất đó là val trở thành 1 mảng các Tuple
  • chỗ thứ nhì là ta sử dụng hàm cursor.executemany(sql,val)

Thực thi lệnh ta có kết quả:

Source code toàn bộ phần Insert Student các bạn có thể tải ở đây:

https://www.mediafire.com/file/839moz9a83bjiau/TestInsertMySQL.py/file

(4) Lập trình Python cập nhật dữ liệu MySQL Server

(4.1) Cập nhật tên Sinh viên có Code=’sv09′ thành tên mới “Hoàng Lão Tà”

cursor = conn.cursor()
sql="update student set name='Hoàng Lão Tà' where Code='sv09'"
cursor.execute(sql)

conn.commit()

print(cursor.rowcount," record(s) affected")

Thực thi mã lệnh trên ta có kết quả:

(4.2) Cập nhật tên Sinh viên có Code=’sv09′ thành tên mới “Hoàng Lão Tà” như viết dạng SQL Injection:

cursor = conn.cursor()
sql="update student set name=%s where Code=%s"
val=('Hoàng Lão Tà','sv09')

cursor.execute(sql,val)

conn.commit()

print(cursor.rowcount," record(s) affected")

Source code toàn bộ phần Update Student các bạn có thể tải ở đây:

https://www.mediafire.com/file/5fes444erp2v4s3/TestUpdateMySQL.py/file

(5) Lập trình Python xóa dữ liệu MySQL Server

(5.1) Xóa Student có ID=14

conn = mysql.connector.connect(
                host=server,
                port=port,
                database=database,
                user=username,
                password=password)
cursor = conn.cursor()
sql="DELETE from student where ID=14"
cursor.execute(sql)

conn.commit()

print(cursor.rowcount," record(s) affected")

Thực hiện mã lệnh trên ta có kết quả:

(5.2) Xóa Student có ID=13 với SQL Injection

conn = mysql.connector.connect(
                host=server,
                port=port,
                database=database,
                user=username,
                password=password)
cursor = conn.cursor()
sql = "DELETE from student where ID=%s"
val = (13,)

cursor.execute(sql, val)

conn.commit()

print(cursor.rowcount," record(s) affected")

Sourecode đầy đủ phần xóa ở đây:

https://www.mediafire.com/file/qgr8ko351nhmho9/TestRemoveMySQL.py/file

Như vậy, tới đây Tui đã hướng dẫn đầy đủ và chi tiết các câu lệnh SQL liên quan tới CRUD để tương tác dữ liệu, sử dụng Python để triệu gọi lệnh.

Hầu hết các lệnh cốt lõi về tương tác Python với MySQL Server các bạn đã nắm, chỉ cần làm tốt cá lệnh này là hầu hết mọi yêu cầu liên quan tới viết phần mềm quản trị hệ thống là bạn có thể thực hiện được.

Bài học sau Tui sẽ nâng cấp bài này bằng cách mô hình hóa hướng đối tượng, sau đó thiết kế giao diện tương tác người dùng, rồi cuối cùng là tích hợp mô hình máy học, thống kê.

Các bạn chú ý theo dõi

Chúc các bạn thành công

Bài 47: Sử dụng hệ quản trị Cơ sở dữ liệu MySQL Server-MySQL Workbench

Trong bài học 46, chúng ta đã cài đặt thành công MySQL Server và MySQL Workbench, bài học này Tui sẽ hướng dẫn cách sử dụng công cụ MySQL Workbench để tương tác và quản lý cơ sở dữ liệu MySQL Server. Các hướng dẫn tập trung vào những thao tác sau:

(1) Kết nối MySQL Server

(2) Tạo và cấu hình Schema

(3) Tạo và cấu hình Table

(4) Các thao tác CRUD bằng visualization

(5) Các thao tác CRUD bằng cách viết SQL Script

(6) Cách Export và Import Schema

Ta bắt đầu thao tác theo hướng dẫn chi tiết dưới đây:

(1) Kết nối MySQL Server:

Khởi động MySQL Workbench lên ta thấy giao diện Welcome to MySQL Workbench như bên dưới:

Nhấn chọn biểu tượng “(+)” ở trên để cài đặt Connection, giao diện Setup New Connection hiển thị ra như dưới đây:

Ta bấm chọn nút lệnh “Store In Vault” để mở cửa sổ “Store Password For Connection’:

Ta nhập mật khẩu @Obama123 đã cấu hình lúc cài đặt MySQL Server ở bài 46, rồi nhấn OK để quay trở lại màn hình Setup New Connection:

Để kiểm tra kết nối tới MySQL Server có thành công hay không ta nhấn nút lệnh “Test Connection“, nếu màn hình thông báo kết nối thành công xuất hiện như dưới đây thì ta đã hoàn tất quá trình cấu hình kết nối MySQL Server:

Nhấn “Ok” để quay trở về màn hình Setup New Connection:

Đặt tên Connection Name “MyConnection” Nhấn OK, để quay trở lại màn hình Welcome to MySQL Workbench:

Lúc này hệ thống sẽ xuất hiện khung lệnh kết nối “MyConnection” như màn hình trên, ta nhấn vào nó để kết nối và mở màn hình quản trị hệ quản trị cơ sở dữ liệu MySQL Server:

Mặc định hệ thống có 3 SChema là “sakila”, “world” và “sys”.

(2) Tạo và cấu hình Schema:

Giả sử ta tạo Schema mới tên là “StudentManagement

Để tạo mới Schema ta có nhiều cách, cách nhanh nhất là nhấn vào biểu tượng “Create a new schema in the connected server” trong thanh toolbar:

Màn hình tạo Schema hiện thị ra như đưới đây:

  • Mục name: Ta đặt tên schema là “StudentManagement
  • Mục Charset/Collation: Ta chọn utf8 tương ứng như hình

Sau đó nhấn nút Apply, màn hình thông báo Apply Changes to Object xuất hiện ra như dưới đây:

Sau khi nhấn OK, màn hình Apply SQL Script to Database sẽ hiển thị bước “Reviewing the SQL Script to be Applied on the Database” ra như dưới đây:

Nhấn nút “Apply” để hiển thị bước “Applying SQL script to the database”:

Nhấn nút “Finish” để hoàn tất, ta có kết quả là schema “studentmanagement” được tạo ra như dưới đây:

Bây giờ ta qua chức năng tạo các Tables cho Schema studentmanagement

(3) Tạo và cấu hình Table:

Để tạo 1 Table mới, ta nhấn chuột phải vào Tables/Sau đó chọn Create Table…. như hình dưới đây:

Lúc này màn hình tạo Table mới hiện thị ra như dưới đây:

  • Mục số 1: là nơi ta đặt tên Table và thiết lập Charset cho Table. Ví dụ ta đặt tên table là “student” và Charset chọn utf8
  • Mục số 2: Là các cột/thuộc tính của table “Student“, giả sử ta có 3 thuộc tính là ID, Code, Name. Bạn nhập trực tiếp vào Column Name, và chỉnh DataType như hình. Lưu ý ID có biểu tượng chìa khóa màu vàng ở đằng trước là vì ta thiết lập nó là khóa chính (tick vào PK), NN (not null), AI (Auto Increment) tự động tăng khi phát sinh dòng dữ liệu mới

Sau đó ta nhấn nút “Apply“, màn hình Apply changes to Object sẽ hiển thị như dưới đây:

Nhấn nút OK, màn hình “Review the SQL Script to be Applied on the Database” xuất hiện như dưới đây:

Ta nhấn nút “Apply”, màn hình “Applying SQL script to the database” xuất hiện như dưới đây:

Ta nhấn “Finish” để hoàn tất quá trình tạo bảng student. Kết quả hiện thị như màn hình dưới đây:

Tuy nhiên, không phải lúc nào ta cũng tạo 1 lần là chính xác hay hiện thiện luôn bảng dữ liệu, mà ta thường làm thiếu hoặc sai và cần bổ sung hay chỉnh sửa. Để làm điều này ta chọn chức năng “Alter Table“:

Bấm chuột phải vào bảng “student” rồi chọn “Alter Table…”

Giả sử rằng ta muốn hiệu chỉnh cấu trúc bảng student như dưới đây:

Ta cấu hình thêm Age, Avatar và Intro, đồng thời hiệu chỉnh Datatype cho Name như hình.

Sau đó lặp lại thao tác nhấn nút “Apply”, rồi “Finish” ta có kết quả:

Như vậy tới đây ta đã biết cách tạo và hiệu chỉnh cấu trúc Table.

(4) Các thao tác CRUD bằng visualization:

Phần này Tui sẽ hướng dẫn các bạn các thao tác Xem, thêm, sửa, xóa dữ liệu (CRUD) bằng công cụ visualization.

  • C (Create, tương ứng với lệnh insert) là thêm mới dữ liệu
  • R (Retrieve, Read, tương ứng với select) là xem, truy vấn dữ liệu
  • U (Update, tương ứng với lệnh update) là cập nhật dữ liệu
  • D (Delete, tương ứng với lệnh delete) là xóa dữ liệu

Để xem dữ liệu trong bảng (R) hoặc cập nhật dữ liệu (U) ta bấm chuột phải vào bảng rồi chọn “Select rows – Limit 1000“:

Để thêm mới dữ liệu hoặc chỉnh sửa dữ liệu, ngay trong mục số 2 ta nhập dữ liệu mới hoặc chỉnh sửa dữ liệu. Cột ID là Auto Increment nên ta không cần nhập, ta nhập dữ liệu cho các ô Code, Name, Age, Avatar, Intro:

Bạn cứ nhận dữ liệu bao nhiêu tùy thích, sau đó nhấn nút “Apply”, màn hình “Review the SQL Script” xuất hiện như dưới đây:

Nhấn nút “Apply”, màn hình “Applying SQL script to the database” xuất hiện:

Sau khi bấm “Finish” ta có kết quả:

Để xóa (D) dữ liệu dòng nào đó, ta bấm chuột phải vào dòng đó rồi chọn “Delete Row(s)”, muốn xóa nhiều dòng thì bôi đen nhiều dòng:

Sau khi bấm Delete Row(s), ta cần lặp lại tao tác nhấn nút “Apply” rồi “Finish” như chức năng thêm mới hay cập nhật dữ liệu.

Ngoài ra, các thao tác Xem, thêm, sửa, xóa ta có thể làm trong nhóm Form Editor:

Ở giao diện trên, bạn có thể nhấn vào biểu tượng Xóa dữ liệu “-” và biểu tượng thêm mới dữ liệu “+”, có thể chỉnh sửa trực tiếp trong các ô dữ liệu. Sau đó nhấn nút “Apply” như các chức năng trước.

(5) Các thao tác CRUD bằng cách viết SQL Script:

Trước tiên chúng ta cần thiết lập default cho Schema:

Bấm chuột phải vào cơ sở dữ liệu/ chọn Set as Default Schema.

khi thiết lập default, thì mọi thác tác ngầm định sẽ ảnh hưởng trên Schema này.

(5.1) Truy vấn dữ liệu bằng SQL Script

  • Truy vấn toàn bộ sinh viên:
SELECT * FROM student;

Sau khi gõ lệnh truy vấn, ta nhấn biểu tượng execute để xem kế quả:

  • Truy vấn các Sinh viên có độ tuổi từ 22 tới 26
SELECT * FROM student where Age>=22 and Age<=26;

Viết xong mã lệnh, ta bấm biểu tượng execute để xem kết quả:

  • Truy vấn toàn bộ sinh viên và sắp xếp theo tuổi tăng dần:
SELECT * FROM student 
order by Age asc

Thực thi lệnh trên ta có kết quả:

  • Truy vấn các Sinh viên có độ tuổi từ 22 tới 26 và sắp xếp theo tuổi giảm dần
SELECT * FROM student 
where Age>=22 and Age<=26 
order by Age desc 

Chạy mã lệnh để xem kết quả thực hiện:

(5.2) Cập nhật dữ liệu bằng SQL Script

Với chức năng cập nhật và xóa, thì đa phần trong các trường hợp MySQL Server sẽ bảo vệ dữ liệu vì sợ ta cập nhật và xóa bậy bạ, ta bỏ chức năng này đi để thử nghiệm:

Vào menu Edit/ chọn Preferences….

Sau đó nhấn vào SQL Editor:

Unchecked “Safe Updates (rejects UPDATEs and DELETEs with no restrictions)” rồi bấm OK

Đóng và Khởi động lại MySQL Workbench (bắt buộc)

  • Sửa tên Sinh viên có mã “sv06” thành “Obama”
update student 
set Name="Obama"
where ID=6

Sau khi thực thi xong câu lệnh Update, ta xem lại câu truy vấn sẽ có kết quả được thay đổi:

(5.3) Xóa dữ liệu bằng SQL Script

Xóa Sinh viên có Code sv06

delete from student
where Code="sv06"

Thực thi lệnh trên xong, sau đó truy vấn lại ta thấy sinh viên sv06 đã bị xóa:

(6) Cách Export và Import Schema:

Cuối cùng ta vào chức năng Export vào Import. Đây là các chức năng quan trọng và tiện lợi, vì ta cần phải di chuyển dữ liệu:

(6.1) Export Schema

Để Export Schema: Ta vào menu Server -> chọn Export Data

Màn hình Data Export xuất hiện ra như dưới đây:

(1)Ta tick chọn Schema muốn Export

(2)Chọn Dump Structure and Data

(3)Chọn nơi lưu trữ dữ liệu Export

(4)Sau đó nhấn nút “Start Export”

Lưu ý rằng, một số Laptop độ phân giải yếu, cần chỉnh lại Resolution mới thấy nút “Start Export”

Ta thấy kết quả báo như dưới đây là thành công:

Ta vào Windows Explorer sẽ thấy thư mục chứa dữ liệu vừa Export:

Ta có thể sao chép, lưu trữ, di chuyển dữ liệu Export này tới bất kỳ nơi đâu, có thể chuyển qua máy tính khác. Ví dụ như làm việc nhóm, ta có thể lấy dữ liệu trong máy của thành viên này qua máy của thành viên khác.

(6.2) Import Schema

Giả sử ta cần import Schema vừa export kia vào 1 máy tính khác:

Máy tính này phần mềm MySQL Server chưa có Schema “studentmanagement”

Ta vào menu Server/chọn Data Import:

Chức năng import sẽ hiện thị ra như dưới đây:

Ta bấm vào nút “New” để tạo Schema mới, trong màn hình Create Schema ta đặt tên “studentmanagement” rồi bấm nút Ok, kết quả hiển thị ra như bên dưới:

Ở màn hình trên ta cần làm các thao tác sau:

(1)Trỏ tới thư mục lưu trữ tập tin được export (không phải tập tin, mà chọn thư mục chứa tập tin được export). Lúc này Schema sẽ hiển thị vào bảng ở bên tay trái (studentmanagement), nhấn vào Schema ta sẽ thấy các bảng dữ liệu dược hiển thị trong màn hình bên phải (student). Tick chọn schema trong trong bảng ở bên trái

(2)Mục Default Target Schema: Chọn Schema mà ta vừa tạo ở trên

(3) Chọn Dump Structure and Data

(4) Bấm chọn “Start Import” để bắt đầu Import. Nếu máy tính resolution kém thì chỉnh lại để thấy nút này.

Kết quả import:

Sau khi thấy màn hình trên hiển thị tức là đã import thành công, tuy nhiên ta vẫn chưa thấy Schema được import được hiển thị trong Navigator:

Trong màn hình này ta bấm vào biểu tượng Refresh (biểu tượng có 2 mũi tên chỗ cùng hàng chữ SCHEMAS). Kết quả import sẽ hiển thị ra như dưới đây:

Như vậy tới đây Tui đã hướng dẫn đầy đủ các chức năng liên quan tới quản trị cơ sở dữ liệu trong MySQL Workbench, các bạn cần thao tác nhuần nhuyễn để các bài học sau được hiệu quả hơn.

Bài học sau chúng ta sẽ học lập trình về Python tương tác dữ liệu MySQL Server, học về các lệnh quan trọng, và mô hình hóa hướng đối tượng với các table trong MySQL Server, cũng như thiết kế phần mềm tương tác người dùng kết hợp với cơ sở dữ liệu MySQL Server(khoảng 3 bài học liên quan).

Các bạn chú ý theo dõi

Chúc các bạn thành công!

Bài 46: Cài đặt hệ quản trị Cơ sở dữ liệu MySQL Server

Các bài học trước, chúng ta đã xử lý dữ liệu local như text file, CSV, Json, hay SQLite database. Tuy nhiên đa phần dự án chúng ta sẽ tương tác với các hệ quản trị cơ sở dữ liệu như MySQL Server, Microsoft SQL Server, PostgreSQL…Hay các hệ thống cơ sở dữ liệu online của các hãng.

Trong phạm vi học tập của khóa học này, Tui hướng dẫn các bạn tương tác dữ liệu với MySQL Server, nó là một trong các hệ quản trị cơ sở dữ liệu nổi tiếng, rất phổ biến hiện nay, được sử dụng rất nhiều trong các hệ thống phần mềm quản lý. Và chúng ta sử dụng MySQL Workbench để làm công cụ thao tác với dữ liệu MySQL Server. Chúng ta có khoảng 4 bài học chính liên quan tới Python và MySQL Server:

(1) Cài đặt hệ quản trị cơ sở dữ liệu MySQL Server, công cụ MySQL Workbench

(2) Sử dụng hệ quản trị cơ sở dữ liệu MySQL Server, công cụ MySQL Workbench

(3) Lập trình tương tác Python và MySQL Server: Kết nối cơ sở dữ liệu, thêm mới dữ liệu, truy vấn dữ liệu, cập nhập dữ liệu, xóa dữ liệu, và các thao tác sắp xếp, paging…

(4) Xây dựng các phần mềm tương tác giao diện người dùng sử dụng Python-Pyqt6- Qt Designer, tích hợp các chức năng thống kê và máy học.

Bài học này chúng ta sẽ tiến hành cài đặt và sử dụng MySQL Server, MySQL Workbench (Lưu ý tùy từng thời điểm bạn cài đặt mà version nó khác nhau, nhưng đừng quan trọng version này, đang học tập thì cứ lấy cái mới nhất mà hệ thống hiển thị):

Bước 1: Tải MySQL Server

Vào link : https://dev.mysql.com/downloads/installer/

Chọn version, sau đó ta tải bản cài đặt offline (303.6M) để cài đặt được nhanh chóng hơn

Khi nhấn vào “Download” thì hệ thống thường yêu cầu ta Login hay Sign Up. Trường hợp này ta chọn “No thanks, just start my download”

Sau khi tải về thành công thì ta tiến hành cài đặt

Bước 2: Cài đặt MySQL Server

Màn hình khởi động cho quá trình cài đặt phần mềm sẽ tương tự như dưới đây:

Hình trên cho thấy, có hàng loạt phần mềm sẽ được cài đặt, bao gồm các thư viện lập trình để kết nối tương tác tới MySQL Server.

Ta để mặc định vậy và nhấn “Execute”, chờ phần mềm cài đặt cho hoàn tất:

Tiếp tục chờ cho tới mà hình complete dưới đây:

Sau đó nhấn nút “Next”, màn hình Product Configuration xuất hiện như bên dưới:

Bạn quan sát trạng thái cài đặt MySQL Server là ready, tiêp tục nhấn nút “Next”, màn hình Type and Networking xuất hiện như dưới đây:

Hãy để mặc định như trên và nhấn nút “Next”, port mặc định 3306. Sau khi nhấn Next, màn hình Authentication Method xuất hiện như dưới đây:

Ta chọn “Use Strong Password Encryption for Authentication” rồi nhấn nút Next. Lúc này màn hình Accounts and Roles xuất hiện như dưới đây (tới màn hình này thao tác từ từ kẻo sót thiết lập tài khoản sẽ không đăng nhập được MySQL Server):

Trước tiên cần cài đặt “Root Account Password”:

-Lưu ý với MySQL Server, thì Administrator của hệ thống là account tên root

-Mục MySQL root password và Repeat Password bạn cần nhập giống nhau

Tui giả sử rằng trong chuỗi các bài học hướng dẫn này sẽ dùng mật khẩu cho MySQL root là: @Obama123

Bạn nhập giống như trên, như vậy từ rày về sau khi đăng nhập tài khoản quản trị tối cao của hệ thống thì dùng:

user name: root

password: @Obama123

Sau đó, tạo các tài khoản cho MySQL User Accounts, cũng ở màn hình trên, bạn nhấn vào nút “Add User“, lúc này màn hình MySQL User Account sẽ hiển thị ra như dưới đây:

Ở màn hình này, tùy ý bạn đặt account. Ví dụ tui đặt như sau (bạn cũng thống nhất đặt giống vầy để các bài học sau không phải sử tài khoản kết nối):

User Name: obama

Password và confirm password: @Obama123

Host và Role chọn như giao diện Tui chụp

Sau đó nhấn nút “Ok”, ta có kết quả:

Sau đó nhấn nút “Next”, màn hình Windows Service hiển thị ra như dưới đây:

Chọn cấu hình mặc định như trên, sau đó nhấn nút “Next”, lúc này màn hình Server File Permissions hiển thị ra như dưới đây:

Ta chọn “Yes, grant full access to the user running the Windows Service….” rồi nhấn Next, màn hình Apply Configuration xuất hiện ra như dưới đây:

Ta nhấn nút “Execute” và chờ cho tới khi Apply Configuration hoàn tất:

Ta nhấn Finish để qua màn hình Product Configuration:

Chờ xuất hiện các trạng thái complete và ready thì nhấn nút Next, lúc này màn hình MySQL Router Configuration xuất hiện như sau:

Để mặc định như trên và nhấn nút Finish, lúc này màn hình Product Configuration quay lại như hình dưới đây:

Ta nhấn Next, để qua màn hình Connect To Server:

Màn hình Connect To Server, là màn hình rất quan trọng, nó trả lời cho chúng ta biết đã cài đặt các thông số kết nối thành công hay chưa.

Ở mục trên ta nhập user name và mật khẩu của hệ thống trước đó. Cụ thể:

user name: root

password: @Obama123

Sau đó nhấn nút “Check” để kiểm tra kết nối có thành công hay không, nếu nó ra màn hình như dưới đây là thành công:

Ta nhấn nút Next để tiếp tục, lúc này màn hình Apply Configuration xuất hiện lại như hình đưới đây:

Ta nhấn nút “Execute”, và chờ quá trình này hoàn tất:

Sau đó ta nhấn nút Finish, màn hình Product Configuration tiếp tục quay lại như dưới đây:

Ta nhấn nút Next, Màn hình Installation Complete xuất hiện như dưới đây:

Như vậy tới đây chúng ta đã hoàn tất quá trình cài đặt MySQL Server và MySQL Workbench, màn hình của Workbench như dưới đây, ở bài học sau Tui sẽ hướng dẫn chi tiết cách sử dụng:

khởi động MySQL Workbench ta thấy giao diện: