数据库课程设计:某自来水公司收费管理系统(SQL Server)
要求
- 实现客户信息、用水类型(类别号、类别名、水价)及业务员管理;
- 实现客户用水信息管理(客户号、月份、用水类别号、用水量);
- 实现客户费用管理(客户号、月份、费用、收费标志),收费标志的默认值为‘未收’;
- 实现收费登记(客户、月份、应收费用、实收费用、业务员),并自动修改收费标志(用触发器实现);
- 创建触发器,实现收费时自动更加应收费用和实收费用,计算本次结余,然后修改客户信息表中的结余金额;
- 创建存储过程统计指定月份应收费用和实收费用;
- 创建存储过程查询指定月份未交费的用户信息,以便催费;
- 创建规则使得月份符合格式“xxxx年xx月”,并绑定到表中相应字段;
- 建立表间关系
设计
思路
每个要求创建一个相应的表,建立联系,再根据具体需求选择增加或者删除表
我的理解是有这么几个要求:
- 客户信息
- 用水类型
- 业务员管理
这三个是基本信息表
- 用水信息管理
- 费用管理
- 收费登记
这三个是管理表,具体表中的属性题目中已经给出
由于要收费自动更加应收费用和实收费用,且要计算结余并更新,所以我选择增加一个表
- 订单管理
这样的话,整体的结构是这样的:
- 基本信息表用于建立客户、业务员以及水类的基本信息供管理表引用
- 用水信息管理负责进行水费的计算并赋给收费登记的应收费用(实现应收费用的自动更加)
- 订单管理负责将收取的费用赋给收费登记的实收费用(实现实收费用的自动更加)
- 收费登记负责统计应收费用和实收费用并进行比较用于确定结余金额以及是否已收费
根据以上思路,就能知道大概需要多少个触发器
- 用水信息管理需要一个
- 订单管理需要一个
- 收费登记需要至少一个
代码
首先是创建表(虽然这个应该都会,触发器里面用到局部变量需要格式相同我就一起写在这)
`/* 由于方便自己看(中文不会一时宕机不记得这是什么表)就全部用中文建表了 */
DROP TABLE IF EXISTS 客户信息; /* 防止验证时候删除数据麻烦直接这里连表一起删除重建 */
DROP TABLE IF EXISTS 业务员信息;
DROP TABLE IF EXISTS 用水类型;
DROP TABLE IF EXISTS 用水信息管理;
DROP TABLE IF EXISTS 收费登记;
DROP TABLE IF EXISTS 费用管理;
DROP TABLE IF EXISTS 订单管理;
CREATE TABLE 客户信息( /* 客户信息表 */
客户号 varchar(10),
客户名 varchar(10),
结余金额 float NOT NULL,
PRIMARY KEY(客户号)
)
CREATE TABLE 业务员信息( /* 业务员信息表 */
业务员号 varchar(10),
业务员名 varchar(10),
PRIMARY KEY(业务员号)
)
CREATE TABLE 用水类型( /* 用水类型表 */
类别号 varchar(10),
类别名 varchar(10),
单位水价 float NOT NULL,
PRIMARY KEY(类别号),
)
CREATE TABLE 用水信息管理( /* 用水信息管理表 */
客户号 varchar(10) references 客户信息(客户号),
月份 varchar(20),
用水类别号 varchar(10) references 用水类型(类别号),
用水量 float,
PRIMARY KEY (客户号,月份)
)
CREATE TABLE 收费登记( /* 收费登记表 */
客户号 varchar(10) references 客户信息(客户号),
月份 varchar(20),
应收费用 float DEFAULT 0,
实收费用 float DEFAULT 0,
业务员 varchar(10) references 业务员信息(业务员号),
PRIMARY KEY(客户号,月份)
)
CREATE TABLE 费用管理( /* 费用管理表 */
客户号 varchar(10) references 客户信息(客户号),
月份 varchar(20),
费用 float ,
收费标志 varchar(10) default ('未收费'),
PRIMARY KEY(客户号,月份),
CHECK (收费标志='已收费' or 收费标志='未收费')
)
CREATE TABLE 订单管理 /* 订单管理表 */
(
订单号 varchar(20),
客户号 varchar(10) references 客户信息(客户号),
月份 varchar(20),
收取费用 float,
业务员号 varchar(10) references 业务员信息(业务员号),
PRIMARY KEY(订单号)
)`
* 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
接下来是触发器部分,本题我用了四个,实际应该可以调整一下结构,可以减少或者改变一下每个触发器的功能的
`DROP TRIGGER IF EXISTS 用水消费记录_插入; /* 方便触发器出问题删掉改完再建懒得create改成alter */
CREATE TRIGGER 用水消费记录_插入 /* 用水信息管理(插入)触发器 */
ON 用水信息管理
AFTER INSERT
AS
DECLARE @客户号 varchar(10)
DECLARE @用水类别 varchar(10)
DECLARE @单位水价 float
DECLARE @用水量 float
DECLARE @水价 float
DECLARE @月份 varchar(20)
SELECT @用水量=用水量,@月份=月份,@客户号=客户号
FROM INSERTED
SELECT @单位水价=单位水价,@用水类别=类别号
FROM 用水类型
BEGIN
INSERT INTO 收费登记(客户号,月份,应收费用)
VALUES((select @客户号 from inserted),(select @月份 from inserted),(@用水量*@单位水价))
END
DROP TRIGGER IF EXISTS 用水消费记录_更改;
CREATE TRIGGER 用水消费记录_更改 /* 用水信息管理(更改)触发器 */
ON 用水信息管理
AFTER UPDATE
AS
DECLARE @客户号 varchar(10)
DECLARE @用水类别 varchar(10)
DECLARE @单位水价 float
DECLARE @用水量 float
DECLARE @水价 float
DECLARE @月份 varchar(20)
SELECT @用水量=用水量,@月份=月份,@客户号=客户号
FROM INSERTED
SELECT @单位水价=单位水价,@用水类别=类别号
FROM 用水类型
BEGIN
UPDATE 收费登记
SET 应收费用=@用水量*@单位水价
WHERE @客户号=收费登记.客户号 AND @月份=收费登记.月份
END`
* 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
这两个触发器是用于“用水信息管理”的,目的是计算水价然后将计算后的水价输入到“收费登记”表中的“应收费用”
实际上这两个触发器可以合并,之前是不知道怎么判断是该插入还是更改数据,后面知道了也懒得改了
`DROP TRIGGER IF EXISTS 收费记录;
CREATE TRIGGER 收费记录 /* 收费记录(插入)触发器 */
ON 收费登记
AFTER INSERT,UPDATE
AS
DECLARE @应收费用 float
DECLARE @实收费用 float
DECLARE @客户号 varchar(10)
DECLARE @用水信息管理_客户号 varchar(10)
DECLARE @月份 char(20)
DECLARE @用水信息管理_月份 char(20)
DECLARE @收费标志 varchar(10)
DECLARE @结余金额 float
SELECT @应收费用=应收费用,@实收费用=实收费用,@客户号=客户号,@月份=月份
FROM INSERTED
SELECT @收费标志=收费标志 from 费用管理
/*SELECT @结余金额=结余金额 from 客户信息*/
SELECT @用水信息管理_客户号=客户号,@用水信息管理_月份=月份 FROM 用水信息管理
BEGIN
SELECT @结余金额=结余金额
FROM 客户信息
where 客户号=@客户号
if @结余金额>0
begin
update 收费登记
set @实收费用=@实收费用+@结余金额,实收费用=@实收费用
where @应收费用>@实收费用 and @客户号=客户号 and @月份=月份
update 客户信息 set 结余金额=0 where 客户号=@客户号
end
if @应收费用<=@实收费用
begin
set @收费标志='已收费'
SELECT @结余金额=结余金额 from 客户信息 where 客户号=@客户号
print(@结余金额)
update 客户信息
set 结余金额=@结余金额+@实收费用-@应收费用
where 客户信息.客户号=@客户号
update 收费登记
set 实收费用=@应收费用
where 客户号=@客户号 and 月份=@月份
end
else
begin
set @收费标志='未收费'
end
IF NOT EXISTS (SELECT 客户号,月份 FROM 费用管理 WHERE 客户号=@客户号 AND 月份=@月份)
begin
INSERT INTO 费用管理(客户号,月份,费用,收费标志)
VALUES((select @客户号 from inserted),(select @月份 from inserted),@应收费用,@收费标志)
end
ELSE
begin
UPDATE 费用管理 SET 费用=@应收费用,收费标志=@收费标志
where @客户号=费用管理.客户号 and 费用管理.月份=@月份
end
END`
* 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
这个触发器就是合并了插入和更新的,它是作用于“收费登记“”表的,主要作用是:
- 收费登记中的记录有插入或者更改时,判断此时客户是否有结余金额,有的话就把结余金额置入“实收费用”中用于支付费用
- 然后比较此时的“应收费用”和“实收费用”。如果应收费用大于实收费用,那就是还没交完费,收费标志保持“未收费”;如果应收费用小于等于实收费用,那就是交完了费,收费标志变成“已收费”,把实收费用减去应收费用剩下的置入结余金额中
- 如果是自己缴费也是同理,订单管理得到的费用置入了“实收费用”,结余金额不够的情况下,实收费用=结余金额提供的+订单管理提供的费用,比较同上
这里我是认为一个触发器可以,但是要分的话好像也可以分成两个触发器
`DROP TRIGGER IF EXISTS 订单信息管理;
CREATE TRIGGER 订单信息管理 /* 订单管理触发器 */
ON 订单管理
AFTER INSERT
AS
DECLARE @客户号 varchar(10)
DECLARE @月份 varchar(20)
DECLARE @实收费用 float
DECLARE @收取费用 float
DECLARE @业务员号 varchar(10)
SELECT @客户号=客户号,@月份=月份,@收取费用=收取费用,@业务员号=业务员号
FROM INSERTED
SELECT @实收费用=实收费用 FROM 收费登记 where 客户号=@客户号 AND 月份=@月份
BEGIN
IF (SELECT 收费标志 FROM 费用管理 WHERE 客户号=@客户号 AND 月份=@月份)='已收费'
ROLLBACK
ELSE
UPDATE 收费登记 SET 业务员=@业务员号,@实收费用=@实收费用+@收取费用,实收费用=@实收费用
where @客户号=收费登记.客户号 and @月份=收费登记.月份
print(@实收费用)
END`
* 1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
* 10
* 11
* 12
* 13
* 14
* 15
* 16
* 17
* 18
* 19
* 20
* 21
* 22
这个触发器就是订单管理的触发器,很简单,就是订单管理输入数据,然后将输入的费用置入“收费登记”表中的“实收金额”
最后就是创建规则和存储结构
`CREATE RULE 月份 /* 规则 */
AS
@月份 like'%年%月'`
* 1
* 2
* 3
规则我是直接某年某月就行,没有具体要求必须是xxxx(四个x)年xx(两个x)月
然后和月份有关的就是下面这个四个表,所以应用在这四个表上
`exec sp_bindrule 月份,'收费登记.月份'
exec sp_bindrule 月份,'订单管理.月份'
exec sp_bindrule 月份,'用水信息管理.月份'
exec sp_bindrule 月份,'费用管理.月份'`
* 1
* 2
* 3
* 4
两个存储过程如下,也很简单
`CREATE PROCEDURE 客户费用管理统计 /* 客户费用管理统计存储过程(应收费用,未收费用) */
@月份 varchar(20)
AS
SELECT 收费登记.客户号,收费登记.应收费用,收费登记.实收费用,费用管理.收费标志
FROM 收费登记,费用管理
WHERE 收费登记.月份=@月份 AND 收费登记.客户号=费用管理.客户号 AND 费用管理.月份=@月份
CREATE PROCEDURE 客户欠费统计 /* 客户欠费统计存储过程 */
@月份 varchar(20)
AS
SELECT 费用管理.客户号,客户信息.客户名,(应收费用-实收费用) 拖欠费用
FROM 费用管理,客户信息,收费登记
WHERE 客户信息.客户号=费用管理.客户号 AND 费用管理.客户号=收费登记.客户号 AND 收费登记.月份=费用管理.月份 AND 费用管理.月份=@月份`
* 1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
* 10
* 11
* 12
* 13
以上为我设计时编写的全部代码,如果想看下又实在不想一段一段复制的话这里有成品资源
问题及分析
在我写出来以后做了一些测试(毕竟这也是我的作业肯定要测试没什么问题才敢验收),出现了一些问题(当然是已修复的,起码我验收的时候没出现),在这里分享下(下面放出的图片均为本代码运行后得到的正常结果,错误结果以及原因用文字描述)
由图可知在2000年1月001号用户张三使用了150单位的生活用水,应收费用3000,结余金额自动支付500
当时出现了实收费用为100的情况,无论哪个客户(有结余金额)都是一样,或者是把所有客户的结余金额都吞了只算了第一个客户的结余金额用于缴费,这种情况是没有确定客户,应该去客户确定的语句那里查找错误
- 问题二:缴费大于应收费用时
张三充了7000元进去,结余金额应当是(7000+500-3000=4500),会出现结余为4600的情况,每个用户都是多100,这种情况我的解决方法是使用局部变量。先用@实收费用计算得到7500,再赋值给收费登记表中的实收费用 - 问题三:结余金额大于应收费用时
- 问题四:已充费后当月再次用水直至欠费时
先把二月份的充值到有结余金额
总结:目前实现了以下几个功能
(1)需要缴费时有结余金额自动用于缴费,多余的自动收回结余金额
(2)交费后盈余部分自动收入结余金额
(3)多个客户之间不冲突
(4)缴费后若当月再次用水需要缴费,重复(1)中步骤