随着时间的推移跟踪活动对象
Posted
技术标签:
【中文标题】随着时间的推移跟踪活动对象【英文标题】:Keep track of active objects over time 【发布时间】:2021-01-20 18:27:49 【问题描述】:我有一个事件表(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, 'Q', 1, 81, 9, 'tsu'),
(3, 'Q', 1, 81, 10, 'tsu'),
(4, 'Q', 1, 81, 4, 'tsu'),
(5, 'Q', 1, 81, 15, 'tsu'),
(6, 'Q', 1, 81, 3, 'tsu'),
(7, 'Q', 1, 81, 5, 'tsu'),
(8, 'Q', 1, 81, 18, 'tsu'),
(9, 'Q', 1, 81, 2, 'tsu'),
(10, 'Q', 1, 81, 1, 'tsu'),
(11, 'Q', 1, 81, 7, 'tsu'),
(12, 'f', 2, 2, NULL, 's'),
(13, 'Z', 2, 871, NULL, 'e'),
(14, 'm', 3, 872, 2, 'pof'),
(15, 's', 3, 873, 31, 'pom'),
(16, 'R', 3, 874, 15, 'fc'),
(17, 'R', 3, 874, 1, 'fc'),
(18, 'R', 3, 874, 31, 'fc'),
(19, 'R', 3, 874, 9, 'fc'),
(20, 'R', 3, 874, 10, 'fc'),
(21, 'R', 3, 874, 4, 'fc'),
(22, 'R', 3, 874, 7, 'fc'),
(23, 'R', 3, 874, 3, 'fc'),
(24, 'R', 3, 874, 5, 'fc'),
(25, 'R', 3, 874, 18, 'fc'),
(26, 'R', 3, 874, 20, 'fc'),
(27, 'k', 3, 876, NULL, 's'),
(28, 'a', 3, 950, 31, 'rco'),
(29, 'y', 3, 1285, 7, 'pof'),
(30, 'N', 3, 1286, 22, 'pom'),
(31, 'i', 3, 1299, 1, 'fc'),
(32, 'i', 3, 1299, 5, 'fc'),
(33, 'i', 3, 1299, 3, 'fc'),
(34, 'i', 3, 1299, 20, 'fc'),
(35, 'i', 3, 1299, 4, 'fc'),
(36, 'i', 3, 1299, 9, 'fc'),
(37, 'i', 3, 1299, 10, 'fc'),
(38, 'i', 3, 1299, 22, 'fc'),
(39, 'i', 3, 1299, 15, 'fc'),
(40, 'i', 3, 1299, 18, 'fc'),
(41, 'I', 3, 1407, 9, 'pof'),
(42, 'T', 3, 1408, 19, 'pom'),
(43, 'u', 3, 1575, 4, 'pof'),
(44, 'V', 3, 1576, 30, 'pom'),
(45, 'B', 3, 2019, NULL, 'e'),
(46, 'h', 4, 60, NULL, 'e');
事件根据pid
和sid
列进行排序(例如,您可以分别以天和小时的形式查看它们,因为 pid 的时间单位比 sid 大,因此您应该首先按 pid 和然后通过 sid 获得正确的顺序)。如您所见,一些事件(event = tsu
或event = fc
)有不止一行,因为它们引用了许多对象(oid
),有些只引用了一个对象(如id = m
的事件),并且有些只有一行,但它们实际上指的是所有先前观察到的不是“死”的物体。它们是一些附加规则:
pof
或rco
事件时已死亡(如event
列中所述)
当对象遇到pof
事件时,会有另一个对象替换它并标记为pom
事件
我需要跟踪当前活动的对象。因此,我想将oid
是NULL
的行与可以从先前事件中推断出的所有活动对象“交叉连接”,其中“交叉连接”是指用oid = NULL
复制该行对于每个当前活跃的oid
。
由于可能难以从文本中掌握逻辑,因此我准备了预期的输出(可在SQL fiddle here 获得):
CREATE TABLE intermediate_result (
id CHAR(1),
pid INT,
sid INT,
oid INT,
event VARCHAR(3)
);
INSERT INTO intermediate_result (id, pid, sid, oid, event)
VALUES
('Q', 1, 81, 20, 'tsu'),
('Q', 1, 81, 9, 'tsu'),
('Q', 1, 81, 10, 'tsu'),
('Q', 1, 81, 4, 'tsu'),
('Q', 1, 81, 15, 'tsu'),
('Q', 1, 81, 3, 'tsu'),
('Q', 1, 81, 5, 'tsu'),
('Q', 1, 81, 18, 'tsu'),
('Q', 1, 81, 2, 'tsu'),
('Q', 1, 81, 1, 'tsu'),
('Q', 1, 81, 7, 'tsu'),
('f', 2, 2, 20, 's'),
('f', 2, 2, 9, 's'),
('f', 2, 2, 10, 's'),
('f', 2, 2, 4, 's'),
('f', 2, 2, 15, 's'),
('f', 2, 2, 3, 's'),
('f', 2, 2, 5, 's'),
('f', 2, 2, 18, 's'),
('f', 2, 2, 2, 's'),
('f', 2, 2, 1, 's'),
('f', 2, 2, 7, 's'),
('Z', 2, 871, 20, 'e'),
('Z', 2, 871, 9, 'e'),
('Z', 2, 871, 10, 'e'),
('Z', 2, 871, 4, 'e'),
('Z', 2, 871, 15, 'e'),
('Z', 2, 871, 3, 'e'),
('Z', 2, 871, 5, 'e'),
('Z', 2, 871, 18, 'e'),
('Z', 2, 871, 2, 'e'),
('Z', 2, 871, 1, 'e'),
('Z', 2, 871, 7, 'e'),
('m', 3, 872, 2, 'pof'),
('s', 3, 873, 31, 'pom'),
('R', 3, 874, 15, 'fc'),
('R', 3, 874, 1, 'fc'),
('R', 3, 874, 31, 'fc'),
('R', 3, 874, 9, 'fc'),
('R', 3, 874, 10, 'fc'),
('R', 3, 874, 4, 'fc'),
('R', 3, 874, 7, 'fc'),
('R', 3, 874, 3, 'fc'),
('R', 3, 874, 5, 'fc'),
('R', 3, 874, 18, 'fc'),
('R', 3, 874, 20, 'fc'),
('k', 3, 876, 15, 's'),
('k', 3, 876, 1, 's'),
('k', 3, 876, 31, 's'),
('k', 3, 876, 9, 's'),
('k', 3, 876, 10, 's'),
('k', 3, 876, 4, 's'),
('k', 3, 876, 7, 's'),
('k', 3, 876, 3, 's'),
('k', 3, 876, 5, 's'),
('k', 3, 876, 18, 's'),
('k', 3, 876, 20, 's'),
('a', 3, 950, 31, 'rco'),
('y', 3, 1285, 7, 'pof'),
('N', 3, 1286, 22, 'pom'),
('i', 3, 1299, 1, 'fc'),
('i', 3, 1299, 5, 'fc'),
('i', 3, 1299, 3, 'fc'),
('i', 3, 1299, 20, 'fc'),
('i', 3, 1299, 4, 'fc'),
('i', 3, 1299, 9, 'fc'),
('i', 3, 1299, 10, 'fc'),
('i', 3, 1299, 22, 'fc'),
('i', 3, 1299, 15, 'fc'),
('i', 3, 1299, 18, 'fc'),
('I', 3, 1407, 9, 'pof'),
('T', 3, 1408, 19, 'pom'),
('u', 3, 1575, 4, 'pof'),
('V', 3, 1576, 30, 'pom'),
('B', 3, 2019, 1, 'e'),
('B', 3, 2019, 5, 'e'),
('B', 3, 2019, 3, 'e'),
('B', 3, 2019, 20, 'e'),
('B', 3, 2019, 30, 'e'),
('B', 3, 2019, 19, 'e'),
('B', 3, 2019, 10, 'e'),
('B', 3, 2019, 22, 'e'),
('B', 3, 2019, 15, 'e'),
('B', 3, 2019, 18, 'e'),
('h', 4, 60, 1, 'e'),
('h', 4, 60, 5, 'e'),
('h', 4, 60, 3, 'e'),
('h', 4, 60, 20, 'e'),
('h', 4, 60, 30, 'e'),
('h', 4, 60, 19, 'e'),
('h', 4, 60, 10, 'e'),
('h', 4, 60, 22, 'e'),
('h', 4, 60, 15, 'e'),
('h', 4, 60, 18, 'e');
SQL 版本为 PostgreSQL 9.5。
【问题讨论】:
如果您向我们展示表的主键(或候选键)以及列的含义,将会提供信息。 该表没有主键,但id
、tid
和oid
的组合可以形成有效的主键。我无法透露列背后的确切含义,但用抽象的术语来说:该表由许多事件组成(事件ID标记为id
,事件类型标记为event
)。该事件可以影响许多对象 (oid
),但也可以只引用一个对象。 pid
和 sid
列的组合记录了事件的时间,因此可以用于排序表。 tid
实际上是指一组对象,但在示例中它是常量(和冗余)
没有主键的表没有意义。添加(自然)键。似乎存在一些(及时)排序(否则您将没有事件),因此至少其中一个关键组件应该具有类似时间的角色。并且:如果列对于示例是多余的:将它们排除在外!
我不确定您所说的没有主键的表没有意义。。为什么?但为了遵守您的评论,我添加了 key
列和后续自然数并删除了备用列。正如我之前提到的,排序是由pid
和sid
的组合给出的。如果这是来自您,请考虑撤销近距离投票。
en.wikipedia.org/wiki/Database_normalization (而您的 key
列是 代理键 :它不添加信息,仅添加身份)
【参考方案1】:
仅使用 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.sid, e.key
LOOP
IF _oid IS NULL THEN
-- expand to ordered set of active objects
RETURN QUERY
SELECT _id, _pid, _sid, 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, _pid, _sid, _oid, _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, _pid, _sid, _key)
ON CONFLICT (oid) DO
UPDATE
SET (pid, sid, key)
= (EXCLUDED.pid, EXCLUDED.sid, EXCLUDED.key);
END CASE;
END IF;
END LOOP;
END
$func$;
db小提琴here
解释一切比写东西更费力。我做了一些假设和要避免的陷阱。特别是,每个 'pof' 行必须紧跟一个 'pom' 行。
如果您对 PL/pgSQL 不坚定,请考虑聘请付费顾问。或者以您选择的程序语言实现。
【讨论】:
@wildplasser:是的,解释是一团糟,当试图理解和帮助时,这可能会令人愤怒。但是给 OP 一些功劳trying。他提供了一个可用的测试用例,包括小提琴,正确格式化并对请求做出反应。这比大多数人都多。如果他不会对这一切感到困惑,他就不会在这里。无论如何,试图理解这个问题在我的脑海中形成了一个答案。不想让它浪费。 @wildplasser:还有我的小烦恼:这个问题预先提供了 Postgres 版本。在我的书中得到一个加分点。 非常感谢!实际上也应该扩展具有键 45 和 46 的行。我不确定您所说的以键 14 和 15 终止系列的行是什么意思,但让我尝试进一步澄清:事件pof
和 pom
实际上在您可以对待 pof
的意义上是相关的作为特定oid
的“关闭”事件,并且当它被来自pom
事件的oid
替换时,pom
是后者的“开启”事件。
例如,在键为 41 的行中,对象 9 已失效并替换为对象 19。尽管在该事件之前的最后一个扩展对象列表中(键 31 到 40),但对象 9 应该不存在然后应该出现在键 45 和对象 19 的扩展行中。我希望现在这更有意义,但如果有任何进一步的解释有帮助,请告诉我!
@jakes 我之前的版本是基于一个误解,只是碰巧产生了类似的结果。【参考方案2】:
我终于能够进入 SQLFiddle 正确地查看问题。
应该重申主请求,说您希望交叉连接具有空值的活动对象,同时保留所有非空 oid 值的行
理解:
-
oid值代表对象
根据结果数据,活动对象依赖于时间,因此要计算活动对象,我们将依赖特定的时间行 ID,它代表时间。
表格插入是与时间相关的,并且将进入未来,后面的行代表未来时间
问题:根据主要请求替换 pom 对象的相关性仍未完全理解,对解决方案没有任何影响。
解决方案:
对于解决方案,有 3 个部分 -
-
添加到您的分机表的自动递增字段。您已经将其添加为 INT 并将其命名为 Key。这将用作唯一的 TimeID
With DistinctTimeIDs as
(
select a.key as TimeID from ext a
)
-
活动对象 - 历史确实很重要。在这种情况下,我们希望在没有 pof 或 rco 事件的特定 TimeID 之前的所有非空 oid。这将为数据集中的每个 TimeID 列出该 TimeID 之前的所有 Active 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, 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)
), NullObjectValues as
(
select * from ext a
where a.oid is null
)
select a.key ,a.id,a.pid,a.sid,a.oid,a.event from ext a where a.oid is not null
union all
select a.key ,a.id,a.pid,a.sid,b.oid,a.event from NullObjectValues a CROSS JOIN ActiveObjects b
where b.TimeID = a.key
order by 1;
SQL 小提琴是http://sqlfiddle.com/#!17/2fb57/2
【讨论】:
我喜欢您的解决方案,因为这种与活动对象的交叉连接是我想到的,但无法巩固一个想法。不过,我在理解 ActiveObjects 定义时遇到了一些麻烦 - 您能否逐步详细说明并更详细地解释 where 子句在这里的确切工作原理?我的意思是我难以理解的部分是为什么 ActiveObjects 会为初始 TimeID(小于 12)积累越来越多的对象 另一件事是,pom
事件的行实际上应该包含在 ActiveObjects 中,用于 TimeID 为 30、42 和 44,但公平地说,这似乎不会影响最终结果.
所以简单来说,每一行代表一个时间点。所以第一件事是获取所有的时间点——TimeID。其次,对于那个时间点,我们需要获取所有没有 pom 或 rco 事件的 oid。这是这部分: select b.TimeID, a.oid from ext a inner join DistinctTimeIDs b on b.TimeID>=a.key where oid is not null and not exist (select 'x' from ext where key以上是关于随着时间的推移跟踪活动对象的主要内容,如果未能解决你的问题,请参考以下文章
如何在内存分析期间跟踪 .NET 应用程序中内存访问的频率和数量?