【问题标题】:SQL formatting to "user friendly date"SQL 格式化为“用户友好的日期”
【发布时间】: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。我知道它很旧,但这就是我所拥有的并且无法升级... :(

感谢您的意见! 帕特

【问题讨论】:

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

标签: mysql sql datetime window-functions gaps-and-islands


【解决方案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:我用早期版本的方法编辑了我的答案
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-07
    • 1970-01-01
    相关资源
    最近更新 更多