问题描述
我有一个事件表(使用 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');
事件根据 pid
和 sid
列排序(例如,您可以分别查看天和小时,因为 pid 是比 sid 更大的时间单位,因此您应该订购首先通过 pid 然后通过 sid 以获得正确的顺序)。如您所见,一些事件(event = tsu
或 event = fc
)有不止一行,因为它们引用了许多对象(oid
),一些只引用一个对象(作为id = m
),有些只有一行,但它们实际上指的是所有先前观察到的不是“死”的物体。它们是一些附加规则:
我需要跟踪当前活动的对象。因此,我想将 oid
为 NULL
的行与可以从以前的事件中推断出的所有活动对象“交叉连接”,其中“交叉连接”我的意思是复制该行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 解决这个问题非常困难且效率低下。我认为您最好的选择是程序性解决方案。
这是在 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 值的所有行
理解:
- oid 值代表对象
- 从结果数据来看,活动对象依赖于时间,因此为了计算活动对象,我们将依赖于代表时间的特定时间行 ID。
- 表插入与时间相关,并且将进入未来,后面的行代表未来时间
- 问题:根据主要请求替换 pom 对象的相关性仍未完全理解,并且对解决方案没有任何影响。
解决方案:
对于解决方案,有 3 个部分 -
- 一个自动增加的字段添加到您的 ext 表中。您已经将其添加为 INT 并将其命名为 Key。这将用作唯一的 TimeID
With DistinctTimeIDs as
(
select a.key as TimeID from ext a
)
- 活动对象 - 历史确实很重要。在这种情况下,我们希望在没有 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)
)
- 空对象值行
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