我有数据库设计(在PostgreSQL 9.6中),其中存储了一个表公司数据。每个公司可以有一个或多个联系人,其详细信息列在另一个表中。 (简化)模式就是这样的:
DROP TABLE IF EXISTS test_company;
CREATE TABLE test_company (id integer, company_name text, contact_person integer[]);
DROP TABLE IF EXISTS test_contact_person;
CREATE TABLE test_contact_person (id integer, person_name text);
现在考虑这样的数据:
INSERT INTO test_company (id, company_name, contact_person) VALUES (1, 'Foo Ldt.', '{1,2}');
INSERT INTO test_company (id, company_name, contact_person) VALUES (2, 'Foo Sub Inc.', '{1,2}');
INSERT INTO test_company (id, company_name, contact_person) VALUES (3, 'Foo Sub Sub Inc.', '{1}');
INSERT INTO test_company (id, company_name, contact_person) VALUES (4, 'Bar Inc.', '{3,4}');
INSERT INTO test_company (id, company_name, contact_person) VALUES (5, 'Foo-Bar Joint-Venture', '{2,3,4}');
INSERT INTO test_contact_person(id, person_name) VALUES (1,'John');
INSERT INTO test_contact_person(id, person_name) VALUES (2,'Maria');
INSERT INTO test_contact_person(id, person_name) VALUES (3,'Bill');
INSERT INTO test_contact_person(id, person_name) VALUES (4,'Jane');
你知道,一个人可能是多家公司的联系人,即使是“对”(如'{1,2}'
也可能是相同的)。
现在要求,在查询公司时:
[{"id":1,"person_name":"John"}]
现在,我正在用这样的子查询来解决这个问题:
SELECT
id,
company_name,
(
SELECT json_agg(my_subquery) FROM
(
SELECT id, person_name FROM test_contact_person
WHERE id = ANY(test_company.contact_person)
)
AS my_subquery
)
contact_person_expanded
FROM test_company;
这给了我预期的结果。然而(一如既往)性能并不令人满意。顺便说一句:两个表上目前都没有索引。我现在想知道:
更新
仅供参考,我想指出RadimBača建议的解决方案似乎在提高性能方面起作用。
首先,我使用丑陋的plv8循环输入更多数据
DROP TABLE IF EXISTS test_company;
CREATE TABLE test_company (id integer, company_name text, contact_person integer[]);
DROP TABLE IF EXISTS test_contact_person;
CREATE TABLE test_contact_person (id integer, person_name text);
DO $$
for(var i = 1; i < 20000; i++) {
plv8.execute('INSERT INTO test_contact_person(id, person_name) VALUES ($1,$2)',[i,'SomePerson' + i]);
}
for(var i = 1; i < 10000; i++) {
plv8.execute('INSERT INTO test_company (id, company_name, contact_person) VALUES ($1,$2,$3)',[i,'SomeCompany' + i,[i,(20 -i)]]);
}
$$ LANGUAGE plv8;
然后我再次尝试了我的查询版本:
SELECT
id,
company_name,
(
SELECT json_agg(my_subquery) FROM
(
SELECT id, person_name FROM test_contact_person
WHERE id = ANY(test_company.contact_person)
)
AS my_subquery
)
contact_person_expanded
FROM test_company;
与
相比,这给了我(总是在本地机器上用pgAdmin 3测量)大约23秒的执行时间SELECT
comp.id,
comp.company_name,
json_agg(json_build_object('id', pers.id, 'person_name', pers.person_name)) AS contact_person_expanded
FROM test_company comp
JOIN test_contact_person pers ON comp.contact_person @> ARRAY[pers.id]
GROUP BY comp.id, comp.company_name
大约需要47秒 - 没有索引。
最后,我添加了一个索引:
DROP INDEX IF EXISTS idx_testcompany_contactperson;
CREATE INDEX idx_testcompany_contactperson on test_company USING GIN ("contact_person");
带子查询的版本的执行时间不会改变,但是当使用JOIN时效果非常明显: 1.1秒!
BTW:我曾经听说子查询test_company.contact_person @> ARRAY[id]
比id = ANY(test_company.contact_person)
快。据我测试,这不是真的。在我的例子中,后一个版本在23秒内返回所有行,而第一个版本花了46秒。
答案 0 :(得分:1)
我会使用M:N基数的常用关系方法
CREATE TABLE company (cid integer primary key, company_name text);
CREATE TABLE contact_person (pid integer primary key, person_name text);
CREATE TABLE contact(
cid integer references company,
pid integer references contact_person,
primary key(cid, pid)
);
对于第一个人,您只需添加以下值
INSERT INTO contact VALUES (1, 1);
INSERT INTO contact VALUES (1, 2);
-- and so on
如果您随后需要公司及其联系人,您只需使用以下JOIN
和JSON汇总
SELECT c.cid,
c.company_name,
json_agg(json_build_object('id', cp.pid, 'person_name', cp.person_name))
FROM company c
JOIN contact ct ON c.cid = ct.cid
JOIN contact_person cp ON cp.pid = ct.pid
GROUP BY c.cid, c.company_name
使用主键自动创建索引,因此性能应该没问题。问题是:你真的想要所有公司及其所有联系人吗?根本没有过滤器?
编辑根据您的评论,我至少会使用JOIN
而非相关子查询重写您的查询。它可能有助于优化者找到更好的计划。
SELECT
comp.id,
comp.company_name,
json_agg(json_build_object('id', pers.id, 'person_name', pers.person_name))
FROM test_company comp
JOIN test_contact_person pers ON comp.contact_person @> ARRAY[pers.id]
GROUP BY comp.id, comp.company_name
这种表示法应该允许Postgresql使用像这样的GIN索引
CREATE INDEX idx_testcompany_contactperson on test_company USING GIN ("contact_person");