2

我想到了这个问题,因为我刚刚发现了这个网站,所以我决定把它贴在这里。

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

例如:对于以下情况,Initial 和 Done 之间的行程时间为 6 天,但 Initial 和 Review 之间的行程时间为 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(如果不是通用的)。

4

10 回答 10

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';
于 2008-09-19T15:50:28.033 回答
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

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

于 2008-09-17T15:01:36.180 回答
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 |
+---------------------+----------------+
于 2008-09-21T00:10:09.493 回答
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 语法的详细信息将根据数据库系统而有所不同)。

于 2008-09-17T13:45:36.597 回答
0

I'm not sure I understand the question exactly, but you can do something like the following which reads the table in one pass then uses a derived table to calculate it. SQL Server code:

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
于 2008-09-17T13:33:37.997 回答
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

Didn't say whether joins were allowed or not. Joins to t3 and t4 (and their comparisons) let you say whether you want the earliest or latest occurrence of the start and end states (in this case, I'm asking for latest "Initial" and earliest "Review")

In real code, my start and end states would be parameters

Edit: Oops, need to include "t3.At < t2.At" and "t4.At > t1.At", to fix some odd sequences of States (e.g. If we removed the second "Review" and then queried from "Work" to "Review", the original query will fail)

于 2008-09-17T13:33:58.590 回答
0

我认为您的步骤(您旅行的每个记录都可以视为一个步骤)可以作为同一活动的一部分组合在一起。然后可以对它的数据进行分组,例如:

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

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

于 2008-09-17T14:29:28.130 回答
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';
于 2008-09-17T13:59:12.040 回答
0

好吧,这有点令人讨厌,但我构建了一个 Web 应用程序来跟踪我妻子在我们生孩子之前的宫缩,这样我就可以在快到医院的时候看到工作。无论如何,我很容易将这个基本的东西构建为两个视图。

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;

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

于 2008-09-30T16:36:54.607 回答
0

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

于 2008-09-17T13:24:31.297 回答