如何进行时间登记?

How to take on time-registration?

我一直在寻找 StackExchange 的网站来讨论基本的想法,因为我几乎可以肯定我在一段时间前遇到过它,但我找不到任何测试版以外的网站,而且没有开发人员朋友深入讨论这个问题,让我来到 Whosebug 寻求帮助,所以这是我的问题。

事件

由于这些天我无事可做,所以我一直在研究 Toggle 和 Clockify 等应用程序,我想知道这些...

作为一个基本概念,用户可以为任何间隙注册 types 个事件,例如:

2017-12-28 00:00:00 ~ 2018-01-14 23:59:59 // vacation
2018-01-20 00:00:00 ~ 2018-01-20 23:59:59 // devOps event
2018-01-24 00:00:00 ~ 2018-01-24 12:00:00 // haircut

详细信息

如果我想要有关 1 月份事件的详细信息,我想得出的实际结论是

details = {
    events: [{...}, {...}, {...}],
    daysUsed: {
        vacation: 10, // holidays+weekends not counted
        internal: 1,
        personal: 0,5            
    }
}

我遇到的纸上问题

即使我 "saved" 数据是原样,例如在 RDS table 中是

// evtId | user | from | to | eventType | description

1 | 1 | 2017-12-28 00:00:00 | 2018-01-14 23:59:59 | V | vacation
2 | 1 | 2018-01-20 00:00:00 | 2018-01-20 23:59:59 | I | devOps event
3 | 1 | 2018-01-24 00:00:00 | 2018-01-24 12:00:00 | P | haircut

我无法使用日期时间来 select 从 1 月 1 日到 1 月 31 日的范围

WHERE userId = 1 and from >= '...' and to <= '...'

我需要将每个事件分散成 dailyEvent 方式,以便实际计算与天数相关的任何事情,例如

// eventId | date | evtType | dayType (holiday | weekend | vacation)

1 | 2017-12-28 | V | vacation
1 | 2017-12-29 | V | vacation
1 | 2017-12-30 | V | vacation & weekend
1 | 2017-12-31 | V | vacation & weekend
1 | 2018-01-01 | V | vacation & holiday
1 | 2018-01-02 | V | vacation
1 | 2018-01-03 | V | vacation
1 | 2018-01-04 | V | vacation
1 | 2018-01-05 | V | vacation
... 

有了这些数据,我就可以轻松地仅使用数据库进行一些计算,至少在应用程序中的日期和处理时间之间是这样

我查看了 MomentJs 库,看看我是否可以,例如,添加原始注册并在某种程度上获得我想要的结果,但它仅用于操作和计算日期,而不是日期范围......日历库会更有意义......没有找到任何 javascript 我可以看到他们解决问题的方法......

两个 table 而不是一个

我还考虑过在 (dailyEvent) 中设置一个辅助 table 时间间隔并每天用一个条目填充 table,这样可以更轻松地从 2 个日期检索数据...

问题是,当用户 edits/deletes 主要事件时,还有一个 table 需要维护...而且同步功能从来没有这么好用过 :/

你怎么看?


已添加

为了让问题更简单,这就是我要深入研究的问题

如何查询得到:

events 1, 2, 4 when query day 3
events 1, 2, 3 when query day 5
events 1, 2, 3 when query from days 4 to 7
events 1, 2 when query from days 10 to 14

这里有一个测试 ms-sql 可以玩:

Hostname    3b5a01df-b75c-41f3-9489-a8ad01604961.sqlserver.sequelizer.com   
Database    db3b5a01dfb75c41f39489a8ad01604961
Username    czhsclbcolgeznij    
Password    WrXF2Eozi2qWHAeYejFqRn8cPfQqZyh3FS2JteUHPZHnmoDhwxgVaeMJrVYUX6HR

在 pseudo-sql 中,要查找时间段的交集,您可以使用

select 
     *
from events
where startdate<=[end of day 3]
and enddate>=[start of day 3] 

好吧,任务无法在 SQL 中完全解决(从期望的输出来看,我猜你已经意识到了这一点)。另外,我不会给出确切的解决方案,因为它可能过于宽泛,但如果有任何问题,我很乐意提供帮助。我查看了您的帐户,发现您在 C# 中有金色徽章,所以如果有任何建议,我会参考这种语言。

首先,你想要的对象很复杂,所以让我假设这样的模型:

public class Summary
{
    public List<string> events { get; set; }
    public Dictionary<char, decimal> daysUsed { get; set; } 
        = new Dictionary<char, decimal> { { 'I', 0 }, { 'V', 0 }, { 'P', 0 } };
}

我认为您关于存储事件的第一个建议更好,我选择了这种方法。

DDL

declare @table table (evtId int, userId int, startDate datetime, endDate datetime, eventType char(1), [description] varchar(1000))
insert into @table values
(1 , 1 , '2017-12-28 00:00:00' , '2018-01-14 23:59:59' , 'V' , 'vacation'),
(2 , 1 , '2018-01-20 00:00:00' , '2018-01-20 23:59:59' , 'I' , 'devOps event'),
(3 , 1 , '2018-01-24 00:00:00' , '2018-01-24 12:00:00' , 'P' , 'haircut')

现在,让我们来看看你想做什么:

首先,声明您想要摘要的期间 - 它可以在 C# 中并通过一些 SQL 方法或一些 ORM 传递到 DB。为简单起见,我使用了 SQL:

declare @startDate datetime, @endDate datetime
select @startDate = '2018-01-01 00:00:00.000', @endDate = '2018-01-31 23:59:59.999'

哪个是你的例子(1 月 1 日至 31 日期间)。

现在让我们尝试获取我们需要的信息:

首先,最重要的部分是定义CTE,它将执行任务:

  1. 获取在给定时间段(一月)内开始或结束的所有事件。注意:部分在给定时间段内的事件也将包括在内

  2. 我们不需要给定时间段以外的任何数据,因此在 CTE 中,我们将用 1 月 1 日代替开始日期,即事件在给定时间段之前开始的时间。此外,我们将用结束日期代替,当活动在给定时间段后结束时,即 1 月 31 日

CTE:

;with cte as (
    select evtId,
           userId,
           case when startDate < @startDate then @startDate else startDate end [startDate],
           case when endDate > @endDate then @endDate else endDate end [endDate],
           eventType,
           [Description]
    from @table
    where (startDate between @startDate and @endDate) or
          (endDate between @startDate and @endDate)
)

现在,我们从您的 table 中获得了“干净”的数据。查询时间:

对于第一部分,即 events: [{...}, {...}, {...}],我想下面的查询就足够了:

select [description] from cte

此查询的结果可以存储在 Summary class 的 events 属性 中。

现在,我们需要填充 daysUsed 属性:

select eventType,
       round(1.0 * sum(datediff(minute, startDate, endDate)) / (60 * 24), 2) [daysUsed]
from cte
group by eventType

以上查询将 return 以下数据:

eventType|daysUsed
I        |1
P        |0.5
V        |14

一般来说,上述查询的结果总是:

eventType|daysUsed
I        |value
P        |value
V        |value

这个结果可以很容易地用于填充 daysUsed Summary class 中的属性。

最后,您可以将 Summary class 的对象序列化为 JSON。然后你就会得到想要的输出。

备注:

  1. C# class 只是一个示例,可以帮助我形象化地了解每一步我想做什么。不知道你用的是什么技术

  2. 我知道,第二次查询(填充 daysUsed)的结果与您的略有不同,因为我没有排除周末。我没有排除周末和节假日,因为这不是主要问题,我认为您可以轻松处理。

  3. 我用的是CTE,只能查询一次,所以严格来说,应该是一些临时的table或者CTE的内部查询可以作为其他查询中的子查询(在我看来这将是最简单的)。

如果我对问题的理解正确,您只是希望 return 在特定时期内活跃的事件。如果那是正确的,那么这是一个相对容易解决的问题。如果满足以下条件,我们可以认为事件在一段时间内处于活动状态:

  1. 事件的起始边缘或结束边缘包含在周期内或

  2. 事件跨越整个周期,在这种情况下,事件的开始边缘将小于周期的开始时间,事件的结束边缘将大于周期的结束时间。

以下示例演示:

-- -------------------------------------------------------
-- Creat sample table and data
-- -------------------------------------------------------
CREATE TABLE [dbo].[EventData](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [EventStart] [datetime] NOT NULL,
    [EventEnd] [datetime] NOT NULL,
 CONSTRAINT [PK_EventData] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO
SET IDENTITY_INSERT [dbo].[EventData] ON 

GO
INSERT [dbo].[EventData] ([ID], [EventStart], [EventEnd]) VALUES (1, CAST(N'2018-01-01T00:00:00.000' AS DateTime), CAST(N'2018-01-13T00:00:00.000' AS DateTime))
GO
INSERT [dbo].[EventData] ([ID], [EventStart], [EventEnd]) VALUES (2, CAST(N'2018-01-03T00:00:00.000' AS DateTime), CAST(N'2018-01-15T00:00:00.000' AS DateTime))
GO
INSERT [dbo].[EventData] ([ID], [EventStart], [EventEnd]) VALUES (3, CAST(N'2018-01-04T00:00:00.000' AS DateTime), CAST(N'2018-01-06T00:00:00.000' AS DateTime))
GO
INSERT [dbo].[EventData] ([ID], [EventStart], [EventEnd]) VALUES (4, CAST(N'2018-01-02T00:00:00.000' AS DateTime), CAST(N'2018-01-04T00:00:00.000' AS DateTime))
GO
SET IDENTITY_INSERT [dbo].[EventData] OFF
GO

 -- -------------------------------------------------------
-- Sample Query
-- -------------------------------------------------------

DECLARE @periodStart DATETIME = '10 Jan 2018';  -- Selection period start date and time
DECLARE @periodEnd DATETIME = '14 Jan 2018'; -- Selection period end date and time

SELECT  *
FROM    [EventData] [E]
WHERE   (
            (   -- Check if event start time is contained within period
                (@periodStart <= [E].[EventStart]) AND (@periodEnd > [E].[EventStart])
            ) 
            OR
            (
                -- Check if event end time is contained within period
                (@periodStart < [E].[EventEnd]) AND (@periodEnd > [E].[EventEnd])
            )
            OR
            (
                -- Check if the event spans the entire period
                (@periodStart >= [E].[EventStart]) AND (@periodEnd <= [E].[EventEnd])
            )
        )