在SQL中对一组节点进行分组,以便组之间没有链接

时间:2017-10-25 08:44:15

标签: sql oracle grouping

我有一个获取项目列表的SQL查询。问题是项目可以链接到另一个项目(因为它共享一些资源)。我想获得一组项目,这样就不会在各组之间共享资源。

我确信这是一个常见的图形问题,但我不知道如何处理它。解决问题的最佳方法是什么?是否有算法在SQL查询中解决它?

(编辑)尝试使用分层方法对我拥有的数据来说很慢,我想因为它没有按层次结构组织。它可以进行预处理,使其更具层次性:

          (1)           (1)             (1)
          /|\     =>     |          =>   |
        /  | \          (3) .. (4)      (3) .. (4)
      (3)..|..(4)         \                     |
          (5)             (5)                  (5)

我看到了一种方法,但不知道何时停止该过程,除了计算每次迭代中的所有更改,并在没有可能的更改时停止。这是一种递归方式吗?

2 个答案:

答案 0 :(得分:1)

解决它的一种方法是使用分层查询:

SQL Fiddle

Oracle 11g R2架构设置

CREATE TABLE projects ( project ) AS
  SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 10;

CREATE TABLE project_links ( project, link ) AS
  SELECT 1, 2 FROM DUAL UNION ALL
  SELECT 2, 4 FROM DUAL UNION ALL
  SELECT 4, 5 FROM DUAL UNION ALL
  SELECT 3, 5 FROM DUAL UNION ALL
  SELECT 8, 6 FROM DUAL UNION ALL
  SELECT 8, 7 FROM DUAL UNION ALL
  SELECT 9, 6 FROM DUAL;

查询1

WITH two_way_links AS (
  SELECT project, link
  FROM   project_links
  UNION ALL
  SELECT link, project
  FROM   project_links
  GROUP BY project, link
)
SELECT MIN( CONNECT_BY_ROOT( p.project ) ) As root,
       COALESCE( l.link, p.project ) AS projects
FROM   projects p
       LEFT OUTER JOIN two_way_links l
       ON ( p.project = l.project )
CONNECT BY NOCYCLE
       PRIOR l.link = p.project
GROUP BY COALESCE( l.link, p.project )
ORDER  BY 1, 2

<强> Results

| ROOT | PROJECTS |
|------|----------|
|    1 |        1 |
|    1 |        2 |
|    1 |        3 |
|    1 |        4 |
|    1 |        5 |
|    6 |        6 |
|    6 |        7 |
|    6 |        8 |
|    6 |        9 |
|   10 |       10 |

<强>更新

您可以尝试查找最小组值来尝试加快速度:

(Explanation here in a related question)

SQL Fiddle

Oracle 11g R2架构设置

CREATE TABLE projects ( project ) AS
  SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 10;

CREATE TABLE project_links ( project, link ) AS
  SELECT 1, 2 FROM DUAL UNION ALL
  SELECT 2, 4 FROM DUAL UNION ALL
  SELECT 4, 5 FROM DUAL UNION ALL
  SELECT 3, 5 FROM DUAL UNION ALL
  SELECT 8, 6 FROM DUAL UNION ALL
  SELECT 8, 7 FROM DUAL UNION ALL
  SELECT 9, 6 FROM DUAL;

查询1

WITH two_way_links AS (
  SELECT project, link
  FROM   project_links
  UNION ALL
  SELECT link, project
  FROM   project_links
  GROUP BY project, link
),
min_links AS (
  SELECT l.*,
         FIRST_VALUE( LEAST( project, link ) )
           OVER( PARTITION BY project ORDER BY link ) AS min_link
  FROM   two_way_links l
)
SELECT MIN( CONNECT_BY_ROOT( p.project ) ) As root,
       COALESCE( l.link, p.project ) AS projects
FROM   projects p
       LEFT OUTER JOIN min_links l
       ON ( p.project = l.project )
START WITH p.project = l.min_link OR l.min_link IS NULL
CONNECT BY NOCYCLE
       PRIOR l.link = p.project
GROUP BY COALESCE( l.link, p.project )
ORDER  BY 1, 2

<强> Results

| ROOT | PROJECTS |
|------|----------|
|    1 |        1 |
|    1 |        2 |
|    1 |        3 |
|    1 |        4 |
|    1 |        5 |
|    6 |        6 |
|    6 |        7 |
|    6 |        8 |
|    6 |        9 |
|   10 |       10 |

答案 1 :(得分:0)

基于执行深度优先搜索的PL / SQL解决方案:

SQL Fiddle

Oracle 11g R2架构设置

CREATE TABLE projects ( project ) AS
  SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 10
/

CREATE TABLE project_links ( project, link ) AS
  SELECT 1, 2 FROM DUAL UNION ALL
  SELECT 2, 4 FROM DUAL UNION ALL
  SELECT 4, 5 FROM DUAL UNION ALL
  SELECT 3, 5 FROM DUAL UNION ALL
  SELECT 8, 6 FROM DUAL UNION ALL
  SELECT 8, 7 FROM DUAL UNION ALL
  SELECT 9, 6 FROM DUAL
/

CREATE TYPE IntList IS TABLE OF INTEGER
/

CREATE TYPE project_group IS OBJECT(
  project       INTEGER,
  project_group INTEGER
)
/

CREATE TYPE project_group_tab IS TABLE OF PROJECT_GROUP
/

CREATE PACKAGE projects_pkg IS
  TYPE project_vertex
    IS RECORD(
      project       PROJECTS.PROJECT%TYPE,
      project_group INTEGER,
      links         IntList
    );

  TYPE project_vertex_assoc_array
    IS TABLE OF project_vertex
    INDEX BY PLS_INTEGER;

  FUNCTION get_project_groups RETURN project_group_tab PIPELINED;
END;
/

CREATE PACKAGE BODY projects_pkg IS
  FUNCTION get_project_groups RETURN project_group_tab PIPELINED
  IS
    p_projects project_vertex_assoc_array;
    i          PLS_INTEGER;
    j          PLS_INTEGER;
    k          PLS_INTEGER;
    g          PLS_INTEGER := 0;
    stack      IntList;

  BEGIN
    FOR rec IN ( SELECT project FROM projects )
    LOOP
      p_projects(rec.project).project := rec.project;
      p_projects(rec.project).links   := IntList();
    END LOOP;

    FOR rec IN ( SELECT project, link FROM project_links )
    LOOP
      p_projects(rec.project).links.EXTEND;
      p_projects(rec.project).links(p_projects(rec.project).links.COUNT) := rec.link;
      p_projects(rec.link).links.EXTEND;
      p_projects(rec.link).links(p_projects(rec.link).links.COUNT) := rec.project;
    END LOOP;

    i := p_projects.FIRST;
    WHILE i IS NOT NULL LOOP
      IF p_projects(i).project_group IS NULL THEN
        g := g + 1;
        stack := IntList( i );
        p_projects(i).project_group := g;
        WHILE stack.COUNT > 0 LOOP
          j := stack(stack.COUNT);
          stack.TRIM;
          FOR n IN REVERSE 1 .. p_projects(j).links.COUNT LOOP
            k := p_projects(j).links(n);
            IF p_projects(k).project_group IS NULL THEN
              p_projects(k).project_group := g;
              stack.EXTEND;
              stack( stack.COUNT ) := k;
            END IF;
          END LOOP;
        END LOOP;
      END IF;
      PIPE ROW(
        project_group(
          p_projects(i).project,
          p_projects(i).project_group
        )
      );
      i := p_projects.NEXT(i);
    END LOOP;
  END;
END;
/

查询1

SELECT *
FROM   TABLE( projects_pkg.get_project_groups )

<强> Results

| PROJECT | PROJECT_GROUP |
|---------|---------------|
|       1 |             1 |
|       2 |             1 |
|       3 |             1 |
|       4 |             1 |
|       5 |             1 |
|       6 |             2 |
|       7 |             2 |
|       8 |             2 |
|       9 |             2 |
|      10 |             3 |