PL/pgSQL:查找人员所属的所有组(也间接)
Posted
技术标签:
【中文标题】PL/pgSQL:查找人员所属的所有组(也间接)【英文标题】:PL/pgSQL: finding all groups person belongs to (also indirectly) 【发布时间】: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"
他属于“人”只是因为他属于“学生”或“足球运动员”。他不是“人”组的直接成员。
提前致谢!
【问题讨论】:
【参考方案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 query 和RECURSIVE
修饰符,如@araqnid 所示。
【讨论】:
以上是关于PL/pgSQL:查找人员所属的所有组(也间接)的主要内容,如果未能解决你的问题,请参考以下文章
以前工作代码的新问题,用于查找用户所属的所有 AD 组(部分或所有身份引用无法翻译)