Лучший способ преобразовать дату и время в “n Часов назад” в SQL
Вопрос
Я написал SQL-функцию для преобразования значения datetime в SQL в более дружественный тип сообщения "n Часов назад" или "n Дней назад" и т.д.И мне было интересно, есть ли лучший способ сделать это.
(Да, я знаю, что "не делайте этого в SQL", но по соображениям дизайна я должен сделать это таким образом).
Вот функция, которую я написал:
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:
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:
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.Он поддерживает "назад" и "с этого момента", множественное число и не использует округление или datediff, но усечение -- datediff дает разницу в 1 месяц между 8/30 и 9/1, что, вероятно, не то, что вы хотите.Округление дает разницу в 1 месяц между 9/1 и 9/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
Ваш код выглядит функциональным.Что касается лучшего способа, то он будет субъективным.Возможно, вы захотите проверить это Страница поскольку это касается временных интервалов в SQL.
Как насчет этого?Вы могли бы расширить этот шаблон, чтобы создавать сообщения "years", и вы могли бы поставить галочку для "1 day" или "1 hour", чтобы не было написано "1 days ago"...
Мне нравится оператор CASE в SQL.
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')
Спасибо за различный код, размещенный выше.
Как указал Хафтор, в исходном коде существуют ограничения, связанные с округлением.Я также обнаружил, что некоторые результаты, которые выдал его код, не соответствовали тому, что я ожидал, напримерДень пятницы -> Утро понедельника будет отображаться как "2 дня назад".Я думаю, мы все назвали бы это 3 дня назад, хотя еще не прошло 3 полных 24-часовых периода.
Итак, я внес поправки в код (это MS SQL).Отказ от ответственности:Я начинающий программист TSQL, так что это довольно халтурно, но работает!!
Я сделал несколько переопределений - напримервсе, что длится до 2 недель, выражается в днях.Все, что превышает этот период до 2 месяцев, выражается в неделях.Все, что превышает это в месяцах и т.д.Просто мне показалось, что это самый интуитивный способ выразить это.
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
Приведенные выше сообщения дали мне несколько хороших идей, поэтому вот еще одна функция для всех, кто использует SQL Server 2012.
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
И реализация:
SELECT TIME_ELAPSED = DBO.FN_TIME_ELAPSED(AUDIT_TIMESTAMP)
FROM SOME_AUDIT_TABLE