请注意,我正在使用postgresql
我有一个organizations
表,一个users
表,一个jobs
表和一个documents
表。我想获得一份按号订购的组织列表,这些组织可以访问他们。
organizations
------------
id (pk)
company_name
users
------------
id (pk)
organization_id
jobs
------------
id (pk)
client_id (id of an organization)
server_id (id of an organization)
creator_id (id of a user)
documents
------------
id (pk)
job_id
所需结果
organizations.id | organizations.company_name | document_count
85 | Big Corporation | 84
905 | Some other folks | 65
403 | ACME, Inc | 14
如您所见,组织可以通过3种不同的路径连接到文档:
organizations.id
=> jobs.client_id
=> documents.job_id
organizations.id
=> jobs.server_id
=> documents.job_id
organizations.id
=> users.organization_id
=> jobs.creator_id
=> documents.job_id
但是我想要一个查询,它将获得每个公司有权访问的所有文件的数量......
我尝试了几件事......就像这样:
SELECT COUNT(documents.id) document_count, organizations.id, organizations.company_name
FROM organizations
INNER JOIN users ON organizations.id = users.organization_id
INNER JOIN jobs ON (
jobs.client_id = organizations.id OR
jobs.server_id = organizations.id OR
jobs.creator_id = users.id
)
INNER JOIN documents ON documents.job_id = jobs.id
GROUP BY organizations.id, organizations.company_name
ORDER BY document_count DESC
LIMIT 10
查询需要一段时间才能运行,但这并不可怕,因为我正在为一次性报告执行此操作,但结果......可能不正确。
第一个列出的组织报告的文件数量为129,834个 - 但这是不可能的,因为documents
表中只有32,820个记录。我觉得它必须计算大量的重复项(由于我的一个连接中的错误?)但我不确定我哪里出错了。
订单显示正确,因为系统的最高容量用户显然位于列表的顶部...但是值以某种方式膨胀。
答案 0 :(得分:1)
问题在于,如果jobs.client_id = organizations.id
或jobs.server_id = organizations.id
,则无法过滤您的INNER JOIN users
(除了ON
条款),所以你&# 39;将为属于该组织的每个用户获取单独的记录。换句话说,对于每个组织,您都要添加三个值:
解决此问题的一种方法是删除INNER JOIN users
行,然后更改:
jobs.creator_id = users.id
到此:
jobs.creator_id IN (SELECT id FROM users WHERE organization_id = organizations.id)
。 。 。但这可能表现得非常糟糕。在找到可接受的查询之前,您可能需要尝试一些事情。
答案 1 :(得分:1)
简化你的想法。你有3个docid路径,所以写3个查询,联合它们并计算
答案 2 :(得分:0)
重新设计它可能为时已晚,但你真的应该这样做。
jobs表不应该有自己的id字段和密钥。
作业表设计非常糟糕,因为每次从id索引对磁盘页面的引用都必须从数据文件中读取1-100个不同的页面才能获得总是想要使用(这是一个工作不应该有自己的id的线索)。
您可以通过使作业使用作业ID字段上的群集或群集索引(取决于数据库系统)来快速解决问题。另一种方法是将其他三个id字段标记为索引上的“包含”,这样页面读取到数据文件就会100%消失。这些中的任何一个都足以使这个“正常工作”。
我鼓励你做的是将id字段和键放在作业上,而是创建一个“自然键”,其中包含其他三个id字段,并在文档表中使用该键。
我还会在作业表和文档表上士气(重复)创建者的组织。用户不会移动到另一个组织并保持相同的访问权限,因此您永远不必运行扫描来同步更新这些访问,即使您这样做也很容易。
通过这些更改,您可以直接在文档表上执行选择,跳过其他表中所需的随机页面读取。在三个不同的id字段中分组的组将有点棘手。我可能会尝试这个,因为它很有趣。
在短期内,尝试群集或包含在作业表上以解决性能问题,我将在今晚检查连接逻辑。
答案 3 :(得分:0)
但是我想要一个查询来获取您有权访问的所有文件的数量......
这就是你的查询开始的地方:
SELECT ... FROM documents
...
由于文档表的唯一线索是作业,因此您还需要作业表::
SELECT ...
FROM documents dc
JOIN jobs jo ON jo.document_id = dc.id
...
现在,是时候进行限制了。 您真正想要哪些文件?您需要三种情况:client_id匹配 组织,或者server_id maches 公司,或者creator_id匹配恰好为工作的用户公司:
SELECT ...
FROM documents dc
JOIN jobs jo ON jo.document_id = dc.id
WHERE jo.client_id = $THE_COMPANY
OR jo.server_id = $THE_COMPANY
OR EXISTS (
SELECT *
FROM users uu
JOIN organizations oo ON uu.organization_id = ex.id
WHERE uu.id = jo.creator_id
AND oo.id = $THE_COMAPNY
)
;
但是,这里可能存在问题。如果两个或多个不同的作业记录指向同一个文档,那么您将计算这两个。您可以向外部查询添加DISTINCT
,也可以将jobs-table向下移动到子查询中:
SELECT ...
FROM documents dc
WHERE EXISTS (
SELECT *
FROM jobs jo
WHERE jo.document_id = dc.id
AND ( jo.client_id = $THE_COMPANY
OR jo.server_id = $THE_COMPANY
OR EXISTS (
SELECT *
FROM users uu
JOIN organizations oo ON uu.organization_id = ex.id
WHERE uu.id = jo.creator_id
AND oo.id = $THE_COMAPNY
)
)
)
;
正如您所看到的,选择文档的方式最终会出现在WHERE (a OR b OR c)
条款中。
更新:(由于OP没有以可用的形式向我们提供表格定义,我必须重建这些表格)
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;
--
-- create the missing tables
--
CREATE TABLE organizations
( id SERIAL NOT NULL PRIMARY KEY
, company_name varchar
);
CREATE TABLE users
( id SERIAL NOT NULL PRIMARY KEY
, organization_id INTEGER NOT NULL REFERENCES organizations(id)
);
CREATE TABLE jobs
( id SERIAL NOT NULL PRIMARY KEY
, client_id INTEGER NOT NULL REFERENCES organizations(id)
, server_id INTEGER NOT NULL REFERENCES organizations(id)
, creator_id INTEGER NOT NULL REFERENCES users(id)
);
CREATE TABLE documents
( id SERIAL NOT NULL PRIMARY KEY
, job_id INTEGER NOT NULL REFERENCES jobs(id)
);
--
-- Populate
--
INSERT INTO organizations(id, company_name) VALUES
(85,'Big Corporation') ,(905,'Some other folks') ,(403,'ACME, Inc')
;
select setval('organizations_id_seq', 905);
INSERT INTO users(organization_id)
SELECT o.id
FROM generate_series(1,1000)
JOIN organizations o ON random() < 0.3
;
INSERT INTO jobs (client_id,server_id,creator_id)
SELECT o1.id, o2.id, u.id
FROM users u
JOIN organizations o1 ON 1=1
JOIN organizations o2 ON o2.id <> o1.id
;
INSERT INTO documents(job_id)
SELECT id FROM jobs j
;
DELETE FROM documents
WHERE random() < 0.5
;
--
-- And the query ...
--
EXPLAIN ANALYZE
SELECT o.id AS org
, count(*) AS the_docs
FROM organizations o
JOIN documents d ON 1=1 -- start with a carthesian product
WHERE EXISTS (
SELECT *
FROM jobs j
WHERE d.job_id = j.id
AND (j.client_id = o.id OR j.server_id = o.id )
)
OR EXISTS (
SELECT *
FROM jobs j
JOIN users u ON j.creator_id = u.id
WHERE u.organization_id = o.id
AND d.job_id = j.id
)
GROUP BY o.id
;
答案 4 :(得分:0)
除了建议UNION
之外,没有一个答案能让我在那里。这就是我想出的:
SELECT COUNT(docs.doc_id) document_count, docs.org_id, docs.org_name
FROM (
SELECT documents.id doc_id, organizations.id org_id, organizations.company_name org_name
FROM documents
INNER JOIN jobs ON documents.job_id = jobs.id
INNER JOIN organizations ON jobs.client_id = organizations.id
UNION
SELECT documents.id doc_id, organizations.id org_id, organizations.company_name org_name
FROM documents
INNER JOIN jobs ON documents.job_id = jobs.id
INNER JOIN organizations ON jobs.server_id = organizations.id
UNION
SELECT documents.id doc_id, organizations.id org_id, organizations.company_name org_name
FROM documents
INNER JOIN jobs on documents.job_id = jobs.id
INNER JOIN users ON jobs.creator_id = users.id
INNER JOIN organizations ON users.organization_id = organizations.id
) docs
GROUP BY org_id, org_name
ORDER BY document_count DESC
性能比任何建议子查询的人要好得多,而且它似乎给了我一个合理的答案