随着时间的推移跟踪活动对象

问题描述

我有一个事件表(使用 id 作为事件 ID),如下所示(另见 SQL fiddle here):

CREATE TABLE ext (
  key INT,id CHAR(1),pid INT,sid INT,oid INT,event VARCHAR(3)
);

INSERT INTO ext (key,id,pid,sid,oid,event)
VALUES 
 (1,'Q',1,81,20,'tsu'),(2,9,(3,10,(4,4,(5,15,(6,3,(7,5,(8,18,(9,2,(10,(11,7,(12,'f',NULL,'s'),(13,'Z',871,'e'),(14,'m',872,'pof'),(15,'s',873,31,'pom'),(16,'R',874,'fc'),(17,(18,(19,(20,(21,(22,(23,(24,(25,(26,(27,'k',876,(28,'a',950,'rco'),(29,'y',1285,(30,'N',1286,22,(31,'i',1299,(32,(33,(34,(35,(36,(37,(38,(39,(40,(41,'I',1407,(42,'T',1408,19,(43,'u',1575,(44,'V',1576,30,(45,'B',2019,(46,'h',60,'e');

事件根据 pidsid 列排序(例如,您可以分别查看天和小时,因为 pid 是比 sid 更大的时间单位,因此您应该订购首先通过 pid 然后通过 sid 以获得正确的顺序)。如您所见,一些事件(event = tsuevent = fc)有不止一行,因为它们引用了许多对象(oid),一些只引用一个对象(作为id = m),有些只有一行,但它们实际上指的是所有先前观察到的不是“死”的物体。它们是一些附加规则:

  • 一个对象在遇到 pofrco 事件时死亡(如 event 列中所述)
  • 当对象遇到 pof 事件时,有另一个对象将其替换为标有 pom 事件

我需要跟踪当前活动的对象。因此,我想将 oidNULL 的行与可以从以前的事件中推断出的所有活动对象“交叉连接”,其中“交叉连接”我的意思是复制该行oid = NULL 代表每个当前活动的 oid

由于文本中的逻辑可能难以理解,因此我准备了预期的输出(可在 SQL fiddle here 上获得):

CREATE TABLE intermediate_result (
  id CHAR(1),event VARCHAR(3)
);

INSERT INTO intermediate_result (id,event)
VALUES 
 ('Q',('Q',('f',('Z',('m',('s',('R',('k',('a',('y',('N',('i',('I',('T',('u',('V',('B',('h','e');

sql 版本为 Postgresql 9.5。

解决方法

单独使用 SQL 解决这个问题非常困难且效率低下。我认为您最好的选择是程序性解决方案。

这是在 PL/pgSQL 中的实现。

CREATE OR REPLACE FUNCTION f_expand_oid_null()
  RETURNS TABLE(_id CHAR(1),_pid int,_sid int,_oid int,_event varchar(3))
  LANGUAGE plpgsql AS
$func$
DECLARE
   _key   int;
   _pof   int;  -- remember for subsequent 'pom'
BEGIN
   -- hold set of "active object IDs
   CREATE TEMP TABLE tmp_active_objects(
     oid int PRIMARY KEY,pid int,sid int,key int
   ) ON COMMIT DROP;

-- loop table rows in order
   FOR _key,_id,_pid,_sid,_oid,_event IN
      SELECT e.key,e.id,e.pid,e.sid,e.oid,e.event
      FROM   ext e
      ORDER  BY e.pid,e.key
   LOOP
      IF _oid IS NULL THEN
         -- expand to ordered set of active objects
         RETURN QUERY
         SELECT _id,a.oid,_event 
         FROM   tmp_active_objects a
         ORDER  BY a.pid,a.sid,a.key;  -- keep original order of events
         -- returns nothing if no active objects
      ELSE
         RETURN QUERY VALUES (_id,_event);
         
         CASE _event
         WHEN 'rco' THEN
            DELETE FROM tmp_active_objects WHERE oid = _oid;
         WHEN 'pof' THEN
            _pof = _oid;   -- remember for subsequent 'pom'
         WHEN 'pom' THEN
            UPDATE tmp_active_objects
            SET    oid = _oid
            WHERE  oid = _pof;
         ELSE
            -- upsert active objects
            INSERT INTO tmp_active_objects (oid,pid,sid,key)
            VALUES (_oid,_key)
            ON CONFLICT (oid) DO
            UPDATE
            SET    (pid,key)
                 = (EXCLUDED.pid,EXCLUDED.sid,EXCLUDED.key);
         END CASE;
      END IF;
   END LOOP;
END
$func$;

dbfiddle here

解释一切比写出来还要辛苦。我做了一些假设,并避免了一些陷阱。特别是,每个 'pof' 行必须紧跟一个 'pom' 行。

如果您不熟悉 PL/pgSQL,请考虑聘请付费顾问。或者使用您选择的程序语言实施。

,

我终于能够进入 SQLFiddle 以正确地看到问题。

应该重申主要请求,说明您希望将活动对象与 oid 的 null 值交叉连接,同时保留所有非 null oid 值的所有行

理解:

  1. oid 值代表对象
  2. 从结果数据来看,活动对象依赖于时间,因此为了计算活动对象,我们将依赖于代表时间的特定时间行 ID。
  3. 表插入与时间相关,并且将进入未来,后面的行代表未来时间
  4. 问题:根据主要请求替换 pom 对象的相关性仍未完全理解,并且对解决方案没有任何影响。

解决方案:

对于解决方案,有 3 个部分 -

  1. 一个自动增加的字段添加到您的 ext 表中。您已经将其添加为 INT 并将其命名为 Key。这将用作唯一的 TimeID
With DistinctTimeIDs as
(
  select a.key as TimeID from ext a
)
  1. 活动对象 - 历史确实很重要。在这种情况下,我们希望在没有 pof 或 rco 事件的特定 TimeID 之前的所有非空 oid。这将为数据集中的每个 TimeID 列出该 TimeID 之前的所有活动 oid。
ActiveObjects as 
( 
  select b.TimeID,a.oid from ext a inner join DistinctTimeIDs b on b.TimeID>=a.key
  where oid is not null 
  and not exists (select 'x' from ext where key<b.TimeID and event in ('pof','rco') and oid=a.oid) 
  and a.key = (select MAX(c.key) from ext c where c.key<b.TimeID and c.event not in ('pof','rco') and c.oid=a.oid)
)
  1. 空对象值行
With NullObjectValues as 
(
  select * from ext a 
  where a.oid is null 
)

将它们放在一起,我们将合并非空对象行,空对象行与每个 TimeID 的活动对象交叉连接

With DistinctTimeIDs as
(
  select a.key as TimeID from ext a
),ActiveObjects as 
( 
  select b.TimeID,'rco') and c.oid=a.oid)
),NullObjectValues as 
(
  select * from ext a 
  where a.oid is null 
)
select a.key,a.id,a.pid,a.event from ext a where a.oid is not null
union all
select a.key,b.oid,a.event from NullObjectValues a CROSS JOIN ActiveObjects b
where b.TimeID = a.key 
order by 1;

SQL Fiddle 是 http://sqlfiddle.com/#!17/2fb57/2