数据库-库表设计 【分享一些库表设计经验】

数据库-库表设计 【分享一些库表设计经验】本文的核心内容:记录积累一些库表设计方案与技巧数据库实体与实体间的对应关系1)数据库表的菜单【分类】设计:如省市关联、图书的一、二级分类。2)数据库表设计之树形结构的表3)表的简化方案(特定情况,例如,用户触发过的场景记录表)4)数据库表设计之购物车,利用Session暂时存储购物车信息。

大家好,又见面了,我是你们的朋友全栈君。

本文的核心内容:记录积累一些库表设计方案与技巧

数据库实体与实体间的对应关系

1)数据库表的菜单【分类】设计:如省市关联、图书的一、二级分类。

2)数据库表设计之树形结构的表

3)表的简化方案(特定情况,例如,用户触发过的场景记录表)

4)数据库表设计之购物车,利用Session暂时存储购物车信息。


实体与实体间的对应关系

一对一

一对一,一般用于对主表的补充。假设A表为用户信息表,存储了用户的姓名、性别、年龄等基本信息。用户的家庭住址信息也属于用户的基本信息。我们可以选择将用户的家庭住址信息放到用户信息表,也可以单独建一张表,存储用户的家庭住址信息,以用户信息表的主键作为关联。

需不需要拆分取决:表信息的关联程度、表的字段个数限度。

一对多

一对多,是最常见的一种设计。就是 A 表的一条记录,对应 B 表的多条记录,且 A 的主键作为 B 表的外键。举几个  例子:

    1. 班级表 与 学生表,一个班级对应多个学生,或者多个学生对应一个班级。
    2. 角色表 与 用户表,一个角色对应多个用户,或者多个用户对应一个角色。
    1. 商品表 与 图片表,一个商品对应多张图片,或者多张图片对应一个商品。

多对多

构建一张关系表将两张表进行关联,形成多对多的形式。例如:

    1. 老师表、学生表;一个学生可以选修多个老师的课程、同时一个老师也可以教多个学生。
--教师表

CREATE TABLE #Teacher(TeacherId int, Name nvarchar(20));

INSERT INTO #Teacher VALUES(1, '张老师'), (2, '王老师');

--学生表

CREATE TABLE #Student(StudentId int, Name nvarchar(20));

INSERT INTO #Student VALUES(1, '小张'), (2, '小赵');

--老师学生关系表
CREATE TABLE #Teacher_Student(StudentId int, TeacherId int);

INSERT INTO #Teacher_Student VALUES(1, 1), (1, 2),(2, 1), (2, 2);

一:数据库表的菜单【分类】设计:如省市关联、图书的一、二级分类

数据库-库表设计 【分享一些库表设计经验】

BookType 一级分类:  少儿、外语、计算机

BookClass 二级分类: 少儿[0-2岁、3-6岁、7-10岁、11-14岁、儿童文学]

                                     外语[英语、日语、韩语、俄语、德语]

                                     计算机[计算机理论、计算机考试、数据库、人工智能、程序设计]

BookInf 图书详情 :    图书信息的详细字段。。。

基于以上关系:我们建表有两种方法

①:建立三张表 一级分类表,二级分类表、图书详情表

一级分类ID->作为二级分类的外键

二级分类ID->作为图书详情的外键

这一种依赖外键,实体模型也比较简单。(不再过多描述)

查询语句:可以采用 left join  on   或者  等值连接  将二级分类的外键与一级分类的主键等值连接即可查询。

②:建立两张表      一级分类和二级分类合并成一张表

                               图书详情表(引用TypeID为外键)

 数据库-库表设计 【分享一些库表设计经验】

TypeID        指一级二级分类的ID(唯一标识、主键) 序列自增从1开始。

TypeName  指一级二级分类的名字

ParentID     指二级分类所属一级分类TypeID (若为一级分类则填”0”与二级分类加以区分)

countNumber     指一级图书包含二级图书的个数

                           二级分类所包含详细图书的个数

 数据库-库表设计 【分享一些库表设计经验】

数据库查询一级分类信息的SQL

select typeid,typename,parentid,countnumber 
from t_booktype   where parentid='0'

数据查询二级分类信息(利用表的自连接)

select  child.typeid,child.typename,
child.parentid,child.countnumber 		
from t_booktype child ,t_booktype  parent 
where child.parentid=parent.typeid

二:MySQL 表中存储树形结构数据

由上面设计技巧引出,如果数据层级有多级呢?简言之就像一棵树一样,我们如何存储树形的数据到数据库。

存储父节点

存储于数据库中,最简单直接的方法,就是存储每个元素的父节点ID,即parent_Id->父节点Id。这种方式方便了插入,但是在某些情况下的查询会束手无策。我们可以增加两个字段(deep,is_leaf)帮助我们更快的查询。

deep=1表示父节点,deep>1 表示子节点。

id
parent_id
deep     //当前树的深度
is_leaf  //是否叶子节点

查询所有父节点deSQL如下:

select * from tree where deep=1

查询某个父节点下的所有子节点:

select * from tree where parent_id="" 

查询某个父节点下的所有后代节点,采用这种库表设计方式,这个需要依靠程序才能实现

存储路径

将存储根结点到每个节点的路径,这种数据结构,可以一眼就看出子节点的深度。要插入自己,然后查出父节点的Path,并且把自己生成的ID更新到path中去。

如果要查询某个节点下的子节点,只需要根据path的路径去匹配,比如要查询D节点下的所有子节点。

select * from tree where path like '1/4/%'

总结

我建议存储树形结构可以将两者结合起来。

id
parent_id
deep     //当前树的深度
path     //根路径
is_leaf  //是否叶子节点

三:MySQL 表的简化(特定情况,例如:用户触发过的场景记录表)

假设业务中有N多道具,比如用户首次使用某个道具触发特效。根据表里有没有用户相关的道具触发记录来完成判断。

id
user_id //用户Id
item_id //道具ID
flag    //是否触发过特效 0-1
add_time
update_time

毫无疑问,上述表结构是能够满足并实现我们需求的,但是如果有20种道具,那么每个用户最终将有20条数据,数据冗余,如何简化?

解决方案:

定义道具枚举值

public enum ItemOnceFlagEnum {
    
    NONE(0),
    
    ITEM_ONE(1),//道具1
    
    ITEM_TWO(1 << 1), //道具2
    
    ITEM_Three(1 << 2), //道具3
    
    ;
    
    private int code;
    
    ItemOnceFlagEnum(int code) {
        this.code = code;
    }
    public int getCode() {
        return code;
    }
    
    public static ItemOnceFlagEnum valueOf(int code) {
        ItemOnceFlagEnum[] values = ItemOnceFlagEnum.values();
        for (ItemOnceFlagEnum flag : values) {
            if (flag.getCode() == code) {
                return flag;
            }
        }
        return NONE;
    }
}

建表

id
user_id //用户Id
flag    //是否触发过特效 2的0次幂、2的1次幂。。。
add_time
update_time

user_id设置为唯一索引

 判断是否使用过某道具与添加道具使用记录

    //判断是否使用过某项道具
    public static boolean isHasThisFlag(long flag, ItemOnceFlagEnum itemOnceFlagEnum) {
        return (flag & itemOnceFlagEnum.getCode()) > 0;
    }

    //添加道具使用记录
    public Result<Boolean> addOnceFlag(long userId, itemOnceFlagEnum flag) {
         ItemOnceFlagDO itemOnceFlagDO = itmeOnceFlagService.getOnceFlagMap(Collections.singletonList(userId));
         long calculateFlag = (Objects.isNull(blockOnceFlagDO) ? 0L : itemOnceFlagDO.getFlag()) | flag.getCode();
         itemOnceFlag.setFlag(calculateFlag);
         boolean res = itemOnceFlagService.addOrUpdateOnceFlag(itemOnceFlag);
    }

添加记录SQL

"INSERT INTO xxxx ( ) VALUES ( :1.userId,  :1.flag ) " +
            " ON DUPLICATE KEY UPDATE flag = :1.flag "

某一项道具为具体2次幂值(仅能够维护2种状态,有或无),flag代表所有触发道具2次幂和。

通过 (flag & itemOnceFlagEnum.getCode())>0  判断是否有某项道具

通过 flag | temOnceFlagEnum.getCode();  添加道具记录


四:购物车模块的库表设计

在电商软件,必不可少的模块就是购物车。

我分享两种设计方法:

①:维护一张购物车表,以用户ID为外键

一个用户一个购物车,用户注册成功的同时,为用户在购物车表内维护一个专属于用户的购物车。(根据我以前学到的知识,这一步可以为用户表创建Insert触发法器,当用户注册成功[触发器将用户ID作为外键插入购物车表],用户即拥有了唯一的购物车)

T_Car

字段

类型

说明

Car_ID

Varchar2(36)

购物车编号  主键

User_ID

Varchar2(36)

外键  用户唯一标识

Car_Status

Varchar2(4)

购物车状态

T_Shop_Item

字段

类型

说明

Shop_item_ID

Varchar2(36)

购物项编号  主键

Car_ID

Varchar2(36)

购物车编号 外键

Product_ID

Varchar2(36)

商品编号   外键

Count

Number(4)

数量

Price

number(8,2)

价格

ProductName

Varchar(30)

商品名

这么实现购物车的弊端:①:非该网站的注册用户无法将商品加入购物车。这与现实的情况不符合。一般我们访问某宝,某东,我们可以以游客的方式将商品加入购物车,直到下订单、付款时才要求我们必须登录。②:每个用户维护一下购物车似乎不太明智,顾客将商品加入购物车到下订单,完成交易,这一需求对数据库更改频繁。

②:所有用户共用一个”购物车”

我们可以直接以用户ID为标识,区分购物车商品所属的用户。

T_Shop_Item

字段

类型

说明

Shop_item_ID

Varchar2(36)

购物项编号  主键

User_ID

Varchar2(36)

用户编号 外键

ProductID

Varchar2(36)

商品编号   外键

Count

Number(4)

数量

Price

number(8,2)

价格

ProductName

Varchar(30)

商品名

即使减少了一张购物车表,但是这表还需要用户登录才能记录用户添加商品情况。

③:利用Session暂时存储购物车内的东西

[用户不登录就能添加商品到购物车;用户登录状态将Session中的信息存入非关系型数据库、关系型数据库。将购物车内的东西持久化存储]

明确一点:Session -> 一次会话有效(在用户不关闭浏览器的前提下,默认存在30分钟;用户关闭浏览器再次打开后,未登录用户的购物车将清空。)

解决如何用Session存储购物车内信息。

第一个难点:那么如何准确区分不同的商品? (自然是商品ID)

第二个难点:那么如何准确标记一个进入购物车的商品? (只有商品ID是不行的)

商品ID,通过商品ID,我们可以查询到商品详情。(价格、名称等等)

同一商品的购买数量。这一信息是不存在于商品详情的。【重点,不能忽略】

这两个信息必须存储。于是我想到Map(两个原因)。

①:存储两个值

Map<String,Integer> map=new HashMap<String,Integer>();

key存储 商品ID,  value存储 商品数量(购买数量)

②:保证商品ID(键)的唯一性

Map的特点:键唯一,值可以重复。我们以商品ID为键,这样就可以保证商品的唯一性。

我们再将map存入Session中就可以了。

当用户添加商品时,只需先从Session中取出map,迭代遍历判断key是否已经存在,若存在取value值加1;若不存在则将商品ID作为key,value数量默认为1,Put进map。

当用户(未登录)查看购物车时,只需从Session中取商品ID和数量,就可以显示购车内商品的详细信息,计算购物车内的商品总价格。

这种存储方式简化了添加商品进入购物车和删除购物车里商品的操作。但是却不得不再次封装一个Map对象将购物车详情页面的信息存储进去,以供购物车展示页面显示数据。

(这是我第一次考虑的存储方案,写到查看购物车详情页面才发现不合理之处。)

改进版本:

Map <String,Goods> map=new HashMap<String,Goods> ();

Key ->商品id

Value->购物车页面需要展示的商品详情(商品名、商品ID、商品数量、商品价格等)

核心:value值维护不再是一个商品数量,而是我们封装的模拟购物车实体对象

数据库-库表设计 【分享一些库表设计经验】

JSP页面的效果图

数据库-库表设计 【分享一些库表设计经验】

源代码:添加商品的Action

	//添加商品Action
	public String addToCar() throws Exception{
		//尝试从Session取出购物车
		ValueStack vs=ActionContext.getContext().getValueStack();
		Map<String,Goods> car=(Map<String,Goods>)vs.findValue("#session.car");
		
		BookService service=new BookServiceImpl();
		
		if(car==null){	
			//判断购物车是否存在
			//不存在则创建,将用户添加的商品加入Map 存入Session
			Map<String,Goods> map=new HashMap<String,Goods>();
			//查询数据将商品详情查询出出来,把我们感兴趣的属性封装到Goods实体中
			Book book=service.getSingleBook(bookid);
			Goods goods=new Goods();
			goods.setBookid(book.getBookid());
			goods.setCount(1);
			goods.setPrice(book.getPrice());
			goods.setCurrentprice(book.getCurrentprice());
			goods.setProductname(book.getProductname());
			
			map.put(bookid, goods);
			vs.setValue("#session.car", map);
		}else{		
			//判断用户添加的商品是否已经存在于购物车里
			//若存在根据key取出Goods->修改商品选购的数量
			//不存在则是首次添加,数量默认为1 ,将商品Put入map
			if(car.containsKey(bookid)){
				Goods product=car.get(bookid);
				product.setCount(product.getCount()+1);
			}else{			
				Book book=service.getSingleBook(bookid);
				Goods goods=new Goods();
				goods.setBookid(book.getBookid());
				goods.setCount(1);
				goods.setPrice(book.getPrice());
				goods.setCurrentprice(book.getCurrentprice());
				goods.setProductname(book.getProductname());				
				car.put(bookid, goods);
			}
		}	
		return Action.SUCCESS;
	}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/151591.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 企业微信打开页面提示“请在微信客户端打开链接”

    企业微信打开页面提示“请在微信客户端打开链接”企业微信不支持打开需要带微信身份的链接。包括几种情况:访问链接为oauth2链接,appid填写为公众号appid。这种情况,建议开发者更换链接,或者将原公众号服务迁移到企业微信服务。 公众平台发布的文章,点击“阅读原文”。这种只能在微信客户端才能访问。官网链接:https://work.weixin.qq.com/api/doc/90001/90148/90457#%E4%BC%81%E4%B8%9A%E5%BE%AE%E4%BF%A1%E6%89%93%E5%BC%80%E9%A1%B5%.

  • Linux系统磁盘分区及挂载 – fdisk[通俗易懂]

    Linux系统磁盘分区及挂载 – fdisk[通俗易懂]本文关键字:文件系统,磁盘分区,格式化,挂载。当我们拿到一块新的硬盘时,他所能够支持的最大空间只是代表硬件上的一个参数,我们要想让他能够正常的工作起来,必须要有相应的文件系统。文件系统决定了文件存储和管理时的方式和数据结构,也就是如何管理磁盘上的文件和文件夹。不同的文件系统拥有不同的特点,这也就是为什么我们在进行格式化操作必须要选定一种文件系统的原因。

  • 2021最新激活码phpstorm破解方法「建议收藏」

    2021最新激活码phpstorm破解方法,https://javaforall.cn/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

  • vue富文本编辑器tinymce_vue移动端富文本编辑器

    vue富文本编辑器tinymce_vue移动端富文本编辑器vue富文本编辑器Vue-Quill-Editor

    2022年10月14日
  • 旁站C段探测_旁站平行检验还有什么

    旁站C段探测_旁站平行检验还有什么旁站查询什么是旁站?旁站是和目标网站在同一台服务器上的其它的网站。在线查询:https://www.webscan.cc/https://www.aizhan.com/C段查询什么是C段?C段服务器的本质是C类IP网段。C段资产是和目标服务器ip处在同一个C段的其它服务器。查询方式:北极熊扫描器Nmap…

    2022年10月22日
  • soapui的教程

    soapui的教程一.这里我安装的是5.2.1这个版本,安装之后按照我的操作步骤即可二.这里放入后台给你的接口,结尾应该是?wsdl如果没有你需要手动加上,否则会报错三:这个是成功界面四:如图,点开之后是这样的,在1的地方你需要输入对应的参数点击4进行查询,查询成功右侧会有对应的xml数据结果,具体参数需要输入什么,应用场景不同可以去问下后台这篇博客基本结合我的另一篇的ksoap2框架的博客一起使用…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号