SQL 格式化为“用户友好的日期”

Posted

技术标签:

【中文标题】SQL 格式化为“用户友好的日期”【英文标题】:SQL formatting to "user friendly date" 【发布时间】:2020-09-29 09:35:13 【问题描述】:

我有以下 SQL 来输出漂亮的工作日期版本:

SELECT GROUP_CONCAT(day(tbl_contract_dates.date) SEPARATOR ', ') as days,
       DATE_FORMAT(tbl_contract_dates.date, '%M') as month,
       DATE_FORMAT(tbl_contract_dates.date, '%Y') as year
FROM tbl_contract_dates 
WHERE tbl_contract_dates.contract_id = 34
GROUP BY DATE_FORMAT(tbl_contract_dates.date, '%M %Y') 
ORDER BY tbl_contract_dates.date;

这将输出如下内容:

+--------------------+-----------+------+
| days               | month     | year |
+--------------------+-----------+------+
| 8, 10, 11, 12, 16  | August    | 2020 |
| 20, 27, 28, 29, 30 | September | 2020 |
| 1, 2               | October   | 2020 |
+--------------------+-----------+------+

如果可能的话,我找不到一种让我的日子变得像这样的方法:

+--------------+-----------+------+
| days         | month     | year |
+--------------+-----------+------+
| 8, 10-12, 16 | August    | 2020 |
| 20, 27-30    | September | 2020 |
| 1-2          | October   | 2020 |
+--------------+-----------+------+

如您所见,我需要将连续数字简单地替换为“-”。

这可以在 SQL / mysql 中实现吗?运行 MySQL 5.5.28。我知道它很旧,但这就是我所拥有的并且无法升级... :(

感谢您的意见! 帕特

【问题讨论】:

你能用示例数据创建一个小提琴,以便我们尝试一下吗? 不确定我是否理解如何创建一个小提琴游乐场...我无法向外界开放对我的数据库的访问 ;) 见meta.***.com/questions/333952/… 指定精确 MySQL版本。 最简单的方法可能是编写一个函数,获取当前结果字符串并输出最终字符串。 (如果 MySQL 函数能够做到这一点;我不知道。)否则你最终会遇到间隙和孤岛问题,你可以用 MySQL 8 来解决这个问题。 【参考方案1】:

我将一些示例数据放入 WITH 子句中以创建游乐场。

这件事并不简单:你必须垂直工作一段时间,然后创建一个岛间隙模式,或者,正如其他人所说,“会话化”日期序列,然后才能创建从到文本。为了使答案更小,我只是进行会话,并显示垂直行,然后再次将其留给 GROUP_CONCAT() ....

这里是:

WITH
workdays(dt) AS (
          SELECT DATE '2020-08-08'
UNION ALL SELECT DATE '2020-08-10'
UNION ALL SELECT DATE '2020-08-11'
UNION ALL SELECT DATE '2020-08-12'
UNION ALL SELECT DATE '2020-08-16'
UNION ALL SELECT DATE '2020-09-20'
UNION ALL SELECT DATE '2020-09-27'
UNION ALL SELECT DATE '2020-09-28'
UNION ALL SELECT DATE '2020-09-29'
UNION ALL SELECT DATE '2020-09-30'
UNION ALL SELECT DATE '2020-10-01'
UNION ALL SELECT DATE '2020-10-02'
)
,
with_counter AS (
-- counter is 1 every time we have
-- more than one day as gap
  SELECT
    *
  , CASE WHEN LAG(dt) OVER(PARTITION BY MONTH(dt) ORDER BY dt) + 1 < dt
           OR LAG(dt) OVER(PARTITION BY MONTH(dt) ORDER BY dt) IS NULL
      THEN 1
      ELSE 0
    END AS counter
  FROM workdays
)
,
with_session AS (
  SELECT
    *
  , SUM(counter) OVER(ORDER BY MONTH(dt), DAY(dt)) AS session
  FROM with_counter
)
-- test query ...
-- SELECT * FROM with_session;
-- out      dt     | counter | session 
-- out ------------+---------+---------
-- out  2020-08-08 |       1 |       1
-- out  2020-08-10 |       1 |       2
-- out  2020-08-11 |       0 |       2
-- out  2020-08-12 |       0 |       2
-- out  2020-08-16 |       1 |       3
-- out  2020-09-20 |       1 |       4
-- out  2020-09-27 |       1 |       5
-- out  2020-09-28 |       0 |       5
-- out  2020-09-29 |       0 |       5
-- out  2020-09-30 |       0 |       5
-- out  2020-10-01 |       1 |       6
-- out  2020-10-02 |       0 |       6

SELECT 
    CAST(MIN(DAY(dt)) AS VARCHAR(2))
  ||CASE WHEN COUNT(*) = 1 THEN ''
      ELSE '-'||CAST(MAX(DAY(dt)) AS VARCHAR(2))
    END
  AS daylit
, DAY(MIN(dt)) AS d
, MONTH(MIN(dt)) AS mn
, TO_CHAR(MIN(dt),'Month') AS mth
, YEAR(MIN(dt)) AS yr
FROM with_session
GROUP BY session
ORDER BY 3,2
;
-- out  daylit | d  | mn |    mth    |  yr  
-- out --------+----+----+-----------+------
-- out  8      |  8 |  8 | August    | 2020
-- out  10-12  | 10 |  8 | August    | 2020
-- out  16     | 16 |  8 | August    | 2020
-- out  20     | 20 |  9 | September | 2020
-- out  27-30  | 27 |  9 | September | 2020
-- out  1-2    |  1 | 10 | October   | 2020

【讨论】:

【参考方案2】:

这看起来像是一个间隙和孤岛问题,您希望将同一个月的“相邻日期”组合在一起。

这是一种使用窗口函数的方法(这需要 MySQL 8.0):

select 
    group_concat(case when min_dt = max_dt then min_dt else concat(min_day, '-', max_day) order by min_day separator ',') as days, 
    year, 
    month
from (
    select 
        date_format(tbl_contract_dates.date, '%Y') year, 
        date_format(tbl_contract_dates.date, '%M') month, 
        min(day(dt)) min_day,
        max(day(dt)) max_day
    from (
        select t.*, row_number() over(partition by year(date), month(date) order by date) rn
        from tbl_contract_dates t
        where contract_id = 34
    ) t
    group by year, month, date_format(date, '%Y-%m-01') - interval rn day
) t
group by year, month

基本思想是使用月初和行号之间的差异来构建组。然后,您可以先按组汇总,然后按月汇总。


在早期版本中,我们可以使用子查询来模拟row_number();如果您有一个大型数据集,这可能无法很好地扩展:

select 
    group_concat(case when min_dt = max_dt then min_dt else concat(min_day, '-', max_day) order by min_day separator ',') as days, year, 
    month
from (
    select 
        date_format(tbl_contract_dates.date, '%Y') year, 
        date_format(tbl_contract_dates.date, '%M') month, 
        min(day(dt)) min_day,
        max(day(dt)) max_day
    from (
        select t.*, 
            (
                select count(*) 
                from tbl_contract_dates t1 
                where t1.contract_id = t.contract_id and t1.date >= date_format(t.date, '%Y-%m-01') and t1.date <= t.date
            ) rn
        from tbl_contract_dates t
        where contract_id = 34
    ) t
    group by year, month, date_format(date, '%Y-%m-01') - interval rn day
) t
group by year, month

【讨论】:

我真的希望这会起作用,但当然不是因为我的 MySQL 版本:5.5.28。我得到#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'order by min_day separator ',') as days @Pat:我用早期版本的方法编辑了我的答案

以上是关于SQL 格式化为“用户友好的日期”的主要内容,如果未能解决你的问题,请参考以下文章

将 SQL 日期格式化为英国格式 [重复]

SQL - 日期存储为 varchar 但格式化为 Julian Date

将 SQL 结果结构化为信封格式

根据 SQL 中的 id 将 JSON 格式化为嵌套数组

将 SQL 结果格式化为数组;通过密钥访问? [复制]

sql 将任何日期格式化为当年的日期(以计算闰年)