【问题标题】:PL/pgSQL: finding all groups person belongs to (also indirectly)PL/pgSQL:查找人员所属的所有组(也间接)
【发布时间】:2011-08-18 08:32:09
【问题描述】:

简单介绍:

我有一个包含用户和组的数据库。 每个用户都可能是一个或多个组的成员。 每个组可能有一个或多个父组。

架构:

CREATE TABLE users(
  username VARCHAR(64) NOT NULL PRIMARY KEY,
  password VARCHAR(64) NOT NULL,
  enabled BOOLEAN NOT NULL);

CREATE TABLE groups (
  id bigserial NOT NULL PRIMARY KEY,
  group_name VARCHAR(64) NOT NULL);

CREATE TABLE groups_inheritance (
  group_id bigint NOT NULL,
  parent_group_id bigint NOT NULL,
  CONSTRAINT fk_group_inheritance_group FOREIGN KEY(group_id) REFERENCES groups(id),
  CONSTRAINT fk_group_inheritance_group_2 FOREIGN KEY(parent_group_id) REFERENCES groups(id),
  CONSTRAINT unique_uk_groups_inheritance UNIQUE(group_id, parent_group_id));

CREATE TABLE group_members (
  id bigint PRIMARY KEY,
  username VARCHAR(64) NOT NULL,
  group_id bigint NOT NULL,
  CONSTRAINT fk_group_members_username FOREIGN KEY(username) REFERENCES users(username),
  CONSTRAINT fk_group_members_group FOREIGN KEY(group_id) REFERENCES groups(id));

我正在寻找一个 PL/pgSQL 函数,它可以找到特定用户所属的所有组(他们的名字)。

例子:

组名:人, 组父级:null

组名:学生, 组父级:人

组名:Football_players, 组父级:人

组名:Basketball_players, 组父级:人

用户名:Maciej, 组:学生、Football_players

f("Maciej") = {"Students", "People", "Football_players"}

他属于“人”只是因为他属于“学生”或“足球运动员”。他不是“人”组的直接成员。

提前致谢!

【问题讨论】:

    标签: sql function postgresql plpgsql


    【解决方案1】:
    WITH RECURSIVE group_ancestry AS (
    SELECT group_id, username
    FROM group_members
    UNION
    SELECT groups_inheritance.parent_group_id, username
    FROM group_ancestry
         JOIN groups_inheritance ON groups_inheritance.group_id = group_ancestry.group_id
    )
    SELECT username, group_id
    FROM group_ancestry
    

    【讨论】:

    • Evetually,我决定接受这个aswer,因为它更简洁和适应性强。
    【解决方案2】:

    如果您只有一级继承(如示例),那么您可以使用这样的查询:

    WITH group_ids AS
    (
        SELECT group_id
        FROM group_members
        WHERE username LIKE 'Maciej'
    )
    SELECT group_name
    FROM
        (SELECT group_id FROM group_ids
            UNION
        SELECT DISTINCT parent_group_id
        FROM groups_inheritance INNER JOIN group_ids USING(group_id)) g
    INNER JOIN groups ON id = group_id;
    

    结果:

        group_name    
    ------------------
     People
     Students
     Football_players
    (3 rows)
    

    PL/pgSQL 函数:

    DROP FUNCTION IF EXISTS f(varchar(64));
    CREATE FUNCTION f(username varchar(64))
    RETURNS text[] AS $$
    DECLARE
        gId bigint;
        pgId bigint;
        gName text;
        result text[] = '{}';
    BEGIN
        FOR gId IN SELECT group_id FROM group_members WHERE username LIKE username
        LOOP
            SELECT INTO gName group_name FROM groupS WHERE id = gId;
            result := result || gName;
            FOR pgId IN SELECT parent_group_id FROM groups_inheritance WHERE group_id = gId
            LOOP
                SELECT INTO gName group_name FROM groups WHERE id = pgId;
                IF NOT (result @> ARRAY[gName]) THEN
                    result := result || gName;
                END IF;
            END LOOP;
        END LOOP;
        RETURN result;
    END $$
    LANGUAGE 'plpgsql';
    

    结果:

    SELECT f('Maciej');
                     f                  
    ------------------------------------
     {Students,People,Football_players}
    (1 row)
    

    但是对于嵌套父组,我认为递归应该是合适的。

    编辑:

    这是嵌套父组的基于递归的变体:

    CREATE OR REPLACE FUNCTION f_recursive(gIdParam bigint, resultArrayParam bigint[])
    RETURNS bigint[] AS $$
    DECLARE
        pgId bigint;
        resultArray bigint[];
    BEGIN
        FOR pgId IN SELECT parent_group_id FROM groups_inheritance WHERE group_id = gIdParam
        LOOP
            IF NOT (resultArrayParam @> ARRAY[pgId]) THEN
                resultArray := resultArray || pgId;
                resultArray := resultArray || f_recursive(pgId, resultArray);
            END IF;
        END LOOP;
        RETURN resultArray;
    END $$
    LANGUAGE 'plpgsql';
    
    CREATE OR REPLACE FUNCTION f(usernameParam varchar(64))
    RETURNS text[] AS $$
    DECLARE
        gId bigint;
        resultArray bigint[];
    BEGIN
        FOR gId IN SELECT group_id FROM group_members WHERE username LIKE usernameParam
        LOOP
            resultArray := resultArray || gId;
            resultArray := resultArray || f_recursive(gId, resultArray);
        END LOOP;
        RETURN array_agg(group_name) 
                FROM groups INNER JOIN (SELECT unnest(resultArray)) u ON unnest = id;
    END $$
    LANGUAGE 'plpgsql';
    

    示例插入:

    INSERT INTO groups (id, group_name) VALUES
        (1, 'People'), (2, 'Workers'), (3, 'Programmers'),
        (4, 'AI-Programmers'), (5, 'Administators'), (6, 'Managers');
    
    INSERT INTO groups_inheritance (group_id, parent_group_id) VALUES
        (2, 1), (3, 2), (4, 3), (5, 2), (6, 2);
    
    INSERT INTO users (username, password, enabled) VALUES
        ('Maciej', '12345', true);
    
    INSERT INTO group_members (id, username, group_id) VALUES
        (1, 'Maciej', 4), (2, 'Maciej', 5);
    

    结果:

    SELECT f('Maciej');
                                 f                             
    -----------------------------------------------------------
     {AI-Programmers,Programmers,Workers,People,Administators}
    (1 row)
    

    另一种方法是使用WITH queryRECURSIVE 修饰符,如@araqnid 所示。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-27
      • 2013-01-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-29
      相关资源
      最近更新 更多