在SQL中将DateTime转换为“n Hours Ago”的最佳方法

在SQL中将DateTime转换为“n Hours Ago”的最佳方法

Best way to convert DateTime to “n Hours Ago” in SQL

我编写了一个SQL函数来将SQL中的日期时间值转换为更友好的"n Hours Ago"或"n Days Ago"等类型的消息。 我想知道是否有更好的方法来做到这一点。

(是的,我知道"不要在SQL中这样做",但出于设计原因,我必须这样做)。

这是我写的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
CREATE FUNCTION dbo.GetFriendlyDateTimeValue
(
    @CompareDate DateTime
)
RETURNS nvarchar(48)
AS
BEGIN
    DECLARE @Now DateTime
    DECLARE @Hours INT
        DECLARE @Suff nvarchar(256)
    DECLARE @Found bit

    SET @Found = 0
    SET @Now = getDate()
    SET @Hours = DATEDIFF(MI, @CompareDate, @Now)/60

    IF @Hours <= 1
    BEGIN
        SET @Suff = 'Just Now'
        SET @Found = 1
        RETURN @Suff
    END

    IF @Hours < 24
    BEGIN
        SET @Suff = ' Hours Ago'
        SET @Found = 1
    END

    IF @Hours >= 8760 AND @Found = 0
    BEGIN
        SET @Hours = @Hours / 8760
        SET @Suff = ' Years Ago'
        SET @Found = 1
    END

    IF @Hours >= 720 AND @Found = 0
    BEGIN
        SET @Hours = @Hours / 720
        SET @Suff = ' Months Ago'
        SET @Found = 1
    END

    IF @Hours >= 168 AND @Found = 0
    BEGIN
        SET @Hours = @Hours / 168
        SET @Suff = ' Weeks Ago'
        SET @Found = 1
    END

    IF @Hours >= 24 AND @Found = 0
    BEGIN
        SET @Hours = @Hours / 24
        SET @Suff = ' Days Ago'
        SET @Found = 1
    END

    RETURN CONVERT(nvarchar, @Hours) + @Suff
END

正如你所说,我可能不会在SQL中这样做,但是作为一个思考练习有一个MySQL实现:

1
2
3
4
5
6
7
8
9
10
11
12
CASE
    WHEN compare_date BETWEEN date_sub(now(), INTERVAL 60 MINUTE) AND now()
        THEN concat(MINUTE(TIMEDIFF(now(), compare_date)), ' minutes ago')

    WHEN datediff(now(), compare_date) = 1
        THEN 'Yesterday'

    WHEN compare_date BETWEEN date_sub(now(), INTERVAL 24 HOUR) AND now()
        THEN concat(HOUR(TIMEDIFF(NOW(), compare_date)), ' hours ago')

    ELSE concat(datediff(now(), compare_date),' days ago')
END

基于在MySQL日期和时间手册页上看到的类似示例


在Oracle中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SELECT
  CC.MOD_DATETIME,
  'Last modified ' ||
  CASE WHEN (sysdate - cc.mod_datetime) < 1
       THEN round((sysdate - CC.MOD_DATETIME)*24) || ' hours ago'
       WHEN (sysdate - CC.MOD_DATETIME) BETWEEN 1 AND 7
       THEN round(sysdate-CC.MOD_DATETIME) || ' days ago'
       WHEN (sysdate - CC.MOD_DATETIME) BETWEEN 8 AND 365
       THEN round((sysdate - CC.MOD_DATETIME) / 7) || ' weeks ago'
       WHEN (sysdate - CC.MOD_DATETIME) > 365  
       THEN round((sysdate - CC.MOD_DATETIME) / 365) || ' years ago'
       END
FROM
  customer_catalog CC


我的尝试 - 这是针对MS SQL的。它支持'前'和'从现在',复数,它不使用舍入或约会,但截断 - 约会在8/30和9/1之间给出1个月的差异,这可能不是你想要的。舍入在9/1和9/16之间提供1个月的差异。再一次,可能不是你想要的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CREATE FUNCTION dbo.GetFriendlyDateTimeValue( @CompareDate DATETIME ) RETURNS NVARCHAR(48) AS BEGIN
DECLARE @s nvarchar(48)
SET @s='Now'
SELECT top 1 @s=CONVERT(nvarchar,abs(n))+' '+s+CASE WHEN abs(n)>1 THEN 's' ELSE '' END+CASE WHEN n>0 THEN ' ago' ELSE ' from now' END FROM (
 SELECT CONVERT(INT,(CONVERT(FLOAT,(getdate()-@comparedate))*n)) AS n, s FROM (
  SELECT 1/365 AS n, 'Year' AS s UNION ALL
  SELECT 1/30, 'Month' UNION ALL
  SELECT 1, 'Day' UNION ALL
  SELECT 7, 'Week' UNION ALL
  SELECT 24, 'Hour' UNION ALL
  SELECT 24*60, 'Minute' UNION ALL
  SELECT 24*60*60, 'Second'
 ) k
) j WHERE abs(n)>0 ORDER BY abs(n)
RETURN @s
END

感谢上面发布的各种代码。

正如Hafthor指出的那样,原始代码与舍入有关。我还发现他的代码被踢出的一些结果与我所期望的结果不符。周五下午 - >周一早上将显示为"2天前"。我想我们都会在3天前打电话给我们,即使已经完成了3个完整的24小时。

所以我修改了代码(这是MS SQL)。免责声明:我是新手TSQL编码器,所以这很hacky,但工作!!

我做过一些改写 - 例如任何长达2周的东西都以天为单位表示。超过2个月的任何事情都以数周表示。任何事情都是在几个月等等。看起来似乎是表达它的直观方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
CREATE FUNCTION [dbo].[GetFriendlyDateTimeValue]( @CompareDate DATETIME ) RETURNS NVARCHAR(48) AS BEGIN
DECLARE @s nvarchar(48)

SET @s='Now'
SELECT top 1 @s=CONVERT(nvarchar,abs(n))+' '+s+CASE WHEN abs(n)>1 THEN 's' ELSE '' END+CASE WHEN n>0 THEN ' ago' ELSE ' from now' END FROM (
 SELECT CONVERT(INT,(CONVERT(FLOAT,(getdate()-@comparedate))*n)) AS n, s FROM (
  SELECT 1/365 AS n, 'year' AS s UNION ALL
  SELECT 1/30, 'month' UNION ALL
  SELECT 1/7, 'week' UNION ALL
  SELECT 1, 'day' UNION ALL
  SELECT 24, 'hour' UNION ALL
  SELECT 24*60, 'minute' UNION ALL
  SELECT 24*60*60, 'second'
 ) k
) j WHERE abs(n)>0 ORDER BY abs(n)

IF @s LIKE '%days%'
BEGIN
 -- if over 2 months ago then express in months
 IF CONVERT(nvarchar,DATEDIFF(MM, @CompareDate, GETDATE())) >= 2
 BEGIN
  SELECT @s = CONVERT(nvarchar,DATEDIFF(MM, @CompareDate, GETDATE())) + ' months ago'
 END

 -- if over 2 weeks ago then express in weeks, otherwise express as days
 ELSE IF CONVERT(nvarchar,DATEDIFF(DD, @CompareDate, GETDATE())) >= 14
 BEGIN
  SELECT @s = CONVERT(nvarchar,DATEDIFF(WK, @CompareDate, GETDATE())) + ' weeks ago'
 END

 ELSE
  SELECT @s = CONVERT(nvarchar,DATEDIFF(DD, @CompareDate, GETDATE())) + ' days ago'
END

RETURN @s
END

这个怎么样?您可以扩展此模式以执行"年"消息,并且您可以检查"1天"或"1小时",因此它不会说"1天前"...

我喜欢SQL中的CASE语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
DROP FUNCTION dbo.time_diff_message    
GO

CREATE FUNCTION dbo.time_diff_message (
    @input_date datetime
)
RETURNS VARCHAR(200)    
AS    
BEGIN    
DECLARE @msg VARCHAR(200)    
DECLARE @hourdiff INT

SET @hourdiff = datediff(HOUR, @input_date, getdate())    
SET @msg = CASE WHEN @hourdiff < 0 THEN ' from now' ELSE ' ago' END    
SET @hourdiff = abs(@hourdiff)    
SET @msg = CASE WHEN @hourdiff > 24 THEN CONVERT(VARCHAR, @hourdiff/24) + ' days' + @msg
                ELSE CONVERT(VARCHAR, @hourdiff) + ' hours' + @msg
            END

RETURN @msg
END

GO    
SELECT dbo.time_diff_message('Dec 7 1941')

您的代码看起来很实用至于更好的方式,那将是主观的。您可能希望查看此页面,因为它处理SQL中的时间跨度。


上面的帖子给了我一些好主意,所以这里是使用SQL Server 2012的任何人的另一个功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    CREATE FUNCTION [dbo].[FN_TIME_ELAPSED]
    (
        @TIMESTAMP DATETIME
    )
    RETURNS VARCHAR(50)
    AS
    BEGIN

    RETURN
    (
        SELECT TIME_ELAPSED =
            CASE
                WHEN @TIMESTAMP IS NULL THEN NULL
                WHEN MINUTES_AGO < 60 THEN CONCAT(MINUTES_AGO, ' minutes ago')
                WHEN HOURS_AGO < 24 THEN CONCAT(HOURS_AGO, ' hours ago')
                WHEN DAYS_AGO < 365 THEN CONCAT(DAYS_AGO, ' days ago')
                ELSE CONCAT(YEARS_AGO, ' years ago') END
        FROM ( SELECT MINUTES_AGO = DATEDIFF(MINUTE, @TIMESTAMP, GETDATE()) ) TIMESPAN_MIN
        CROSS APPLY ( SELECT HOURS_AGO = DATEDIFF(HOUR, @TIMESTAMP, GETDATE()) ) TIMESPAN_HOUR
        CROSS APPLY ( SELECT DAYS_AGO = DATEDIFF(DAY, @TIMESTAMP, GETDATE()) ) TIMESPAN_DAY
        CROSS APPLY ( SELECT YEARS_AGO = DATEDIFF(YEAR, @TIMESTAMP, GETDATE()) ) TIMESPAN_YEAR
    )
    END
    GO

并实施:

1
2
    SELECT TIME_ELAPSED = DBO.FN_TIME_ELAPSED(AUDIT_TIMESTAMP)
    FROM SOME_AUDIT_TABLE

推荐阅读