如何巩固时间块?

时间:2014-07-24 21:43:48

标签: sql sql-server tsql

我有一个派生表,其中列出了外键(ID)的相对秒数:

CREATE TABLE Times (
    ID INT
  , TimeFrom INT
  , TimeTo INT
);

该表主要包含非重叠数据,但有时候我有TimeTo< TimeFrom的另一条记录:

+----+----------+--------+
| ID | TimeFrom | TimeTo |
+----+----------+--------+
| 10 | 10       | 30     |
| 10 | 50       | 70     |
| 10 | 60       | 150    |
| 10 | 75       | 150    |
| .. | ...      | ...    |
+----+----------+--------+

结果集是一个扁平的线性空闲报告,但由于这些重叠太多,我最终会使用负时间。即如果ID = 10上方的窗口长达150秒,并且我总结了从窗口大小中减去的相对秒数的差异,那么我将结束150-(20+20+90+75)=-55。我尝试过这种方法,这就是让我意识到需要平整的重叠。

所以,我正在寻找的解决方案是将重叠平铺成一组:

+----+----------+--------+
| ID | TimeFrom | TimeTo |
+----+----------+--------+
| 10 | 10       | 30     |
| 10 | 50       | 150    |
| .. | ...      | ...    |
+----+----------+--------+

注意事项:性能在这里非常重要,因为这是一个较大的查询的一部分,它本身就能很好地运行,如果我能帮助它,我宁愿不要影响它的性能。

关于“哪个秒有间隔”的评论,这是我为最终结果尝试的东西,我正在寻找性能更好的东西。适应我的例子:

SELECT SUM(C.N)
FROM (
    SELECT A.N, ROW_NUMBER()OVER(ORDER BY A.N) RowID
    FROM 
          (SELECT TOP 60 1 N FROM master..spt_values) A
        , (SELECT TOP 720 1 N FROM master..spt_values) B
    ) C
WHERE EXISTS (
        SELECT 1
        FROM Times SE
        WHERE SE.ID = 10
            AND SE.TimeFrom <= C.RowID
            AND SE.TimeTo >= C.RowID
            AND EXISTS (
                SELECT 1
                FROM Times2 D
                WHERE ID = SE.ID
                    AND D.TimeFrom <= C.RowID
                    AND D.TimeTo >= C.RowID
            )
        GROUP BY SE.ID
    )

我对此解决方案的问题是,我在查询计划中的EXISTS查询中获得了一个行计数假脱机,其执行次数等于COUNT(C. *)。我在该查询中留下了实数,以说明绕过这种方法是最好的。因为即使使用Row Count Spool也会大大降低查询成本,因此执行计数也会大大增加查询的成本。

进一步编辑:最终目标是将其放在一个过程中,因此表变量和临时表也是一种可能使用的工具。

5 个答案:

答案 0 :(得分:2)

行。我仍然只想用一个SELECT来做这件事。但这完全奏效:

DECLARE @tmp TABLE (ID INT, GroupId INT, TimeFrom INT, TimeTo INT)

INSERT INTO @tmp
    SELECT ID, 0, TimeFrom, TimeTo 
    FROM Times
    ORDER BY Id, TimeFrom

DECLARE @timeTo int, @id int, @groupId int

SET @groupId = 0

UPDATE @tmp
SET 
    @groupId = CASE WHEN id != @id THEN 0 
                    WHEN TimeFrom > @timeTo THEN @groupId + 1 
                    ELSE @groupId END,
    GroupId = @groupId,
    @timeTo = TimeTo,
    @id = id    

SELECT Id, MIN(TimeFrom), Max(TimeTo) FROM @tmp 
GROUP BY ID, GroupId ORDER BY ID

答案 1 :(得分:1)

将每一行连接到相同ID值的后继重叠行(如果存在)。

现在,对于LHS left join RHS结果集中的每一行,对ID的已用时间的贡献是:

isnull(RHS.TimeFrom,LHS.TimeTo) - LHS.TimeFrom as TimeElapsed

通过ID对这些进行求和可以给出正确的答案。

请注意:
   - 在没有重叠后继行的情况下,计算只是
 LHS.TimeTo - LHS.TimeFrom
  - 如果存在重叠的后继行,则计算将净到
 (RHS.TimeFrom - LHS.TimeFrom) + (RHS.TimeTo - RHS.TimeFrom)
这简化为   RHS.TimeTo - LHS.TimeFrom

答案 2 :(得分:0)

如下所示(由于CTE而假设SQL 2008+):

    WITH Overlaps
    AS
    (
        SELECT  t1.Id, 
                TimeFrom = MIN(t1.TimeFrom), 
                TimeTo = MAX(t2.TimeTo)
        FROM    dbo.Times t1
            INNER JOIN dbo.Times t2 ON t2.Id = t1.Id
                                    AND t2.TimeFrom > t1.TimeFrom
                                    AND t2.TimeFrom < t1.TimeTo
        GROUP BY t1.Id
    )
    SELECT      o.Id,
                o.TimeFrom,
                o.TimeTo
    FROM    Overlaps o

    UNION ALL

    SELECT      t.Id,
                t.TimeFrom,
                t.TimeTo
    FROM    dbo.Times t
        INNER JOIN Overlaps o   ON o.Id = t.Id
                                AND (o.TimeFrom > t.TimeFrom OR o.TimeTo < t.TimeTo);

我没有很多要测试的数据,但在我拥有的较小数据集上似乎不错。

答案 3 :(得分:0)

我也围绕着这个问题 - 我发现,问题就在于你的数据。

你声称(如果我说得对),这些条目应反映用户空闲/回来时的相对时间。

因此,您应该考虑清理数据并重构插入以生成有效的数据集。 例如,两行:

+----+----------+--------+
| ID | TimeFrom | TimeTo |
+----+----------+--------+
| 10 | 50       | 70     |
| 10 | 60       | 150    |

如何用户可以空闲直到第二个70,但是在第二个60空闲时?这已经意味着,他已经回到了最新的第59位。

我只能假设此问题来自用户可能正在使用您的应用程序的不同线程和/或浏览器窗口(选项卡)。 (每个人都拥有自己的“空闲检测”)

因此,不应该解决症状 - 你应该解决原因! 为什么此数据条目是否已插入表中?如果用户在插入新行之前已经处于空闲状态,您可以通过简单检查来避免这种情况。

  • IDTimeTo
  • 上创建唯一键约束
  • 每当检测到 idle-event 时,请执行以下查询:

    INSERT IGNORE INTO Times (ID,TimeFrom,TimeTo)VALUES('10', currentTimeStamp, -1); -- (If the user is already "idle" - nothing will happen)

  • 每当检测到 comeback-event 时,请执行以下查询:

    UPDATE Times SET TimeTo=currentTimeStamp WHERE ID='10' and TimeTo=-1 -- (If the user is already "back" - nothing will happen)

此处链接的小提琴:http://sqlfiddle.com/#!2/dcb17/1会为您的示例重现事件链,但会产生一组干净且合乎逻辑的空闲窗口:

ID   TIMEFROM   TIMETO

10   10         30 
10   50         70 
10   75         150 

注意:输出与您想要的输出略有不同。但我觉得这更准确,原因如上所述:用户不能在第二个70上空闲而不从之前的当前空闲状态返回。他或者STAYS空闲(第二个线程/标签进入空闲事件)或者他回来了。


特别是对于最大限度提高性能的需求,您应该修复数据而不是发明一种解决方法。插入时可能是3毫秒,但选择后可能值20秒!


编辑:如果多线程/多会话 是错误插入的原因,您还需要执行检查,如果most_recent_come_back_time < now() - idleTimeout - 另外一个用户可能会在tab1上回来,并在几秒钟后在tab2上空闲记录,因为tab2确实遇到了它的空闲超时,导致用户只刷新了tab1。

答案 4 :(得分:0)

我有相同的&#39;问题一次&#39;天&#39; (另外不计算WE和假期) 计算这个词给了我以下想法:

create table Seconds ( sec INT);
insert into Seconds values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9), ...

select count(distinct sec) from times t, seconds s
where s.sec between t.timefrom and t.timeto-1
and id=10;

你可以将开头减少到0(我把&#39; 10&#39;这里放在大括号中)

select count(distinct sec) from times t, seconds s
where s.sec between t.timefrom- (10) and t.timeto- (10)-1
and id=10;

和最终

select count(distinct sec) from times t, seconds s,
(select min(timefrom) m from times where id=10) as m
where s.sec between t.timefrom-m.m and t.timeto-m.m-1
and id=10;

另外你可以&​​#34;忽略&#34;例如。通过划分你松散一些prezition但获得速度10秒钟

select count(distinct sec)*d from times t, seconds s,
(select min(timefrom) m from times where id=10) as m,
(select 10 d) as d
where s.sec between (t.timefrom-m)/d and (t.timeto-m)/d-1
and id=10;

当然,这取决于你必须看到的范围,但是一天的时间&#39;或两秒钟应该工作(虽然我没有测试)

fiddle ...