关系数据库中的旅行时间计算?

时间:2008-09-17 13:16:35

标签: sql mysql sql-server oracle postgresql

我记住了这个问题,因为我刚发现这个网站,所以我决定将它发布在这里。

假设我有一个带有时间戳的表和给定“对象”的状态(通用含义,而不是OOP对象);是否有一种最佳方法来计算状态和下一次出现另一个(或相同)状态(我称之为“旅行”)与单个SQL语句之间的时间(内部SELECT和UNION不计算)?

例如:对于以下情况,初始和完成之间的旅行时间为6天,但在初始和审核之间的旅行时间为2天。

  

2008-08-01 13:30:00 - 初始
  2008-08-02 13:30:00 - 工作
  2008-08-03 13:30:00 - 评论
  2008-08-04 13:30:00 - 工作
  2008-08-05 13:30:00 - 评论
  2008-08-06 13:30:00 - 接受
  2008-08-07 13:30:00 - 完成

不需要是通用的,只要说明你的解决方案特定的SGBD,如果不是通用的话。

10 个答案:

答案 0 :(得分:1)

这是使用分析函数的Oracle方法。

with data as (
SELECT 1 trip_id, to_date('20080801 13:30:00','YYYYMMDD HH24:mi:ss') dt, 'Initial'  step from dual UNION ALL
SELECT 1 trip_id, to_date('20080802 13:30:00','YYYYMMDD HH24:mi:ss') dt, 'Work'     step from dual  UNION ALL
SELECT 1 trip_id, to_date('20080803 13:30:00','YYYYMMDD HH24:mi:ss') dt, 'Review'   step from dual  UNION ALL
SELECT 1 trip_id, to_date('20080804 13:30:00','YYYYMMDD HH24:mi:ss') dt, 'Work'     step from dual UNION ALL
SELECT 1 trip_id, to_date('20080805 13:30:00','YYYYMMDD HH24:mi:ss') dt, 'Review'   step from dual  UNION ALL
SELECT 1 trip_id, to_date('20080806 13:30:00','YYYYMMDD HH24:mi:ss') dt, 'Accepted' step from dual  UNION ALL
SELECT 1 trip_id, to_date('20080807 13:30:00','YYYYMMDD HH24:mi:ss') dt, 'Done'     step from dual )
select trip_id,
       step,
       dt - lag(dt) over (partition by trip_id order by dt) trip_time
from  data
/


1   Initial 
1   Work        1
1   Review      1
1   Work        1
1   Review      1
1   Accepted    1
1   Done        1

这些在传统上我们可能使用自联接的情况下非常常用。

答案 1 :(得分:1)

PostgreSQL语法:

DROP TABLE ObjectState;
CREATE TABLE ObjectState (
    object_id integer not null,--foreign key
    event_time timestamp NOT NULL,
    state varchar(10) NOT NULL,
    --Other fields 
    CONSTRAINT pk_ObjectState PRIMARY KEY (object_id,event_time)
);

对于给定状态,找到给定类型的第一个下属状态

select parent.object_id,parent.event_time,parent.state,min(child.event_time) as ch_event_time,min(child.event_time)-parent.event_time as step_time
from 
    ObjectState parent
    join ObjectState child on (parent.object_id=child.object_id and parent.event_time<child.event_time)
where 
    --Starting state 
    parent.object_id=1 and parent.event_time=to_timestamp('01-Aug-2008 13:30:00','dd-Mon-yyyy hh24:mi:ss')
    --needed state
    and child.state='Review'
group by parent.object_id,parent.event_time,parent.state;

此查询不是最短的,但它应该易于理解并用作其他查询的一部分:

列出事件及其给定对象的持续时间

select parent.object_id,parent.event_time,parent.state,min(child.event_time) as ch_event_time,
       CASE WHEN parent.state<>'Done' and min(child.event_time) is null THEN (select localtimestamp)-parent.event_time ELSE min(child.event_time)-parent.event_time END  as step_time
from 
    ObjectState parent
    left outer join ObjectState child on (parent.object_id=child.object_id and parent.event_time<child.event_time)
where parent.object_id=4    
group by parent.object_id,parent.event_time,parent.state
order by parent.object_id,parent.event_time,parent.state;

列出未“完成”的对象的当前状态

select states.object_id,states.event_time,states.state,(select localtimestamp)-states.event_time as step_time
from
    (select parent.object_id,parent.event_time,parent.state,min(child.event_time) as ch_event_time,min(child.event_time)-parent.event_time as step_time
     from 
        ObjectState parent
        left outer join ObjectState child on (parent.object_id=child.object_id and parent.event_time<child.event_time)       
     group by parent.object_id,parent.event_time,parent.state) states
where     
    states.object_id not in (select object_id from ObjectState where state='Done')
    and ch_event_time is null;

测试数据

insert into ObjectState (object_id,event_time,state)
select 1,to_timestamp('01-Aug-2008 13:30:00','dd-Mon-yyyy hh24:mi:ss'),'Initial' union    all
select 1,to_timestamp('02-Aug-2008 13:40:00','dd-Mon-yyyy hh24:mi:ss'),'Work' union all
select 1,to_timestamp('03-Aug-2008 13:50:00','dd-Mon-yyyy hh24:mi:ss'),'Review' union all
select 1,to_timestamp('04-Aug-2008 14:30:00','dd-Mon-yyyy hh24:mi:ss'),'Work' union all
select 1,to_timestamp('04-Aug-2008 16:20:00','dd-Mon-yyyy hh24:mi:ss'),'Review' union all
select 1,to_timestamp('06-Aug-2008 18:00:00','dd-Mon-yyyy hh24:mi:ss'),'Accepted' union all
select 1,to_timestamp('07-Aug-2008 21:30:00','dd-Mon-yyyy hh24:mi:ss'),'Done';


insert into ObjectState (object_id,event_time,state)
select 2,to_timestamp('01-Aug-2008 13:30:00','dd-Mon-yyyy hh24:mi:ss'),'Initial' union all
select 2,to_timestamp('02-Aug-2008 13:40:00','dd-Mon-yyyy hh24:mi:ss'),'Work' union all
select 2,to_timestamp('07-Aug-2008 13:50:00','dd-Mon-yyyy hh24:mi:ss'),'Review' union all
select 2,to_timestamp('14-Aug-2008 14:30:00','dd-Mon-yyyy hh24:mi:ss'),'Work' union all
select 2,to_timestamp('15-Aug-2008 16:20:00','dd-Mon-yyyy hh24:mi:ss'),'Review' union all
select 2,to_timestamp('16-Aug-2008 18:02:00','dd-Mon-yyyy hh24:mi:ss'),'Accepted' union all
select 2,to_timestamp('17-Aug-2008 22:10:00','dd-Mon-yyyy hh24:mi:ss'),'Done';

insert into ObjectState (object_id,event_time,state)
select 3,to_timestamp('12-Sep-2008 13:30:00','dd-Mon-yyyy hh24:mi:ss'),'Initial' union    all
select 3,to_timestamp('13-Sep-2008 13:40:00','dd-Mon-yyyy hh24:mi:ss'),'Work' union all
select 3,to_timestamp('14-Sep-2008 13:50:00','dd-Mon-yyyy hh24:mi:ss'),'Review' union   all
select 3,to_timestamp('15-Sep-2008 14:30:00','dd-Mon-yyyy hh24:mi:ss'),'Work' union all
select 3,to_timestamp('16-Sep-2008 16:20:00','dd-Mon-yyyy hh24:mi:ss'),'Review';


insert into ObjectState (object_id,event_time,state)
select 4,to_timestamp('21-Aug-2008 03:10:00','dd-Mon-yyyy hh24:mi:ss'),'Initial' union all
select 4,to_timestamp('22-Aug-2008 03:40:00','dd-Mon-yyyy hh24:mi:ss'),'Work' union all
select 4,to_timestamp('23-Aug-2008 03:20:00','dd-Mon-yyyy hh24:mi:ss'),'Review' union all
select 4,to_timestamp('24-Aug-2008 04:30:00','dd-Mon-yyyy hh24:mi:ss'),'Work';

答案 2 :(得分:0)

我不认为你可以用一个SQL语句得到答案,因为你试图从许多记录中获得一个结果。在SQL中实现这一点的唯一方法是获取两个不同记录的时间戳字段并计算差异(datediff)。因此,需要UNIONS或内部联接。

答案 3 :(得分:0)

我不确定我是否完全理解了这个问题,但您可以执行以下操作,在一次传递中读取表,然后使用派生表来计算它。 SQL Server代码:

CREATE TABLE #testing
(
    eventdatetime datetime NOT NULL,
    state varchar(10) NOT NULL
)

INSERT INTO #testing (
    eventdatetime,
    state
) 
SELECT '20080801 13:30:00', 'Initial' UNION ALL
SELECT '20080802 13:30:00', 'Work' UNION ALL
SELECT '20080803 13:30:00', 'Review' UNION ALL
SELECT '20080804 13:30:00', 'Work' UNION ALL
SELECT '20080805 13:30:00', 'Review' UNION ALL
SELECT '20080806 13:30:00', 'Accepted' UNION ALL
SELECT '20080807 13:30:00', 'Done'

SELECT DATEDIFF(dd, Initial, Review)
FROM (
SELECT  MIN(CASE WHEN state='Initial' THEN eventdatetime END) AS Initial,
        MIN(CASE WHEN state='Review' THEN eventdatetime END) AS Review
FROM #testing
) AS A

DROP TABLE #testing

答案 4 :(得分:0)

create table A (
    At datetime not null,
    State varchar(20) not null
)
go
insert into A(At,State)
select '2008-08-01T13:30:00','Initial' union all
select '2008-08-02T13:30:00','Work' union all
select '2008-08-03T13:30:00','Review' union all
select '2008-08-04T13:30:00','Work' union all
select '2008-08-05T13:30:00','Review' union all
select '2008-08-06T13:30:00','Accepted' union all
select '2008-08-07T13:30:00','Done'
go
--Find trip time from Initial to Done
select DATEDIFF(day,t1.At,t2.At)
from
    A t1
        inner join
    A t2
        on
            t1.State = 'Initial' and
            t2.State = 'Review' and
            t1.At < t2.At
        left join
    A t3
        on
            t3.State = 'Initial' and
            t3.At > t1.At and
            t4.At < t2.At
        left join
    A t4
        on
            t4.State = 'Review' and
            t4.At < t2.At and
            t4.At > t1.At
where
    t3.At is null and
    t4.At is null

没有说是否允许加入。加入t3和t4(和他们的比较)让你说你是否想要最早或最晚出现的开始和结束状态(在这种情况下,我要求最新的“初始”和最早的“评论”)

在实际代码中,我的开始和结束状态将是参数

编辑:哎呀,需要包括“t3.At&lt; t2.At”和“t4.At&gt; t1.At”,以修复一些奇怪的国家序列(例如,如果我们删除了第二个“评论”和然后从“工作”查询到“查看”,原始查询将失败)

答案 5 :(得分:0)

如果您有序列号和时间戳,则可能更容易:在大多数RDBMS中,您可以创建自动增量列而不更改任何INSERT语句。然后你加入表格及其自身副本以获得增量

select after.moment - before.moment, before.state, after.state
from object_states before, object_states after
where after.sequence + 1 = before.sequence

(其中SQL语法的细节因数据库系统而异。)

答案 6 :(得分:0)

    -- Oracle SQl

    CREATE TABLE ObjectState
    (
        startdate date NOT NULL,
        state varchar2(10) NOT NULL
    );



   insert into ObjectState 
   select to_date('01-Aug-2008 13:30:00','dd-Mon-rrrr hh24:mi:ss'),'Initial' union all
   select to_date('02-Aug-2008 13:30:00','dd-Mon-rrrr hh24:mi:ss'),'Work' union all
   select to_date('03-Aug-2008 13:30:00','dd-Mon-rrrr hh24:mi:ss'),'Review' union all
   select to_date('04-Aug-2008 13:30:00','dd-Mon-rrrr hh24:mi:ss'),'Work' union all
   select to_date('05-Aug-2008 13:30:00','dd-Mon-rrrr hh24:mi:ss'),'Review' union all
   select to_date('06-Aug-2008 13:30:00','dd-Mon-rrrr hh24:mi:ss'),'Accepted' union all
   select to_date('07-Aug-2008 13:30:00','dd-Mon-rrrr hh24:mi:ss'),'Done';

-- Days in between two states

  select  o2.startdate - o1.startdate as days
  from ObjectState o1, ObjectState o2
  where o1.state = 'Initial'
  and o2.state = 'Review';

答案 7 :(得分:0)

我认为您的步骤(您旅行的每个记录都可以看作是一个步骤)可以在某个地方组合在一起作为同一活动的一部分。然后可以将数据分组在其上,例如:

SELECT Min(Tbl_Step.dateTimeStep) as tripBegin, _   
       Max(Tbl_Step.dateTimeStep) as tripEnd _
FROM 
       Tbl_Step 
WHERE 
       id_Activity = 'AAAAAAA'

使用此原则,您可以计算其他聚合,例如活动中的步骤数等。但是你不会找到一种SQL方法来计算两个步骤之间的间隙值,因为这样的数据不属于第一步或第二步。一些报告工具使用他们所谓的“运行总和”来计算这样的中间数据。根据您的目标,这可能是您的解决方案。

答案 8 :(得分:0)

我试图在MySQL中这样做。你需要使用一个变量,因为MySQL中没有rank函数,所以它会是这样的:

set @trip1 = 0; set @trip2 = 0;
SELECT trip1.`date` as startdate, datediff(trip2.`date`, trip1.`date`) length_of_trip
FROM
(SELECT @trip1 := @trip1 + 1 as rank1, `date` from trip where state='Initial') as trip1
INNER JOIN
(SELECT @trip2 := @trip2 + 1 as rank2, `date` from trip where state='Done') as trip2
ON rank1 = rank2;

我假设您要计算“初始”和“完成”状态之间的时间。

+---------------------+----------------+
| startdate           | length_of_trip |
+---------------------+----------------+
| 2008-08-01 13:30:00 |              6 |
+---------------------+----------------+

答案 9 :(得分:0)

好吧,这有点超乎想象,但是我建立了一个网络应用程序,以便在我们生孩子之前跟踪我妻子的宫缩,以便在接近医院的时候能够看到工作。无论如何,我很容易将这个基本的东西建成两个视图。

create table contractions time_date timestamp primary key;

create view contraction_time as
SELECT a.time_date, max(b.prev_time) AS prev_time
   FROM contractions a, ( SELECT contractions.time_date AS prev_time
           FROM contractions) b
  WHERE b.prev_time < a.time_date
  GROUP BY a.time_date;

create view time_between as 
SELECT contraction_time.time_date, contraction_time.prev_time, contraction_time.time_date - contraction_time.prev_time
   FROM contraction_time;

这显然可以作为一个子选择来完成,但我也将中间视图用于其他事情,所以这很好。