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 格式化为“用户友好的日期”的主要内容,如果未能解决你的问题,请参考以下文章