PL/pgSQL:查找人员所属的所有组(也是间接的)

发布于 2024-11-30 02:39:38 字数 1376 浏览 1 评论 0原文

简单介绍:

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

架构:

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 函数,它可以查找特定用户所属的所有组(其名称)。

示例:

群组名称:People, 组父组:空

组名称:学生, 组父组:人员

组名称:Football_players, 组父组:People

组名称:Basketball_players, 组父组:People

用户名:Maciej, groups : Students, Football_players

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

他属于“People”只是因为他属于“Students”或“Football_players”。他不是“人民”团体的直接成员。

提前致谢!

Simple intro:

I have a database with users and groups.
Every user might be a member of one or more groups.
Every group might have one or more parent groups.

Schema:

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));

I'm looking for a PL/pgSQL function which finds all groups (their names) particular user belongs to.

Example:

group name: People,
group parent: null

group name: Students,
group parent: People

group name: Football_players,
group parent: People

group name: Basketball_players,
group parent: People

user name: Maciej,
groups : Students, Football_players

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

He belongs to "People" just because he belongs to "Students" or "Football_players". He is not a direct member of "People" group.

Thanks in advance!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

淡忘如思 2024-12-07 02:39:38
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
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
往日情怀 2024-12-07 02:39:38

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

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)

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

编辑:

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

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 查询 以及 RECURSIVE 修饰符,如 @araqnid 所示。

If you have just one level of inheritance (as in example), then you could use such query:

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;

Result:

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

PL/pgSQL function:

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';

Result:

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

However for nested parent groups I think that recursion should be suitable.

EDIT:

Here is recursion-based variant for nested parent groups:

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';

Example insert:

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);

Result:

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

Another way is to use WITH query along with RECURSIVE modifier as @araqnid shown.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文