postgresql 检查一对多关系是否在另一个关系中具有所有值

zd287kbt  于 2023-11-18  发布在  PostgreSQL
关注(0)|答案(3)|浏览(182)

我有一个表,其中包含所有的报告

report_id   | Name 
---------------+-------------
      1        | Sales Q3
      2        | Sales Q2

字符串
我有一个关系表,其中包含一个到多个关系的报告与部门

report_id   |  dept_id  
-----------------+---------------
      1       | 4
      1       | 5
      2       | 4


关系表表示报告(1)包含有关部门(4 & 5)的数据,报告(2)包含有关部门(4)的数据
我有另一个表,它指定了用户和部门之间的关系。

user_id     |  dept_id  
-----------------+---------------
      1       | 4
      1       | 5
      2       | 4
      3       | 5


用户表还表示用户和部门之间的关系。
我需要获取一个特定用户的所有报告集,只有当用户在该特定报告中的所有部门。
例如
用户(1)有权访问dept(4,5),因此查询应返回两个报告(1,2)
用户(2)有权访问部门(4),因此查询应仅返回报告(2),而不应返回报告(1),因为它与部门(5)有关
用户(3)有权访问部门(5),因此查询不应返回任何报告,因为报告与部门(4)相关
类似于

Select * from reports inner join relation on relation.report_id = reports.report_id ( some condition to satisfy all rows of relations.dept_id are present in users.dept_id table ) where users.user_id = 1


考虑到上述用例,这些表是维护报表、部门和用户之间关系的正确方法吗?
编辑:
添加了架构定义
Postgres 9.5版

CREATE TABLE reports (
    report_id integer NOT NULL,
    report_name varchar(100)
);

CREATE TABLE relation (
    report_id integer NOT NULL,
    dept_id integer NOT NULL
);

CREATE TABLE users (
    user_id integer NOT NULL,
    dept_id integer NOT NULL
);
INSERT INTO reports VALUES(1, 'Sales Q3'),(2, 'Sales Q2');
INSERT INTO relation VALUES(1, 4),(1, 5),(2,4);
INSERT INTO users VALUES(1, 4),(1, 5),(2,4),(3,5);

k3fezbri

k3fezbri1#

我们可以在COUNT()作为窗口函数的帮助下尝试join方法:

WITH cte AS (
    SELECT rels.user_id, rpt.report_id, rpt.Name,
           COUNT(rel.dept_id) OVER (PARTITION BY rels.user_id) rpt_cnt,
           COUNT(*) OVER (PARTITION BY rels.user_id) total_cnt
    FROM relations rels
    LEFT JOIN relation rel ON rel.dept_id = rels.dept_id
    INNER JOIN reports rpt ON rpt.report_id = rel.report_id
)

SELECT user_id, report_id, Name
FROM cte
WHERE rpt_cnt = total_cnt
ORDER BY user_id, report_id;

字符串
上面的想法是,我们尝试为每个用户的部门找到一个匹配的报告。如果所有部门都匹配一个报告,我们将返回给定用户的所有报告。

d4so4syb

d4so4syb2#

用数组实现的情况下.
创建了用户可以访问的dept_id数组,并将报告中的dept_id转换为数组。
如果用户部门数组完全存在于报告部门数组中,请确定。

select reports.report_name,rel.report_id,rel.dept_list from reports 
inner join 
(select report_id,count(*) as total,array_agg(dept_id) as dept_list 
from relation group by report_id) 
rel on rel.report_id=reports.report_id and 
rel.dept_list <@ (select array_agg(dept_id) as user_dept_list from users where user_id=1 group by user_id);

字符串

wsxa1bj1

wsxa1bj13#

  • “查找给定用户所属的任何部门的报告,并且未为该用户所属的任何部门发布。"*
WITH usr AS (SELECT dept_id FROM users WHERE user_id = 1)
SELECT rep.report_name, rel.*
FROM   reports rep
JOIN  (
   SELECT report_id, array_agg(dept_id) AS dept_list 
   FROM   usr
   JOIN   relation rel USING (dept_id)
   GROUP  BY 1
   ) rel  USING (report_id)
WHERE  NOT EXISTS (
   SELECT FROM relation rel1
   WHERE  rel1.report_id = rel.report_id
   AND    rel1.dept_id NOT IN (TABLE usr)
   );

字符串
fiddle
基于几个假设,这个问题没有公开。比如:所有涉及的列都定义为NOT NULL
为了获得最佳性能,在relation(dept_id, report_id)上设置一个索引**,在relation(report_id, dept_id)上设置一个**索引(另一列在前)。两者之一可以是PK,另一个是额外的普通索引。请参阅我的小提琴中的索引!请参阅:

你后来发布了一个类似问题的答案-可以优化:

SELECT rep.report_name, rel.*
FROM   reports rep
JOIN  (
   SELECT report_id, array_agg(dept_id) AS dept_list
   FROM   relation
   GROUP  BY 1
   ) rel USING (report_id)
WHERE  rel.dept_list <@ ARRAY(SELECT dept_id FROM users WHERE user_id = 1)
ORDER  BY rep.report_id;  -- optional


fiddle

我的第一个查询应该对大表执行得更好,因为它可以提前排除大多数不相关的报告,而您的查询必须处理 * 所有 * 报告。

相关问题