Meilleur moyen de convertir DateTime en « n Hours Ago » dans SQL
Question
J'ai écrit une fonction SQL pour convertir une valeur datetime en SQL en un type de message plus convivial "il y a n heures" ou "il y a n jours", etc.Et je me demandais s'il y avait une meilleure façon de le faire.
(Oui, je sais "ne le faites pas en SQL" mais pour des raisons de conception, je dois le faire de cette façon).
Voici la fonction que j'ai écrite :
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
La solution
Comme vous le dites, je ne le ferais probablement pas en SQL, mais comme exercice de réflexion, ayez une implémentation 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
Basé sur un échantillon similaire vu sur le Date et heure MySQL pages de manuel
Autres conseils
Dans 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
Ma tentative - c'est pour MS SQL.Il prend en charge "il y a" et "à partir de maintenant", la pluralisation et n'utilise pas d'arrondi ou de datediff, mais la troncature - datediff donne 1 mois de différence entre le 30/08 et le 01/09, ce qui n'est probablement pas ce que vous voulez.L'arrondi donne un écart de 1 mois entre le 1/9 et le 16/9.Encore une fois, ce n'est probablement pas ce que vous voulez.
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
Votre code semble fonctionnel.Quant à une meilleure façon, cela va devenir subjectif.Vous voudrez peut-être vérifier ceci page car il traite des intervalles de temps en SQL.
Que dis-tu de ça?Vous pouvez étendre ce modèle pour créer des messages "années", et vous pouvez cocher "1 jour" ou "1 heure" afin qu'il ne dise pas "il y a 1 jour"...
J'aime l'instruction CASE dans 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')
Merci pour les différents codes postés ci-dessus.
Comme Hafthor l'a souligné, le code original présente des limites liées à l'arrondi.J'ai également constaté que certains des résultats renvoyés par son code ne correspondaient pas à ce à quoi je m'attendais, par ex.Vendredi après-midi -> Lundi matin s'afficherait comme « il y a 2 jours ».Je pense que nous appellerions tous cela il y a 3 jours, même si 3 périodes complètes de 24 heures ne se sont pas écoulées.
J'ai donc modifié le code (c'est MS SQL).Clause de non-responsabilité:Je suis un codeur TSQL novice donc c'est assez hacky, mais ça marche !!
J'ai effectué quelques remplacements - par ex.tout ce qui peut aller jusqu'à 2 semaines est exprimé en jours.Tout ce qui dépasse 2 mois est exprimé en semaines.Tout ce qui dépasse cela se compte en mois, etc.Cela semblait être la manière intuitive de l’exprimer.
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
Les articles ci-dessus m'ont donné de bonnes idées, voici donc une autre fonction pour toute personne utilisant 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
Et la mise en œuvre :
SELECT TIME_ELAPSED = DBO.FN_TIME_ELAPSED(AUDIT_TIMESTAMP)
FROM SOME_AUDIT_TABLE