PostgreSQL 9.3 - 比较两组数据而不重复第一组中的值

时间:2015-02-12 02:52:21

标签: sql postgresql plpgsql postgresql-9.3

我有一组表定义了一些需要遵循的规则,例如:

CREATE TABLE foo.subrules (
    subruleid SERIAL PRIMARY KEY,
    ruleid INTEGER REFERENCES foo.rules(ruleid),
    subrule INTEGER,
    barid INTEGER REFERENCES foo.bars(barid)
);

INSERT INTO foo.subrules(ruleid,subrule,barid) VALUES 
    (1,1,1),
    (1,1,2),
    (1,2,2),
    (1,2,3),
    (1,2,4),
    (1,3,3),
    (1,3,4),
    (1,3,5),
    (1,3,6),
    (1,3,7);

这定义的是一组"子规则"需要满足......如果所有"子规则"如果满意,那么规则也会得到满足。 在上面的例子中," subruleid" 1可以满足于" barid"值12。 另外," subruleid" 2可以满足于" barid" 234的值。 同样," subruleid" 3可以满足于" barid"值34567

我还有一个如下所示的数据集:

 primarykey |  resource  |   barid  
------------|------------|------------
     1      |     A      |     1      
     2      |     B      |     2      
     3      |     C      |     8        

棘手的部分是,一旦" subrule"对资源"满意,#34;资源"不能满足任何其他"子规则" (即使相同的" barid"会满足另一个" subrule")

所以,我需要的是评估并返回以下结果:

   ruleid   |   subrule  |   barid    | primarykey |  resource  
------------|------------|------------|------------|------------
     1      |     1      |     1      |     1      |     A      
     1      |     1      |     2      |    NULL    |    NULL
     1      |     2      |     2      |     2      |     B      
     1      |     2      |     3      |    NULL    |    NULL
     1      |     2      |     4      |    NULL    |    NULL
     1      |     3      |     3      |    NULL    |    NULL    
     1      |     3      |     4      |    NULL    |    NULL
     1      |     3      |     5      |    NULL    |    NULL
     1      |     3      |     6      |    NULL    |    NULL
     1      |     3      |     7      |    NULL    |    NULL
    NULL    |    NULL    |    NULL    |     3      |     C

有趣的是,如果" primarykey" 3有一个" barid"值2(而非8)的结果将相同。

我尝试了几种方法,包括plpgsql函数,通过" subruleid"执行分组。使用ARRAY_AGG(barid)并从barid构建一个数组,并检查barid数组中的每个元素是否在" subruleid"通过循环分组,但它感觉不对。

是否有更优雅或更有效的选择?

2 个答案:

答案 0 :(得分:2)

以下片段找到解决方案(如果有)。第三(资源)是硬编码的。如果只需要一个解决方案,则应添加一些对称断路器。

如果资源的数量没有限制,我认为可以通过列举所有可能的表格(Hilbert?mixed-radix?)并在修剪之后选择它们来解决问题。令人满意的。

 -- the data
CREATE TABLE subrules
    ( subruleid SERIAL PRIMARY KEY
    , ruleid INTEGER -- REFERENCES foo.rules(ruleid),
    , subrule INTEGER
    , barid INTEGER -- REFERENCES foo.bars(barid)
);

INSERT INTO subrules(ruleid,subrule,barid) VALUES
    (1,1,1), (1,1,2),
    (1,2,2), (1,2,3), (1,2,4),
    (1,3,3), (1,3,4), (1,3,5), (1,3,6), (1,3,7);

CREATE TABLE resources
    ( primarykey INTEGER NOT NULL PRIMARY KEY
    ,  resrc  varchar
    ,  barid  INTEGER NOT NULL
        );

INSERT INTO resources(primarykey,resrc,barid) VALUES
      (1, 'A', 1) ,(2, 'B', 2) ,(3, 'C', 8)
        -- ################################
        -- uncomment next line to find a (two!) solution(s)
     -- ,(4, 'D', 7)
        ;

-- all matching pairs of subrules <--> resources
WITH pairs AS (
        SELECT sr.subruleid, sr.ruleid, sr.subrule, sr.barid
        , re.primarykey, re.resrc
        FROM subrules sr
        JOIN resources re ON re.barid = sr.barid
        )
SELECT
        p1.ruleid AS ru1 , p1.subrule AS sr1 , p1.resrc AS one
        , p2.ruleid AS ru2 , p2.subrule AS sr2 , p2.resrc AS two
        , p3.ruleid AS ru3 , p3.subrule AS sr3 , p3.resrc AS three
  -- self-join the pairs, excluding the ones that
  -- use the same subrule or resource
FROM pairs p1
JOIN pairs p2 ON p2.primarykey > p1.primarykey -- tie-breaker
JOIN pairs p3 ON p3.primarykey > p2.primarykey -- tie breaker
WHERE 1=1
AND p2.subruleid <> p1.subruleid
AND p2.subruleid <> p3.subruleid
AND p3.subruleid <> p1.subruleid
        ;

结果(取消注释缺少资源的行):

 ru1 | sr1 | one | ru2 | sr2 | two | ru3 | sr3 | three 
-----+-----+-----+-----+-----+-----+-----+-----+-------
   1 |   1 | A   |   1 |   1 | B   |   1 |   3 | D
   1 |   1 | A   |   1 |   2 | B   |   1 |   3 | D
(2 rows)

资源{A,B,C}当然可以是硬编码的,但这会阻止“D”记录(或任何其他记录)作为缺失的链接。

答案 1 :(得分:1)

既然你没有澄清这个问题,我会按照我自己的假设。

  • subrule数字按升序排列,每条规则都没有间隙。
  • 表格(subrule, barid)中的
  • UNIQUEsubrules
  • 如果同一barid有多个资源,则这些对等项中的分配是任意的。
  • 如评论所述,资源数量与子规则数相匹配(这对我建议的解决方案没有影响)。
  • 算法如下:

    1. 选择最小subrule个数字的子规则。
    2. 将资源分配给可能的最低barid(第一个具有匹配资源的资源),这会消耗资源。
    3. 匹配第一个资源后,跳至下一个较高的subruleid并重复2.
    4. 在最后一个子规则之后附加所有剩余资源。

您可以使用递归CTE 纯SQL 实现此功能:

WITH RECURSIVE cte AS ((
   SELECT s.*, r.resourceid, r.resource
        , CASE WHEN r.resourceid IS NULL THEN '{}'::int[]
               ELSE ARRAY[r.resourceid] END AS consumed
   FROM   subrules s
   LEFT   JOIN resource r USING (barid)
   WHERE  s.ruleid = 1
   ORDER  BY s.subrule, r.barid, s.barid
   LIMIT  1
   )
   UNION ALL (
   SELECT s.*, r.resourceid, r.resource
        , CASE WHEN r.resourceid IS NULL THEN c.consumed
                                         ELSE c.consumed || r.resourceid END
   FROM   cte           c
   JOIN   subrules      s ON s.subrule = c.subrule + 1
   LEFT   JOIN resource r ON r.barid = s.barid
                         AND r.resourceid <> ALL (c.consumed)
   ORDER  BY r.barid, s.barid
   LIMIT  1
   ))
SELECT ruleid, subrule, barid, resourceid, resource FROM cte

UNION ALL  -- add unused rules
SELECT s.ruleid, s.subrule, s.barid, NULL, NULL 
FROM   subrules s
LEFT   JOIN cte c USING (subruleid)
WHERE  c.subruleid IS NULL

UNION ALL  -- add unused resources
SELECT NULL, NULL, r.barid, r.resourceid, r.resource
FROM   resource r
LEFT   JOIN cte c USING (resourceid)
WHERE  c.resourceid IS NULL    
ORDER  BY subrule, barid, resourceid;

返回 完全 您要求的结果 SQL Fiddle.

解释

它基本上是上面列出的算法的实现。

  • baridsubrule只能进行一次匹配。因此LIMIT 1需要额外的括号:

  • 收集&#34;消费&#34;数组consumed中的资源,并将其排除在r.resourceid <> ALL (c.consumed)的重复分配之外。特别注意我如何避免数组中的NULL值,这会破坏测试。

  • CTE仅返回匹配的行。在外部SELECT中添加不匹配的规则和资源,以获得完整的结果。


或者你在表subruleresource上打开两个游标,并用任何不错的编程语言(包括PL / pgSQL)实现算法。