SQL选择使日期时间范围在标记切换之间的项目

时间:2016-10-17 10:31:23

标签: sql datetime toggle firebird

说我有一张像这样的表:

CREATE TABLE TESTTABLE (
  ID Integer NOT NULL,
  ATMOMENT Timestamp NOT NULL,
  ISALARM Integer NOT NULL,
  CONSTRAINT PK_TESTTABLE PRIMARY KEY (ID)
);

ISALARM标志在ATMOMENT随机时刻切换0到1之间,就像在这个示例数据集中一样:

INSERT INTO TESTTABLE (ID, ATMOMENT, ISALARM) VALUES ('1', '01.01.2016, 00:00:00.000', '1');
INSERT INTO TESTTABLE (ID, ATMOMENT, ISALARM) VALUES ('2', '01.01.2016, 00:01:00.000', '1');
INSERT INTO TESTTABLE (ID, ATMOMENT, ISALARM) VALUES ('3', '01.01.2016, 00:02:00.000', '0');
INSERT INTO TESTTABLE (ID, ATMOMENT, ISALARM) VALUES ('4', '01.01.2016, 00:02:00.000', '0');
INSERT INTO TESTTABLE (ID, ATMOMENT, ISALARM) VALUES ('10', '02.01.2016, 00:00:00.000', '1');
INSERT INTO TESTTABLE (ID, ATMOMENT, ISALARM) VALUES ('11', '02.01.2016, 00:00:00.000', '1');
INSERT INTO TESTTABLE (ID, ATMOMENT, ISALARM) VALUES ('12', '02.01.2016, 00:01:00.000', '0');
INSERT INTO TESTTABLE (ID, ATMOMENT, ISALARM) VALUES ('20', '03.01.2016, 00:00:00.000', '1');
INSERT INTO TESTTABLE (ID, ATMOMENT, ISALARM) VALUES ('21', '03.01.2016, 00:01:00.000', '1');
INSERT INTO TESTTABLE (ID, ATMOMENT, ISALARM) VALUES ('22', '03.01.2016, 00:02:00.000', '0');
INSERT INTO TESTTABLE (ID, ATMOMENT, ISALARM) VALUES ('23', '03.01.2016, 00:02:00.000', '1');
INSERT INTO TESTTABLE (ID, ATMOMENT, ISALARM) VALUES ('30', '04.01.2016, 00:00:00.000', '1');
INSERT INTO TESTTABLE (ID, ATMOMENT, ISALARM) VALUES ('31', '04.01.2016, 00:00:00.000', '1');
INSERT INTO TESTTABLE (ID, ATMOMENT, ISALARM) VALUES ('32', '04.01.2016, 00:00:00.000', '0');
INSERT INTO TESTTABLE (ID, ATMOMENT, ISALARM) VALUES ('33', '04.01.2016, 00:00:00.000', '0');
INSERT INTO TESTTABLE (ID, ATMOMENT, ISALARM) VALUES ('40', '05.01.2016, 00:00:00.000', '1');
INSERT INTO TESTTABLE (ID, ATMOMENT, ISALARM) VALUES ('41', '05.01.2016, 00:00:00.000', '1');
INSERT INTO TESTTABLE (ID, ATMOMENT, ISALARM) VALUES ('42', '05.01.2016, 00:00:00.000', '0');
INSERT INTO TESTTABLE (ID, ATMOMENT, ISALARM) VALUES ('43', '05.01.2016, 00:00:00.000', '0');

我需要选择所有报警范围,即ISAMARM设置为1的ATMOMENT范围(前一个范围关闭后第一次)在范围开始,并在范围结束时重置为0。为清楚起见,首先重置足以关闭此范围;另外说同时ISALARM设置和重置被视为范围结束(可能作为开始)。 上面的示例数据集应该产生如下内容:

 ALARMBEGIN                |  LASTALARMBEGIN            |  ALARMEND
-------------------------- | -------------------------- | --------
'01.01.2016, 00:00:00.000' | '01.01.2016, 00:01:00.000' | '01.01.2016, 00:02:00.000'
'02.01.2016, 00:00:00.000' | '02.01.2016, 00:00:00.000' | '02.01.2016, 00:01:00.000'
'03.01.2016, 00:00:00.000' | '03.01.2016, 00:02:00.000' | '03.01.2016, 00:02:00.000'
'04.01.2016, 00:00:00.000' | '04.01.2016, 00:00:00.000' | '04.01.2016, 00:00:00.000'
'05.01.2016, 00:00:00.000' | '05.01.2016, 00:00:00.000' | '05.01.2016, 00:00:00.000'

我自己的解决方案(下图)看起来非常难看并且运行速度非常慢(大约1分钟),即使TESTTABLE具有相对较小的数据集,只有大约2500条记录(使用Firebird2.5和Postgresql进行测试;我不是很好使用数据库优化;“在测试时创建索引IDX_TESTTABLE1(ATMOMENT,ISALARM)”有帮助但不是很多。

对我来说这很奇怪,因为在将ISALARM字段与之前的记录之一进行比较时,对所有TESTTABLE记录(由ATMOMENT排序)进行简单的线性迭代可以得到我想要的范围更快的范围。

有没有优雅的解决方案让SQL更快更清洁地选择它?

SELECT DISTINCT a1.ATMOMENT AS ALARMBEGIN, a2.ATMOMENT AS LASTALARMBEGIN, a3.ATMOMENT AS ALARMEND
FROM TESTTABLE a1
JOIN TESTTABLE a2 ON 
    (a1.ATMOMENT<a2.ATMOMENT
        AND NOT EXISTS(SELECT * FROM TESTTABLE x WHERE 
            x.ISALARM=0 AND a1.ATMOMENT<=x.ATMOMENT AND x.ATMOMENT<a2.ATMOMENT))
    OR (a1.ATMOMENT=a2.ATMOMENT)
JOIN TESTTABLE a3 ON 
    (a2.ATMOMENT<a3.ATMOMENT
        AND NOT EXISTS(SELECT * FROM TESTTABLE x WHERE 
            (x.ISALARM=0 AND a2.ATMOMENT<=x.ATMOMENT AND x.ATMOMENT<a3.ATMOMENT)
            OR (x.ISALARM=1 AND a2.ATMOMENT<x.ATMOMENT AND x.ATMOMENT<=a3.ATMOMENT)))
    OR (a2.ATMOMENT=a3.ATMOMENT)
WHERE a1.ISALARM<>0 AND a2.ISALARM<>0 AND a3.ISALARM=0
    AND (NOT EXISTS(SELECT * FROM TESTTABLE x1 WHERE
            x1.ATMOMENT<a1.ATMOMENT)
        OR EXISTS(SELECT * FROM TESTTABLE x1 WHERE
            x1.ISALARM=0
            AND x1.ATMOMENT<a1.ATMOMENT
            AND NOT EXISTS(SELECT * FROM TESTTABLE x2 WHERE
                x1.ATMOMENT<x2.ATMOMENT AND x2.ATMOMENT<a1.ATMOMENT)))
ORDER BY a1.ATMOMENT

谢谢。

更新1

感谢Gordon Linoff和Jayvee的解决方案(Firebird3.0和PostgreSQL非常好),我决定依靠Firebird2.5的订购效率并设计出“选择”,它甚至比我之前的那个更糟糕但是运行得更快。对于那些需要使用Firebird2.5完成的人:

WITH 
GROUPEDTABLE_TT (ATMOMENT, NOTISALARMRESET, ISALARMSET)
AS(
SELECT a.ATMOMENT, MIN(a.ISALARM), MAX(a.ISALARM)
FROM TESTTABLE a
GROUP BY a.ATMOMENT),

INTERVALBEGIN_TT 
AS(
SELECT a1.ATMOMENT
FROM GROUPEDTABLE_TT a1
WHERE 
    a1.ISALARMSET<>0
    AND (NOT EXISTS (SELECT * FROM GROUPEDTABLE_TT x WHERE
            x.ATMOMENT<a1.ATMOMENT)
        OR (SELECT FIRST 1 x.NOTISALARMRESET FROM GROUPEDTABLE_TT x WHERE
            x.ATMOMENT<a1.ATMOMENT
            ORDER BY x.ATMOMENT DESC)=0)),

INTERVALLAST_TT 
AS(
SELECT a2.ATMOMENT FROM GROUPEDTABLE_TT a2
WHERE a2.ISALARMSET=1
    AND (a2.NOTISALARMRESET=0
        OR (a2.NOTISALARMRESET=1 
            AND (SELECT FIRST 1 x.NOTISALARMRESET FROM GROUPEDTABLE_TT x WHERE
                x.ATMOMENT>a2.ATMOMENT
                ORDER BY x.ATMOMENT ASC)=0
            AND (SELECT FIRST 1 x.ISALARMSET FROM GROUPEDTABLE_TT x WHERE
                x.ATMOMENT>a2.ATMOMENT
                ORDER BY x.ATMOMENT ASC)=0))),

INTERVALEND_TT 
AS(
SELECT a1.ATMOMENT
FROM GROUPEDTABLE_TT a1
WHERE 
    a1.NOTISALARMRESET=0
    AND (a1.ISALARMSET=1 
        OR (a1.ISALARMSET=0 
            AND (SELECT FIRST 1 x.ISALARMSET FROM GROUPEDTABLE_TT x WHERE
                x.ATMOMENT<a1.ATMOMENT
                ORDER BY x.ATMOMENT DESC)=1
            AND (SELECT FIRST 1 x.NOTISALARMRESET FROM GROUPEDTABLE_TT x WHERE
                x.ATMOMENT<a1.ATMOMENT
                ORDER BY x.ATMOMENT DESC)=1))),

ENCLOSEDINTERVALS_TT (BEGINMOMENT, LASTBEGINMOMENT, ENDMOMENT)
AS(
SELECT ib.ATMOMENT, 
    (SELECT FIRST 1 il.ATMOMENT FROM INTERVALLAST_TT il WHERE 
        ib.ATMOMENT<=il.ATMOMENT ORDER BY il.ATMOMENT ASC),
    (SELECT FIRST 1 ie.ATMOMENT FROM INTERVALEND_TT ie WHERE 
        ib.ATMOMENT<=ie.ATMOMENT ORDER BY ie.ATMOMENT ASC)
FROM INTERVALBEGIN_TT ib)

SELECT * FROM ENCLOSEDINTERVALS_TT
ORDER BY BEGINMOMENT

更新2 ...但我的选择似乎显示取决于总记录数的取数的二次增长(或至少快于线性);对于FB2.5,最好使用单程线性迭代的程序。或者将FB30与以下解决方案一起使用......

2 个答案:

答案 0 :(得分:4)

这已在PostgreSQL中测试过,想法是分别为开头,最后的开头和结尾创建3个有序的公用表,然后加入3个表。

只需创建一个CTE并使用case语句标记行,然后使用selfjoin(稍后可以执行),可以使用更少的代码完成它,但这样代码更加自我解释并且应该也相当高效。

;
with beginnings
as
(
    select atmoment, row_number() over(order by atmoment) rn from
    (
        select *, lag(atmoment,1) over(order by atmoment,isalarm desc) prevtime, 
          lag(isalarm,1) over(order by atmoment,isalarm desc) prevstatus
        from testtable 
    ) t
    where coalesce(prevstatus,0)=0 and isalarm=1
),
ends 
as
(
    select atmoment, row_number() over(order by atmoment) rn from
    (
        select *, lead(atmoment,1) over(order by atmoment,isalarm) nexttime, 
          lead(isalarm,1) over(order by atmoment,isalarm) nextstatus
        from testtable 
    ) t
    where coalesce(nextstatus,1)=1 and isalarm=0
),
lastbeginnings
as
(
    select atmoment, row_number() over(order by atmoment) rn from
    (
        select *, lead(atmoment,1) over(order by atmoment,isalarm desc) nexttime, 
          lead(isalarm,1) over(order by atmoment,isalarm desc) nextstatus
        from testtable 
    ) t
    where coalesce(nextstatus,0)=0 and isalarm=1
)

select b.atmoment ALARMBEGIN, lb.atmoment LASTALARMBEGIN, e.atmoment  ALARMEND 
from beginnings b
join lastbeginnings lb on lb.rn=b.rn
join ends e on e.rn=b.rn

结果:

> 2016-01-01 00:00:00 | 2016-01-01 00:01:00 | 2016-01-01 00:02:00
> 2016-01-02 00:00:00 | 2016-01-02 00:00:00 | 2016-01-02 00:01:00
> 2016-01-03 00:00:00 | 2016-01-03 00:02:00 | 2016-01-03 00:02:00
> 2016-01-04 00:00:00 | 2016-01-04 00:00:00 | 2016-01-04 00:00:00
> 2016-01-05 00:00:00 | 2016-01-05 00:00:00 | 2016-01-05 00:00:00

答案 1 :(得分:2)

我认为您可以使用row_number()

在Firebird 3.0中执行此操作
select alarm, min(atmoment), max(atmoment)
from (select t.*,
             row_number() over (order by atmoment) as seqnum,
             row_number() over (partition by alarm order by atmoment) as seqnum_a
      from testtable t
     ) t
group by alarm, (seqnum - seqnum_a);

有点难以解释这是如何工作的。但是如果运行子查询,您将看到差异如何识别您感兴趣的组。