数据库课程设计:某自来水公司收费管理系统(SQL Server)

要求

  • 实现客户信息、用水类型(类别号、类别名、水价)及业务员管理;
  • 实现客户用水信息管理(客户号、月份、用水类别号、用水量);
  • 实现客户费用管理(客户号、月份、费用、收费标志),收费标志的默认值为‘未收’;
  • 实现收费登记(客户、月份、应收费用、实收费用、业务员),并自动修改收费标志(用触发器实现);
  • 创建触发器,实现收费时自动更加应收费用和实收费用,计算本次结余,然后修改客户信息表中的结余金额;
  • 创建存储过程统计指定月份应收费用和实收费用;
  • 创建存储过程查询指定月份未交费的用户信息,以便催费;
  • 创建规则使得月份符合格式“xxxx年xx月”,并绑定到表中相应字段;
  • 建立表间关系

设计

思路

每个要求创建一个相应的表,建立联系,再根据具体需求选择增加或者删除表
我的理解是有这么几个要求:

  • 客户信息
  • 用水类型
  • 业务员管理

这三个是基本信息表

  • 用水信息管理
  • 费用管理
  • 收费登记

这三个是管理表,具体表中的属性题目中已经给出
由于要收费自动更加应收费用和实收费用,且要计算结余并更新,所以我选择增加一个表

  • 订单管理

这样的话,整体的结构是这样的:

  1. 基本信息表用于建立客户、业务员以及水类的基本信息供管理表引用
  2. 用水信息管理负责进行水费的计算并赋给收费登记的应收费用(实现应收费用的自动更加)
  3. 订单管理负责将收取的费用赋给收费登记的实收费用(实现实收费用的自动更加)
  4. 收费登记负责统计应收费用和实收费用并进行比较用于确定结余金额以及是否已收费

根据以上思路,就能知道大概需要多少个触发器

  • 用水信息管理需要一个
  • 订单管理需要一个
  • 收费登记需要至少一个

代码

首先是创建表(虽然这个应该都会,触发器里面用到局部变量需要格式相同我就一起写在这)

/* 由于方便自己看(中文不会一时宕机不记得这是什么表)就全部用中文建表了 */
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(订单号)
)

接下来是触发器部分,本题我用了四个,实际应该可以调整一下结构,可以减少或者改变一下每个触发器的功能的

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

这两个触发器是用于“用水信息管理”的,目的是计算水价然后将计算后的水价输入到“收费登记”表中的“应收费用
实际上这两个触发器可以合并,之前是不知道怎么判断是该插入还是更改数据,后面知道了也懒得改了

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 @收费标志='未收费'		/* 这里由于收费标志已经默认是“未收费,所以其实可以不用这个else的” */
		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

这个触发器就是合并了插入和更新的,它是作用于“收费登记“”表的,主要作用是:

  • 收费登记中的记录有插入或者更改时,判断此时客户是否有结余金额,有的话就把结余金额置入“实收费用”中用于支付费用
  • 然后比较此时的“应收费用”和“实收费用”。如果应收费用大于实收费用,那就是还没交完费,收费标志保持“未收费”;如果应收费用小于等于实收费用,那就是交完了费,收费标志变成“已收费”,把实收费用减去应收费用,剩下的置入结余金额中
  • 如果是自己缴费也是同理,订单管理得到的费用置入了“实收费用”,结余金额不够的情况下,实收费用=结余金额提供的+订单管理提供的费用,比较同上

这里我是认为一个触发器可以,但是要分的话好像也可以分成两个触发器

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

这个触发器就是订单管理的触发器,很简单,就是订单管理输入数据,然后将输入的费用置入“收费登记”表中的“实收金额

最后就是创建规则和存储结构

CREATE RULE 月份			/* 规则 */
AS
@月份 like'%年%月'

规则我是直接某年某月就行,没有具体要求必须是xxxx(四个x)年xx(两个x)月
然后和月份有关的就是下面这个四个表,所以应用在这四个表上

exec sp_bindrule 月份,'收费登记.月份'
exec sp_bindrule 月份,'订单管理.月份'
exec sp_bindrule 月份,'用水信息管理.月份'
exec sp_bindrule 月份,'费用管理.月份'

两个存储过程如下,也很简单

CREATE PROCEDURE 客户费用管理统计								/* 客户费用管理统计存储过程(应收费用,未收费用) */
@月份 varchar(20)
AS
SELECT 收费登记.客户号,收费登记.应收费用,收费登记.实收费用,费用管理.收费标志
FROM 收费登记,费用管理
WHERE 收费登记.月份=@月份 AND 收费登记.客户号=费用管理.客户号 AND 费用管理.月份=@月份

CREATE PROCEDURE 客户欠费统计									/* 客户欠费统计存储过程 */
@月份 varchar(20)
AS
SELECT 费用管理.客户号,客户信息.客户名,(应收费用-实收费用) 拖欠费用
FROM 费用管理,客户信息,收费登记
WHERE 客户信息.客户号=费用管理.客户号 AND 费用管理.客户号=收费登记.客户号 AND 收费登记.月份=费用管理.月份 AND 费用管理.月份=@月份

以上为我设计时编写的全部代码,如果想看下又实在不想一段一段复制的话这里有[成品资源](https://download.csdn.net/download/qq_44184756/14036244)

问题及分析

在我写出来以后做了一些测试(毕竟这也是我的作业肯定要测试没什么问题才敢验收),出现了一些问题(当然是已修复的,起码我验收的时候没出现),在这里分享下(下面放出的图片均为本代码运行后得到的正常结果,错误结果以及原因用文字描述

问题一
首先,先创建以上一些基本信息
接下来是问题

  • 问题一:刚插入用水信息,结余金额用于充费时
    在这里插入图片描述在这里插入图片描述

由图可知在2000年1月001号用户张三使用了150单位的生活用水,应收费用3000,结余金额自动支付500
当时出现了实收费用为100的情况,无论哪个客户(有结余金额)都是一样,或者是把所有客户的结余金额都吞了只算了第一个客户的结余金额用于缴费,这种情况是没有确定客户,应该去客户确定的语句那里查找错误

  • 问题二:缴费大于应收费用时

    在这里插入图片描述
    张三充了7000元进去,结余金额应当是(7000+500-3000=4500),会出现结余为4600的情况,每个用户都是多100,这种情况我的解决方法是使用局部变量。先用**@实收费用**计算得到7500,再赋值给收费登记表中的实收费用

  • 问题三:结余金额大于应收费用时
    在这里插入图片描述
    这种情况出现过当下次再用水到不够时,之前的实收费用也变成最后未收费的费用(下面的是目前已修复后得到的理想结果)
    在这里插入图片描述在这里插入图片描述
    这种情况是收费登记的记录没有确定,我没记错的话应该是用下面红线圈的部分确定的
    在这里插入图片描述

  • 问题四:已充费后当月再次用水直至欠费时
    先把二月份的充值到有结余金额
    在这里插入图片描述
    在这里插入图片描述
    此时张三还剩余3500,如果3月用水量增加到400单位(水价=400*20=8000>3500+3000)
    结果为
    在这里插入图片描述
    在这里插入图片描述
    这是正常情况,张三欠费,结余金额为0,也显示未收费
    在之前的几个版本里出现了更改后结余金额仍为3500,实收费用仍为3000,显示未收费情况,再充值5500后(即实收费用为5500+3000=8500),此时结余金额应为(8500-8000+3500=4000),但实际结果为500(5500+3000-8000=500)
    这里应该是结余金额的判断出现问题,问题大概率是出现在收费记录里面关于结余金额的取值(也就是说没有取到,后面结余时直接替换了结余金额),这里我是改了下面画圈的部分来确定结余金额,后面好像也改了一点,不过是局部变量的改变(比如结余金额的赋值先赋给@结余金额再给结余金额)和客户身份的进一步确定避免加错结余金额,应该关系不大。这个问题是我后面准备死马当活马医直接验收和老师说这个问题的时候发现好了,所以只知道是这里除了问题,解决方法不是很清楚
    在这里插入图片描述

总结:目前实现了以下几个功能
(1)需要缴费时有结余金额自动用于缴费,多余的自动收回结余金额
(2)交费后盈余部分自动收入结余金额
(3)多个客户之间不冲突
(4)缴费后若当月再次用水需要缴费,重复(1)中步骤

因为我自己弄得时候也出现了不少问题,所以我知道如果这些地方(或类似
的)出现问题一时半会找不到解决方法会很难受,所以这里整理了一下我碰到的问题及解决方法。本人对SQL Server及代码的编写也不是很熟悉,如果有错误或遗漏的还望指出,也欢迎提出别的问题以及解决方案,亦或者是一些改进的建议。


后续补充

2022.5.23 附
因为有人问到这个的E-R图,我去翻了下,当时我交实验报告的时候画的图应该是这样的:
E-R图
不过我已经有段时间没碰这个了,当时的思路有没有问题我也不确定。姑且说下我的理解,这里的整个系统应该是以某个或某些表为核心,其他表围绕核心表执行相应功能,可以这么理解:核心表是主程序,其余表则是核心表需要的一个个功能封装成的函数。在这里我的核心是收费登记,但是实际要做成个系统的话,可能要把用水信息管理作为核心更正确点。

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐