【问题标题】:Create a date range in mysql在mysql中创建一个日期范围
【发布时间】:2011-01-10 02:50:17
【问题描述】:

动态创建日期范围以供报告使用的最佳方式。

因此,如果某一天没有活动,我可以避免在报告中出现空行。

主要是为了避免这个问题:What is the most straightforward way to pad empty dates in sql results (on either mysql or perl end)?

【问题讨论】:

  • gcb,您查看我修改后的答案了吗?
  • 现在回顾这个,我认为填补空白是由于表现层......
  • @code_burgar:在 MySQL 中是可以做到的,你甚至不需要过程,看这里stackoverflow.com/questions/2157282/…

标签: mysql datetime


【解决方案1】:

我的建议是:不要让你的生活变得更艰难,让生活变得更轻松。只需为每个日历日创建一个包含一行的表格,其中包含您认为合理需要持续的尽可能多的行。在数据仓库中,这是常见的解决方案,而且这种方式实现得如此广泛,以至于没有它的 dwh 会产生代码异味。

许多习惯于处理更传统的 oltp/数据输入应用程序的人会自然而然地反对这种想法,因为觉得无论如何都可以生成数据,因此不应该存储它。但是,如果您确实创建了一个这样的表格,您可以使用许多有用的属性来装饰它,例如是holdiday 还是周末,并且您可以在其中存储许多常见的日期表示(iso、欧洲、us 格式等),其中可以在创建报告时为您节省大量时间(因为您不必费心弄清楚日期格式在您使用的每个报告工具中是如何工作的。或者您可以更进一步,每天更新您的日期表以标记标志当日、当周、当月、当年等 - 各种有用的工具使构建需要针对某个日期范围工作的报告变得更加容易。

根据评论中的要求提供 MySQL 示例代码:

delimiter //

DROP PROCEDURE IF EXISTS p_load_dim_date
//

CREATE PROCEDURE p_load_dim_date (
    p_from_date DATE
,   p_to_date   DATE
)
BEGIN
    DECLARE v_date DATE DEFAULT p_from_date;
    DECLARE v_month tinyint;
    CREATE TABLE IF NOT EXISTS dim_date (
        date_key               int          primary key
    ,   date_value             date
    ,   date_iso               char(10)
    ,   year                   smallint
    ,   quarter                tinyint
    ,   quarter_name           char(2)
    ,   month                  tinyint
    ,   month_name             varchar(10)
    ,   month_abbreviation     varchar(10)
    ,   week                   char(2)
    ,   day_of_month           tinyint
    ,   day_of_year            smallint
    ,   day_of_week            smallint
    ,   day_name               varchar(10)
    ,   day_abbreviation       varchar(10)
    ,   is_weekend             tinyint
    ,   is_weekday             tinyint
    ,   is_today               tinyint
    ,   is_yesterday           tinyint
    ,   is_this_week           tinyint
    ,   is_last_week           tinyint
    ,   is_this_month          tinyint
    ,   is_last_month          tinyint
    ,   is_this_year           tinyint
    ,   is_last_year           tinyint
    );
    WHILE v_date < p_to_date DO
        SET v_month := month(v_date);
        INSERT INTO dim_date(
            date_key
        ,   date_value
        ,   date_iso
        ,   year
        ,   quarter
        ,   quarter_name
        ,   month
        ,   month_name
        ,   month_abbreviation
        ,   week
        ,   day_of_month
        ,   day_of_year
        ,   day_of_week
        ,   day_name
        ,   day_abbreviation
        ,   is_weekend
        ,   is_weekday
        ) VALUES (
            v_date + 0
        ,   v_date
        ,   DATE_FORMAT(v_date, '%y-%c-%d')
        ,   year(v_date)
        ,   ((v_month - 1) DIV 3) + 1
        ,   CONCAT('Q', ((v_month - 1) DIV 3) + 1)
        ,   v_month
        ,   DATE_FORMAT(v_date, '%M')
        ,   DATE_FORMAT(v_date, '%b')
        ,   DATE_FORMAT(v_date, '%u')
        ,   DATE_FORMAT(v_date, '%d')
        ,   DATE_FORMAT(v_date, '%j')
        ,   DATE_FORMAT(v_date, '%w') + 1
        ,   DATE_FORMAT(v_date, '%W')
        ,   DATE_FORMAT(v_date, '%a')
        ,   IF(DATE_FORMAT(v_date, '%w') IN (0,6), 1, 0)
        ,   IF(DATE_FORMAT(v_date, '%w') IN (0,6), 0, 1)
        );
        SET v_date := v_date + INTERVAL 1 DAY;
    END WHILE;
    CALL p_update_dim_date();
END;
//

DROP PROCEDURE IF EXISTS p_update_dim_date;
//

CREATE PROCEDURE p_update_dim_date()
    UPDATE dim_date
    SET    is_today         = IF(date_value = current_date, 1, 0)
    ,      is_yesterday     = IF(date_value = current_date - INTERVAL 1 DAY, 1, 0)
    ,      is_this_week     = IF(year = year(current_date) AND week = DATE_FORMAT(current_date, '%u'), 1, 0)
    ,      is_last_week     = IF(year = year(current_date - INTERVAL 7 DAY) AND week = DATE_FORMAT(current_date - INTERVAL 7 DAY, '%u'), 1, 0)
    ,      is_this_month    = IF(year = year(current_date) AND month = month(current_date), 1, 0)
    ,      is_last_month    = IF(year = year(current_date - INTERVAL 1 MONTH) AND month = month(current_date - INTERVAL 1 MONTH), 1, 0)
    ,      is_this_year     = IF(year = year(current_date), 1, 0)
    ,      is_last_year     = IF(year = year(current_date - INTERVAL 1 YEAR), 1, 0)
    WHERE  is_today
    OR     is_yesterday
    OR     is_this_week
    OR     is_last_week
    OR     is_this_month
    OR     is_last_month
    OR     is_this_year
    OR     is_last_year
    OR     IF(date_value = current_date, 1, 0)
    OR     IF(date_value = current_date - INTERVAL 1 DAY, 1, 0)
    OR     IF(year = year(current_date) AND week = DATE_FORMAT(current_date, '%u'), 1, 0)
    OR     IF(year = year(current_date - INTERVAL 7 DAY) AND week = DATE_FORMAT(current_date - INTERVAL 7 DAY, '%u'), 1, 0)
    OR     IF(year = year(current_date) AND month = month(current_date), 1, 0)
    OR     IF(year = year(current_date - INTERVAL 1 MONTH) AND month = month(current_date - INTERVAL 1 MONTH), 1, 0)
    OR     IF(year = year(current_date), 1, 0)
    OR     IF(year = year(current_date - INTERVAL 1 YEAR), 1, 0)
    ;
//

delimiter ;

使用 p_load_dim_date,您最初会使用 25 年的数据加载 dim_date 表。每天,最好在午夜时分,你运行p_update_dim_date。然后您可以使用标志字段 is_today、is_yesterday、is_this_week、is_last_week 等来选择常用范围。当然,您应该修改此代码以满足您的特定需求,但这就是想法。因此,没有即时生成范围,您只需提前足够长的时间预加载。对于一天中的时间,可以设置类似的设计 - 您应该能够通过此代码自行管理。

对于照顾假期的更奇特的日期维度以及月份和日期的本地化名称,您可以查看: http://rpbouman.blogspot.com/2007/04/kettle-tip-using-java-locales-for-date.htmlhttp://rpbouman.blogspot.com/2010/01/easter-eggs-for-mysql-and-kettle.html

【讨论】:

  • 愿意分享一个解决方案,以每小时间隔创建这样一个表吗?
  • gcb:如果您需要基于日期的日期范围,则每小时更新没有多大意义 - 您最终会在 24 次中进行 23 次相同的更新。如果您同时需要日期和时间范围,典型的解决方案是创建两个表 - 一个用于日历日(一行 = 1 天),一个用于一天中的时间(一行 = 适用于问题的最小间隔,但通常为 1 秒、1 分钟或 1 小时)。我可以将您链接到解决方案,但在这种情况下,如果我知道您使用的是什么数据库以及您的报告所基于的解决方案/语言会有所帮助。
  • 需要基于天和基于小时。我在 MySQL 上
  • gcb:我用可能的解决方案和更多链接更新了我的答案。
  • 酷。我最终将它改编成一个更简单的版本,但我肯定会保存它!谢谢!
【解决方案2】:

在 MySQL 中没有直接的方法可以做到这一点。最好的办法是使用您选择的服务器端语言生成一个 daterange 数组,然后从数据库中提取数据并使用日期作为键将结果数组与您的 daterange 数组合并。

您使用哪种服务器端语言?

编辑:

基本上你会做的是(伪代码):

// Create an array with all dates for a given range
dates = makeRange(startDate, endDate); 

getData = mysqlQuery('SELECT date, x, y, z FROM a WHERE a AND b AND c');

while (r = fetchRowArray(getData)) {

  dates[ date(r['date']) ] = Array ( x, y, z);

}

您最终会得到一个可以循环访问的日期数组,其中包含有或没有与之关联的活动数据的日期。

可以轻松修改为按小时分组/过滤数据。

【讨论】:

  • 那我必须每天使用一个查询?每小时的报告呢?
  • 您无需每天进行一次查询。我会在几秒钟内编辑我的答案。
  • 如果您在数据库中没有任何日期为“2010-01-01”的记录,那么您的循环将错过那一天,因为它正在循环从 mysql 查询返回的日期。跨度>
  • @gcb 这个想法实际上是让循环错过没有从 db 返回的天数,因此生成的日期范围。首先创建一个日期范围数组,然后将数据库中的数据与那些有数据的日期相关联,其余的为空,但仍然存在。 PS 即使这个问题被问到已经 10 个月了,今天有人对此表示赞同,我注意到我错过了你的评论,所以我觉得有必要做出解释。
  • 我现在可以看到,错过 DB 中的日子是正确的方法。如果表示层是相对的,则表示层应填写空白。
【解决方案3】:

尝试在 MySQL 存储例程中使用循环来创建日期范围:

   declare iterDate date;
   set iterDate = startDate;

   DROP TABLE IF EXISTS MyDates;
   create temporary table MyDates (
      theDate date
   );

   label1: LOOP
     insert into MyDates(theDate) values (iterDate); 
     SET iterDate = DATE_ADD(iterDate, INTERVAL 1 DAY);
     IF iterDate <= endDate THEN
        ITERATE label1;
     END IF;
     LEAVE label1;
   END LOOP label1;

   select * from MyDates;
   DROP TABLE IF EXISTS MyDates;

startDateendDate 构成范围的端点,并作为参数提供给例程。

【讨论】:

    【解决方案4】:

    我最近做了一些研究来寻找和评估可能的选择。 http://www.freeportmetrics.com/devblog/2012/11/02/how-to-quickly-add-date-dimension-to-pentaho-mondrian-olap-cube/.

    你可以使用:

    • 水壶
    • 退化的维度
    • lucidb 内置函数
    • 即将推出的蒙德里安内置函数
    • 您自己的自定义脚本来生成 SQL
    • 前面提到的mysql脚本

    请查看博文了解更多详情。它还包含 Roland 的 sql 脚本的改进版本,该脚本将自动计算给定列的日期范围并将其与日期维度连接。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-09-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-02
      相关资源
      最近更新 更多