前言:
今天和小伙伴们分享下时间维度表的应用,先说个简单的业务场景,有一张记录的用户注册信息,然后想在后台管理系统中开发一个可以看到每天注册用户数量的图表统计功能,那么你会怎么处理呢?
正文:
一、模拟场景
1.我们先看下用户表的数据
2.然后根据这张表开始查询每天的用户注册数据量
select count(1) as registerNum, gmt_date from user GROUP BY DATE_FORMAT(gmt_date,’%Y-%m-%d’)
查询结果:
我们会发现一个问题 ,虽然是统计出了注册数据,但是如果某天没有注册数据,那么对应的那天就没有统计数量,这样就会有问题了,因为我们想统计的是每天用户注册的数量,如果某天没有用户注册,那么当天的统计数量应该是0。
那么我门应该如何解决这个问题呢,这时候就引出了今天的主角——时间维度表。
二、时间维度表是什么?
简单的理解:就是一个记录每天时间相关信息的表。
我们可以先看下时间维表的结构和数据
三、如何解决我们开头的遇到的问题
有了这张表后,我们就可以比较容易的的统计每天新增的用户的数量了,只要去右连接这张表就可以啦。
select b.day,count(a.id) as registerNum from user a RIGHT JOIN dim_day b on DATE_FORMAT(a.gmt_date,’%Y-%m-%d’)=b.day GROUP BY b.day
看下sql的执行结果:
可以看出,如果当天没有用户注册,就会统计出当天新增数据为0的结果,这样就可以实现每天注册用户的数据量了。
在这里我只是简单的举了一个例子告诉大家时间维表的用处,大家根据具体的需求可以更灵活的去使用。
四、如何生成时间维表
1.建表语句
DROP TABLE IF EXISTS `dim_day`;
CREATE TABLE `day` (
`day_key` longtext COMMENT '日期yyyyMMdd',
`day` longtext COMMENT '日期yyyy-MM-dd',
`isworkday` int(11) DEFAULT NULL COMMENT '是否是工作日 1是0不是',
`week` int(11) DEFAULT NULL COMMENT '一年的第多少周',
`week_name` longtext COMMENT '一年的第多少周名称',
`weekofday` int(11) DEFAULT NULL COMMENT '周几',
`weekofday_name` longtext COMMENT '周名称',
`month` int(11) DEFAULT NULL COMMENT '月',
`month_name` longtext COMMENT '月名称',
`season` int(11) DEFAULT NULL COMMENT '季度',
`season_name` longtext COMMENT '季度名称',
`year` int(11) DEFAULT NULL COMMENT '年',
`year_name` longtext COMMENT '年名称',
`month_yyyy_mm` longtext,
`week_yyyy_ww` longtext,
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.生成几年数据的工具类
DateUtils
public class DateUtils {
static List<String> weekendIsWorkDateList = new ArrayList<String>();
public static final String YYYYMMDD = "yyyy-MM-dd";
public static final String YYYYMMDD_ZH = "yyyy年MM月dd日";
/**
* 获取年份
* @param date
* @return
*/
public static int getYear(Date date) {
Calendar c = Calendar.getInstance();
c.setTime(date);
int year = c.get(Calendar.YEAR);
return year;
}
// 获取月份
public static int getMonth(Date date) {
Calendar c = Calendar.getInstance();
c.setTime(date);
int month = c.get(Calendar.MONTH);
return month + 1;
}
// 获取周
public static int getWeekOfYear(Date date) {
Calendar c = Calendar.getInstance();
c.setTime(date);
int week_of_year = c.get(Calendar.WEEK_OF_YEAR);
if (c.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) {
week_of_year = week_of_year - 1;
}
week_of_year = week_of_year;
return week_of_year;
}
public static void addMini(Date date,int xx){
SimpleDateFormat sdf = new SimpleDateFormat("HHmm");
Calendar nowTime = Calendar.getInstance();
nowTime.setTime(date);
nowTime.add(Calendar.MINUTE, xx);
System.out.println(sdf.format(nowTime.getTime()));
}
public static void main(String[] args) throws ParseException {
Date date =new Date();
System.out.println(date.getMinutes());
}
/**
* 获取季度
*
* @param date
* @return
*/
public static int getSeason(Date date) {
int season = 0;
Calendar c = Calendar.getInstance();
c.setTime(date);
int month = c.get(Calendar.MONTH);
switch (month) {
case Calendar.JANUARY:
case Calendar.FEBRUARY:
case Calendar.MARCH:
season = 1;
break;
case Calendar.APRIL:
case Calendar.MAY:
case Calendar.JUNE:
season = 2;
break;
case Calendar.JULY:
case Calendar.AUGUST:
case Calendar.SEPTEMBER:
season = 3;
break;
case Calendar.OCTOBER:
case Calendar.NOVEMBER:
case Calendar.DECEMBER:
season = 4;
break;
default:
break;
}
return season;
}
public static Date parseDate(String strDate) {
return parseDate(strDate, null);
}
/**
* parseDate
*
* @param strDate
* @param pattern
* @return
*/
public static Date parseDate(String strDate, String pattern) {
Date date = null;
try {
if (pattern == null) {
pattern = YYYYMMDD;
}
SimpleDateFormat format = new SimpleDateFormat(pattern);
date = format.parse(strDate);
} catch (Exception e) {
}
return date;
}
/**
* format date
*
* @param date
* @return
*/
public static String formatDate(Date date) {
return formatDate(date, null);
}
/**
* format date
*
* @param date
* @param pattern
* @return
*/
public static String formatDate(Date date, String pattern) {
String strDate = null;
try {
if (pattern == null) {
pattern = YYYYMMDD;
}
SimpleDateFormat format = new SimpleDateFormat(pattern);
strDate = format.format(date);
} catch (Exception e) {
System.out.println("formatDate error:"+e);
}
return strDate;
}
/**
* @title 判断是否为工作日
* @detail 工作日计算: 1、正常工作日,并且为非假期 2、周末被调整成工作日
* @param dateUtils
* 日期
* @return 是工作日返回true,非工作日返回false
*/
public static boolean isWorkday(GregorianCalendar calendar) {
SimpleDateFormat dataFormat = new SimpleDateFormat("yyyy-MM-dd");
boolean isWeekend = calendar.get(GregorianCalendar.DAY_OF_WEEK) != GregorianCalendar.SATURDAY && calendar.get(GregorianCalendar.DAY_OF_WEEK) != GregorianCalendar.SUNDAY;
if (isWeekend) {
return !getWeekdayIsHolidayList().contains(dataFormat.format(calendar.getTime()));
} else {
return getWeekendIsWorkDateList().contains(dataFormat.format(calendar.getTime()));
}
}
/**
* @title 获取周六和周日是工作日的情况(手工维护)
* @return 周末是工作日的列表
*/
public static List<String> getWeekendIsWorkDateList() {
List<String> list = new ArrayList<String>();
list.add("2019-02-02");
list.add("2019-02-03");
list.add("2019-05-04");
list.add("2019-05-05");
list.add("2019-09-28");
list.add("2019-09-29");
return list;
}
/**
* @title 获取周一到周五是假期的情况(手工维护)
* @return 平时是假期的列表
*/
public static List<String> getWeekdayIsHolidayList() {
if (weekendIsWorkDateList.isEmpty()) {
weekendIsWorkDateList.add("2019-01-01");
weekendIsWorkDateList.add("2019-02-04");
weekendIsWorkDateList.add("2019-02-05");
weekendIsWorkDateList.add("2019-02-06");
weekendIsWorkDateList.add("2019-02-07");
weekendIsWorkDateList.add("2019-02-08");
weekendIsWorkDateList.add("2019-04-05");
weekendIsWorkDateList.add("2019-05-01");
weekendIsWorkDateList.add("2019-05-02");
weekendIsWorkDateList.add("2019-05-03");
weekendIsWorkDateList.add("2019-06-07");
weekendIsWorkDateList.add("2019-09-13");
weekendIsWorkDateList.add("2019-10-01");
weekendIsWorkDateList.add("2019-10-02");
weekendIsWorkDateList.add("2019-10-03");
weekendIsWorkDateList.add("2019-10-04");
weekendIsWorkDateList.add("2019-10-07");
}
return weekendIsWorkDateList;
}
}
WeekdayUtil
public class WeekdayUtil {
/**
* @title 判断两个日期是否在指定工作日内
* @detail (只计算周六和周日)
* 例如:前时间2008-12-05,后时间2008-12-11
* @author chanson
* @param beforeDate 前时间
* @param afterDate 后时间
* @param deadline 最多相隔时间
* @return 是的话,返回true,否则返回false
*
*/
static String[] weekDays = {"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
static int[] intweekDays = {7,1,2,3,4,5,6};
static String filePath="D:\\dim_day.txt";
static FileOutputStream fos = null;
static {
try {
fos = new FileOutputStream(filePath);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public boolean compareWeekday(String beforeDate, String afterDate, int deadline) {
SimpleDateFormat dataFormat = new SimpleDateFormat("yyyy-MM-dd");
try {
Date d1 = dataFormat.parse(beforeDate);
Date d2 = dataFormat.parse(afterDate);
//工作日
int workDay = 0;
GregorianCalendar gc = new GregorianCalendar();
gc.setTime(d1);
// 两个日期相差的天数
long time = d2.getTime() - d1.getTime();
long day = time / 3600000 / 24 + 1;
if(day < 0){
//如果前日期大于后日期,将返回false
return false;
}
for (int i = 0; i < day; i++) {
if(DateUtils.isWorkday(gc)){
workDay++;
// System.out.println(dataFormat.format(gc.getTime())+" 工作日+DAY_OF_WEEK: " + weekDays[gc.get(Calendar.DAY_OF_WEEK)-1]);
//buidWeekDayJson(dataFormat, gc,1);
buidWeekDaySql(gc,1);
} else{
// buidWeekDayJson( dataFormat,gc,0);
buidWeekDaySql(gc,0);
}
//往后加1天
gc.add(GregorianCalendar.DATE, 1);
}
return workDay <= deadline;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
private void buidWeekDaySql( GregorianCalendar gc,int isWorkDay) {
String day_key=DateUtils.formatDate(gc.getTime(), "yyyyMMdd");
String date=DateUtils.formatDate(gc.getTime(), "yyyy-MM-dd");
int weekofday=intweekDays[gc.get(Calendar.DAY_OF_WEEK)-1];
String weekofday_name=weekDays[gc.get(Calendar.DAY_OF_WEEK)-1];
int week=DateUtils.getWeekOfYear(gc.getTime());
int season=DateUtils.getSeason(gc.getTime());
int month=DateUtils.getMonth(gc.getTime());
int year=DateUtils.getYear(gc.getTime());
String month_yyyy_mm=null;
String week_yyyy_ww=null;
if(month<10){
month_yyyy_mm=year+"-0"+month;
}else{
month_yyyy_mm=year+"-"+month;
}
if(week<10){
week_yyyy_ww=year+"-0"+week;
}else{
week_yyyy_ww=year+"-"+week;
}
StringBuffer buffer=new StringBuffer(" INSERT INTO dim.dim_day_new "
+ "(day_key,day,isworkday,week,week_name,weekofday,weekofday_name,month,month_name,season,season_name,year,year_name,month_yyyy_mm,week_yyyy_ww) VALUES ('");
buffer.append(day_key).append("','"). append(date).append("','").
append(isWorkDay).append("','").
append(week).append("','").append("第"+week+"周").append("','").
append(weekofday).append("','").append(weekofday_name).append("','").
append(month).append("','").append(month+"月").append("','").
append(season).append("','").append("第"+season+"季度").append("','").
append(year).append("','").append(year+"年").append("','").
append(month_yyyy_mm).append("','").append(week_yyyy_ww).append("');");
try {
fos.write(buffer.toString().getBytes());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println (buffer);
}
private void buidWeekDayJson(SimpleDateFormat dataFormat, GregorianCalendar gc,int isWeekday) {
String date=dataFormat.format(gc.getTime());
String week=weekDays[gc.get(Calendar.DAY_OF_WEEK)-1];
StringBuffer buffer=new StringBuffer();
int season=DateUtils.getSeason(gc.getTime());
int month=DateUtils.getMonth(gc.getTime());
buffer.append("{\"day\":\"").append(date).append("\",\"isworkday\":").append(isWeekday).
append(",\"week\":\"").append(week).
append("\",\"month\":").append(month).
append(",\"season\":").append(season).
append("}");
System.out.println (buffer);
}
public static void main(String[] args) throws IOException {
WeekdayUtil dateUtils = new WeekdayUtil();
// FileOutputStream fos = new FileOutputStream(filePath);
dateUtils.compareWeekday("2020-01-01", "2020-12-31", 10);
fos.close();
}
}
3.DateUtils里需要引用 WeekdayUtil ,然后在DateUtils的main方法里,写下开始时间和结束时间即可。
public static void main(String[] args) throws IOException {
WeekdayUtil dateUtils = new WeekdayUtil();
dateUtils.compareWeekday("2020-01-01", "2020-12-31", 10);
fos.close();
}
由于每年的节假日都有变化,所以需要在 DateUtils里的getWeekendIsWorkDateList()和getWeekdayIsHolidayList()方法里手动的去修正具体的节假日期。
/**
* @title 获取周六和周日是工作日的情况(手工维护)
* @return 周末是工作日的列表
*/
public static List<String> getWeekendIsWorkDateList() {
List<String> list = new ArrayList<String>();
list.add("2019-02-02");
list.add("2019-02-03");
list.add("2019-05-04");
list.add("2019-05-05");
list.add("2019-09-28");
list.add("2019-09-29");
return list;
}
/**
* @title 获取周一到周五是假期的情况(手工维护)
* @return 平时是假期的列表
*/
public static List<String> getWeekdayIsHolidayList() {
if (weekendIsWorkDateList.isEmpty()) {
weekendIsWorkDateList.add("2019-01-01");
weekendIsWorkDateList.add("2019-02-04");
weekendIsWorkDateList.add("2019-02-05");
weekendIsWorkDateList.add("2019-02-06");
weekendIsWorkDateList.add("2019-02-07");
weekendIsWorkDateList.add("2019-02-08");
weekendIsWorkDateList.add("2019-04-05");
weekendIsWorkDateList.add("2019-05-01");
weekendIsWorkDateList.add("2019-05-02");
weekendIsWorkDateList.add("2019-05-03");
weekendIsWorkDateList.add("2019-06-07");
weekendIsWorkDateList.add("2019-09-13");
weekendIsWorkDateList.add("2019-10-01");
weekendIsWorkDateList.add("2019-10-02");
weekendIsWorkDateList.add("2019-10-03");
weekendIsWorkDateList.add("2019-10-04");
weekendIsWorkDateList.add("2019-10-07");
}
return weekendIsWorkDateList;
}
4.执行DateUtils的main方法,打印出时间维表的sql,放到Navicat里执行即可。