Postgres递归2个交替的字段

时间:2012-01-23 15:25:12

标签: sql postgresql recursion

这就是我想要做的。我有一张简单的表格:

table USAGE_LOG:

USAGE_ID
PC_ID
MEMORY_STICK_ID

因此,每个使用条目代表一个与特定记忆棒一起使用的PC。

PC和记忆棒ID都链接到设备表,其中device_type对于PC为1,对于记忆棒为2。

我的情况是我在某些PC上发现了病毒,现在我需要使用我的USAGE_LOG表跟踪所有可能受感染的机器。

我的电脑被标记为在另一张桌子中有病毒:

table VIRUS:

PC_ID
VIRUS_ID

我需要创建一个CTE(或视图),通过递归搜索连接到受感染PC的每个记忆棒来保存所有可能受感染设备的列表,然后我需要找到连接到每个可疑记忆棒的每台PC ,以及随后的每台PC等等。

限制是我无法更改我的数据结构,也无法创建任何函数或存储过程。

这是可能的,如果是这样,它是如何完成的?

1 个答案:

答案 0 :(得分:1)

测试设置

我为那些想要自己查看的人提供了我的测试设置:

CREATE TEMP TABLE usage_log (
  usage_id int primary key
, pc_id  int
, memory_stick_id int);
INSERT INTO usage_log VALUES
  (1,1,2)
, (2,3,2)
, (3,3,4)
, (4,5,4)
, (5,5,6)
, (6,7,6)
, (7,9,8)
, (8,9,10)
, (9,3,12)
, (10,11,12)
, (11,11,13);

CREATE TEMP TABLE virus(
  pc_id int
, virus_id int);
INSERT INTO virus values
  (3, 4)
, (3, 5)
, (5, 6)

/* Alternative test:
TRUNCATE VIRUS;
INSERT INTO virus values
  (9, 4)
, (9, 5);
*/

使用两个递归CTE

进行查询

此查询应该会递归地查找可能被内部使用感染的所有PC和USB记忆棒:

WITH RECURSIVE v_start AS (
    SELECT pc_id, max(u.usage_id) AS usage_id
    FROM   virus v
    JOIN   usage_log u USING (pc_id)
    GROUP  BY 1
    ),

    v_down AS (
    SELECT u.*
    FROM   usage_log u
    JOIN   v_start v USING (usage_id)

    UNION
    SELECT DISTINCT u.*
    FROM   usage_log u 
    JOIN   v_down v ON u.pc_id = v.pc_id OR u.memory_stick_id = v.memory_stick_id
    WHERE  u.usage_id < v.usage_id
    ),

    v_up AS (
    SELECT u.*
    FROM   usage_log u
    JOIN   v_down v USING (usage_id)

    UNION
    SELECT DISTINCT u.*
    FROM   usage_log u 
    JOIN   v_up v ON u.pc_id = v.pc_id OR u.memory_stick_id = v.memory_stick_id
    WHERE  u.usage_id > v.usage_id
    )

SELECT pc_id, 1 AS device_type            -- PCs
FROM   v_up
GROUP  BY 1

UNION ALL
SELECT memory_stick_id, 2 AS device_type  -- USB-sticks
FROM   v_up
GROUP  BY 1
ORDER  BY 2, 1

解释查询

  • 我认为usage_id是反映时间表的连续列。

  • 在第一个CTE中{li>

    编辑 v_start我找到了每个受感染PC的最新日志条目。完全忽略了这种病毒。在我的第一个版本中,我有最早的使用min(usage_id),但那是在我改进查询以包含所有前体之前,然后然后让它沿着时间轴传播。我们现在必须从最新的使用开始,其中包括更多的嫌疑人。如果我们有更多信息可以使用,我们可以缩小搜索范围。

  • 第一个CTE也是RECURSIVE,因为从技术上讲,您一次只能使用一种CTE。没有UNION部分,它实际上是普通CTE 。有关WITH clause (CTE)

  • 的手册中有更多内容
  • 在以下两个CTE中,我按时返回工作,然后再转发以查找可能已联系的任何USB-stick PC。 时间顺序是相关的。请参阅以下说明 请注意,我在初稿中使用 UNION 代替UNION ALL。每一步消除重复都应该更有效。

  • 最终SELECT是一个穷人的数据透视表功能,同时删除PC和USB记忆棒之间的任何重复项。在这种情况下,两个列表都与UNION ALL放在一起。最终结果按设备类型和设备ID排序。

解释策略

最糟糕的情况是我们必须假设的,这意味着在任何使用期间,PC可能已被感染。

我们需要从最新用途中强化返回v_down),以包含病毒可能来自的所有设备。

然后,从所有可疑行开始,我们再次将前进v_up)延长,找到可能已被感染的任何和所有设备。

Voilá:可能涉及的所有PC和USB记忆棒。


通过示例解释

考虑这个设置:

INSERT INTO usage_log VALUES
  (1,1,2)  -- PC 1 connects to stick 3
, (3,3,4)  -- PC 3 connects to stick 4
, (2,3,2)  -- PC 3 connects to stick 2
, (2,3,6); -- PC 3 connects to stick 6

在这里,坚持4 从不与USB 2,代理或不接触。但如果我们忽略时间表,他们就会联系起来。 Stick 6通过代理连接到Stick 2,因为它连接到PC 3 它与Stick 2联系。