数据库大作业:公交管理系统

前言

因为在复习数据库,整理下大三数据库大作业。

环境配置

需要一个简单的前端,这里使用的是 python38 + Flask(简单的 web 框架) + psycopg2(PostgreSQL 数据库适配器) ,这就没什么好说的了。对应有一小段 python 代码和 html,css 代码,我前端苦手,这些前端功能是直接拿的同学的,因此这里也不多谈。python 代码可以见后。

当然需要一个数据库软件,我们选择的是 PostgreSQL。参考 Windows 10平台安装PostgreSQL 14.2详细教程 - 知乎 就行。在我重新完善这个实验的时候,使用的版本是 16。下面阐述一些细节。

安装过程中我遇到了上述链接阐述的初始化失败的问题,可以安装上述博主提供的命令行代码手动初始化即可。

在网上的教程中,往往是使用 psql 命令行交互,不过,事实上 PostgreSQL 包含一个名为 pgAdmin 的可视化窗口,因此我就直接使用了可视化操作进行了。

在我的电脑上,PostgreSQL 服务器不会自启动,也无法在 service 中找到,因此我需要通过命令手动启动服务器:

1
./bin/pg_ctl.exe -D "C:\\PostgreSQLDataArea" start

其中,第一个地址是指定软件安装目录下的 pg_tcl.exe, 第二个目录则是指明服务器数据的存放地址,默认是安装目录下 data 文件夹,但是我给它改了。如果你的 pg_ctl 被包含在了环境目录中,那么第一串就可以直接写成 pg_ctl。

启动服务器后就可以在 pgAdmin 中去连接服务器,创建数据库了。

设计数据库

显然这一步才是整个作业的关键

首先回顾作业的要求:

公交车公司架构如下:

  1. 公交公司有若干个【车队】,每个【车队】下有若干条【线路】;
  2. 公交公司有若干辆【汽车】,每辆【车】属于一条【线路】;
  3. 每个【车队】有一名【队长】,他只有管理工作,不开车;
  4. 每条【线路】有若干名【司机】;每名【司机】只在一条【线路】上开车;
  5. 【司机】开车时会产生【违章】,包含: 闯红灯、未礼让斑马线、压线、违章停车等;
  6. 【队长】、【路队长】负责将司机的违章信息输入到系统,包含: 司机、车辆、车队、线路、站点、时间、违章等

公交公司管理系统要求功能如下:

  1. 录入司机信息
  2. 录入汽车信息
  3. 录入违章信息
  4. 查询车队拥有的司机
  5. 查询时间段内实际信息
  6. 查询车队违章信息

对此,数据库的结构大体思路如下:

有车队、线路、站点、汽车、员工等表存储信息,有“违章类型表”和“违章信息表”表示违章信息。特别地,线路和站点之间是多对多的关系,而且有顺序要求,因此我们还要建立一个“线路-站点”关联表。具体的 SQL 语句见后。

接下来对数据库进行范式分析:

  1. 1NF:属性不可被分解。不满足 1NF 就不是数据库,因此我们的数据库显然是满足的;
  2. 2NF:非主属性完全依赖主键。满足。
  3. 3NF:非主属性不存在传递依赖。满足。
  4. BC范式:主属性没有传递依赖和部分依赖。满足。

代码

建表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
-- 汽车表
CREATE TABLE "buses" (
"id" int4 PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
"number" varchar(16) UNIQUE,
"route_id" int4,
"capacity" int4 NOT NULL,
CHECK (number SIMILAR TO '[京津冀晋蒙辽吉黑沪苏浙皖闽赣鲁豫鄂湘粤桂琼渝川贵云藏陕甘青宁新][A-HJ-NP-Z][A-HJ-NP-Z0-9]{5,6}') -- 检查车牌号格式
);

-- 车队表
CREATE TABLE "fleets" (
"id" int4 PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
"leader_id" int4 NOT NULL
);

-- 违章类型表
CREATE TABLE "offence_types" (
"id" int4 PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
"type" varchar(255) NOT NULL UNIQUE
);


-- 路线表
CREATE TABLE "routes" (
"id" int4 PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
"leader_id" int4 UNIQUE,
"fleet_id" int4
);

-- 站点表
CREATE TABLE "stops" (
"id" int4 PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
"name" varchar(255) NOT NULL UNIQUE
);

-- 路线-站点表
CREATE TABLE "route-stops" (
"route_id" int4 NOT NULL,
"order" int4 NOT NULL,
"stop_id" int4 NOT NULL,
PRIMARY KEY ("route_id", "order")
);

-- 员工表
CREATE TABLE "workers" (
"id" int4 PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
"name" varchar(255) NOT NULL,
"gender" varchar(2) NOT NULL,
"is_driver" bool NOT NULL DEFAULT false,
"route_id" int4,
CHECK (gender = '男' or gender = '女')
);

-- 违章表
CREATE TABLE "offences" (
"id" int4 PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
"driver_id" int4 NOT NULL,
"time" timestamp NOT NULL,
"bus_id" int4 NOT NULL,
"stop_id" int4 DEFAULT NULL,
"offence_type_id" int4 NOT NULL
);

-- 外键
ALTER TABLE buses ADD FOREIGN KEY (route_id) REFERENCES routes (id) ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE fleets ADD FOREIGN KEY (leader_id) REFERENCES workers (id) ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE offences ADD FOREIGN KEY (offence_type_id) REFERENCES offence_types (id) ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE offences ADD FOREIGN KEY (driver_id) REFERENCES workers (id) ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE offences ADD FOREIGN KEY (bus_id) REFERENCES buses (id) ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE offences ADD FOREIGN KEY (stop_id) REFERENCES stops (id) ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE routes ADD FOREIGN KEY (leader_id) REFERENCES workers (id) ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE routes ADD FOREIGN KEY (fleet_id) REFERENCES fleets (id) ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "route-stops" ADD FOREIGN KEY (route_id) REFERENCES routes (id) ON DELETE SET NULL ON UPDATE SET NULL;
ALTER TABLE "route-stops" ADD FOREIGN KEY (stop_id) REFERENCES stops (id) ON DELETE SET NULL ON UPDATE SET NULL;
ALTER TABLE workers ADD FOREIGN KEY (route_id) REFERENCES routes (id) ON DELETE SET NULL ON UPDATE CASCADE;

-------- 视图 --------
-- 不同车队的驾驶员基本信息
CREATE VIEW "fleet-driver"(fleet_id, driver_id, driver_name, driver_gender)
AS
SELECT fleets.id, workers.id, workers.name, workers.gender
FROM fleets
INNER JOIN routes ON fleets.id = routes.fleet_id
INNER JOIN workers ON routes.id = workers.route_id
WHERE workers.is_driver;

-- 不同驾驶员的违章信息
CREATE VIEW "driver-offence"(driver_id, offence_time, bus_number, stop_name, offence_type, offence_id)
AS
SELECT offences.driver_id,
offences.time,
buses.number,
stops.name,
offence_types.type,
offences.id
FROM offences
INNER JOIN buses ON offences.bus_id = buses.id
INNER JOIN stops ON offences.stop_id = stops.id
INNER JOIN offence_types ON offences.offence_type_id = offence_types.id;

-- 不同车队的违章信息
CREATE VIEW "fleet-offence"(fleet_id, offence_time, offence_type)
AS
SELECT "fleet-driver".fleet_id, offences.time, offence_types.type
FROM offences
INNER JOIN offence_types ON offences.offence_type_id = offence_types.id
INNER JOIN "fleet-driver" ON offences.driver_id = "fleet-driver".driver_id;


python 前端

访问 localhost:5000/BusCompanyManage 网站即可,注意修改 python 代码中的数据库用户名、密码等信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#! python3.8

import traceback
from flask import Flask, render_template, request
import psycopg2

# 连接数据库
conn = psycopg2.connect(
database='bus_company_db',
user='postgres',
password='password',
host='127.0.0.1',
port='5432'
)

# 数据库操作游标
cur = conn.cursor()

def queryDb(sql: str):
''' 通过SQL语句查询数据库,返回查询结果。 '''
cur.execute(sql)
conn.commit()
return cur.fetchall()

def modifyDb(sql: str):
''' 通过SQL语句修改数据库。 '''
cur.execute(sql)
conn.commit()


def insertDriver(form):
''' 录入司机基本信息,如工号、姓名、性别等。 '''
sql = f'''INSERT INTO workers(name, gender, is_driver, route_id)
VALUES('{form['name']}', '{form['gender']}', TRUE, {form['route_id']});'''
modifyDb(sql)
sql = 'SELECT * FROM workers ORDER BY id DESC;'
return queryDb(sql)


def insertBus(form):
''' 录入汽车基本信息,如车牌号、座数等。 '''
sql = f'''INSERT INTO buses(number, route_id, capacity)
VALUES('{form['number']}', {form['route_id']}, {form['capacity']});'''
modifyDb(sql)
sql = 'SELECT * FROM buses ORDER BY id DESC;'
return queryDb(sql)


def insertOffence(form):
''' 录入司机的违章信息。 '''
sql = f'''INSERT INTO offences(driver_id, time, bus_id, stop_id, offence_type_id)
VALUES('{form['driver_id']}',
'{form['time']}',
{form['bus_id']},
{form['stop_id'] or 'NONE'},
{form['offence_type_id']});'''
modifyDb(sql)
sql = 'SELECT * FROM offences ORDER BY id DESC;'
return queryDb(sql)


def queryFleetDrivers(form):
''' 查询某个车队下的司机基本信息。 '''
sql = f'''SELECT driver_id, driver_name, driver_gender
FROM "fleet-driver"
WHERE fleet_id = {form['fleet_id']};'''
return queryDb(sql)


def queryDriverOffences(form):
''' 查询某名司机在某个时间段的违章详细信息。 '''
sql = f'''SELECT offence_time, bus_number, stop_name, offence_type, offence_id
FROM "driver-offence"
WHERE driver_id = {form['driver_id']}
AND offence_time BETWEEN '{form['start_time']}' AND '{form['end_time']}';'''
return queryDb(sql)


def queryFleetOffences(form):
''' 查询某个车队在某个时间段的违章统计信息,如:2次闯红灯、4次未礼让斑马线等。 '''
sql = f'''SELECT offence_type, COUNT(*)
FROM "fleet-offence"
WHERE fleet_id = {form['fleet_id']}
AND offence_time BETWEEN '{form['start_time']}' AND '{form['end_time']}'
GROUP BY offence_type;'''
return queryDb(sql)


actionTypes = {
'ins-driver': [insertDriver, ['编号', '姓名', '性别', '是否为司机', '路线编号']],
'ins-bus': [insertBus, ['编号', '车牌号', '路线编号', '载客量']],
'ins-offence': [insertOffence, ['司机编号', '时间', '车辆编号', '站点编号', '违章类型号', '违章编号']],
'qry-fleet-driver': [queryFleetDrivers, ['编号', '姓名', '年龄']],
'qry-driver-offence': [queryDriverOffences, ['违章时间', '车牌号', '站点', '违章类型', '编号']],
'qry-fleet-offence': [queryFleetOffences, ['类型', '次数']]
}


app = Flask(__name__)

@app.route('/BusCompanyManage', methods=['GET', 'POST'])
def manage():

msg = None
thead = None
tbody = None

try:
if request.method == 'GET':
msg = '欢迎来到公交安全管理系统。'
elif request.method == 'POST':
msg = '提交成功!'
form = request.form
action = form['type']
tbody = actionTypes[action][0](form)
thead = actionTypes[action][1]
except Exception:
msg = traceback.format_exc()

return render_template(
'index.html',
msg=msg,
thead=thead,
tbody=tbody
)

if __name__ == '__main__':
app.run(debug=True)