2017-11-14 1 views
0

나는 하나의 테이블 회사 데이터가 저장되는 (PostgreSQL 9.6에서) 데이터베이스 디자인을 가지고 있습니다. 각 회사에는 세부 사항이 다른 테이블에 배치 된 담당자가 한 명 이상있을 수 있습니다. 그런 (간체) 스키마되는 것을 : JSON 배열 데이터의 하위 쿼리 대신 JOIN을 사용 하시겠습니까?

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"}]
같은 JSON 배열으로 한 열에서 모든 반환해야

지금 당장 하위 쿼리를 사용하여 문제를 해결할 수 있습니다.

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; 

예상되는 결과를 얻을 수 있습니다. 그러나 (항상 그렇듯이) 성능은 만족스럽지 않습니다. BTW : 현재 테이블에 인덱스가 없습니다. 지금 궁금합니다.

  • JOIN을 사용하면 쿼리가 빨라 집니까? 그렇다면 정확히 JOIN이 JSON 배열을 반환하도록하려면 어떻게해야합니까?
  • 인덱스를 사용하면 성능이 향상됩니까? '예'인 경우 : 어떤 종류의 색인 열입니까?

업데이트

그냥 참조를 위해 내가 Radim BACA에 의해 제안 된 솔루션은 성능을 확보 측면에서 작동하는 것 같다 점을 지적하고 싶습니다.

먼저 내가 다시 쿼리 내 버전을 시도

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; 

못생긴 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"); 

변경되지 않습니다 하위 쿼리와 버전의 실행 시간은이 효과를 조인을 사용하지만 때하는 극적이다 : 1.1 초

마지막으로 인덱스를 추가!

BTW : 서브 쿼리에서 test_company.contact_person @> ARRAY[id]id = ANY(test_company.contact_person)보다 빠르다고 한 번 들었습니다. 내가 테스트 한 내용은 사실이 아닙니다. 필자의 경우, 후자의 버전은 23 초 만에 모든 행을 반환했다. 처음에는 46 초가 걸렸다.

답변

1

나는 M에 대한 일반적인 관계형 접근 사용합니다 : 당신이 다음 자신의 연락처와 함께 회사가 필요한 경우 단순히 다음 값

INSERT INTO contact VALUES (1, 1); 
INSERT INTO contact VALUES (1, 2); 
-- and so on 

를 추가, 최초의 사람의 경우 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) 
); 

당신 단순히 다음 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 

demo

색인은 기본 키로 자동 생성되므로 성능이 좋을 것입니다. 문제는 모든 회사와 모든 담당자가 정말로 필요하다는 것입니다. 전혀 필터가 없습니까?

사용자 의견에 따라 EDIT 상관 관계가있는 하위 쿼리 대신 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을이

CREATE INDEX idx_testcompany_contactperson on test_company USING GIN ("contact_person"); 
+0

같은 GIN 인덱스를 사용하지 않습니다 허용해야합니다. 연락 담당자를 배치 한 회사 당 하나의 행이 필요합니다. 필터링은 다른 문제이며 여기서는 직접 관련이 없습니다. – cis

+0

@cis가 업데이트되었습니다. 필터가 전체 솔루션의 성능 및 색인과 밀접하게 관련되어 있다고 생각합니다. –

+0

감사합니다! 적어도 나에게 약간의 힌트를 준다. 그러나 수십개의 테이블과 컬럼을 재 설계하는 것은 의문의 여지가 있습니다. 그래서, 내 데이터베이스 디자인과 붙어있어 문제가 그 경우에 관한 쿼리를 최적화하는 방법입니다. 물론 필터는 성능 및 인덱스와 관련이 있지만 지금은 "JSON 배열에 대한 하위 쿼리 대 JOIN"문제에 대한 직접 연결을 볼 수 없습니다. – cis

관련 문제