[Django ORM] 1:N 관계의 테이블들을 그룹핑해서 한 row에 N관계에 있는 컬럼들을 모두 보고싶을때(with. PostgreSQL)

리미·2020년 8월 13일
0

Django ORM

목록 보기
1/1
post-thumbnail

내가 원하는 것

일단 내가 처음에 원하고자 했던 것을 보도록하자.
예는 지금 내가 참여하고 있는 프로젝트로 예를 들도록하겠다

검색결과 페이지를 급하게 프리랜서분(섭이)께서 제작해주셨는데 regroup으로 되어있길래,
데이터가 많아지면 느려지기도 하고 뷰와 템플릿 소스가 길어진 느낌을 받아서 재개발을 하기로 마음먹었다(싹다갈아엎는 그거)

난 모든걸 ORM 한줄에 끝낼꺼야!

항상 목표는 크게 가지는게 좋다

일단 나의 목표 우선순위는
1. 최대한 템플릿을 건드리지 않아야하며(앞단에서 처리하는 것을 최소화)
2. ORM 한줄로 원하는 모든걸 끝내야하며
3. 뷰의 소스를 최대한 줄인다

원하는 결과를 그리고 올바른 함수 찾아보기

지금 모델의 구조는 이런식으로 되어있다
상위대학교 ----- 하위대학교(본교, 분교, 제2캠퍼스 등)
상위대학교에 한양대학교이면
하위대학교에 본교, 에리카캠퍼스의 데이터가 2개 있는 것이다.(상위대학과 FK로 연결)

결국 한 행에서 보이겠다고 한것은 두개의 테이블을 조회해서 select 했을때 내가 원하는 데이터는 이런식이다

상위대학이름 | 하위대학1이름 | 하위대학1주소   | 하위대학2이름 | 하위대학2주소 ...
--------------------------------------------------------------------
한양대학교   |    본교    | 서울특별시 사근동 |    분교    | 경기도 안산시 상록구
강원대학교   |  춘천캠퍼스  |  강원도 춘천시  |  삼척캠퍼스  | 강원도 삼척시

이런식으로 보여주고싶다
UNION을 쓸까?
CONCAT을 쓸가?
그치만 CONCAT으로 string으로 만들면 또 거기서 리스트로 변경해서 뽑아야되거나 해야하기 때문에 잡일이 많아진다. 잡일을 최소화하기위해
그리고 하위 대학이 2개뿐만이 아니라 3개일수도 있고 4개일수도 있다.
이걸 고민하고있다가 PostgreSQL의 집계함수들은 뭐가 있나 조사해보았다
참고 : https://www.postgresql.org/docs/9.5/functions-aggregate.html

일단 눈에 들어오는 함수부터 표시해서 검색을 해보기 시작했다.

array_agg는 해당 컬럼들의 데이터들을 array로 변경해서 보여주는 것이고,
json_agg는 해당 컬럼들의 데이터들을 json으로 변경해서 보여주는 것인데

Django 공식문서를 통해 ORM 에서 비슷한게 있나 조사해본다
https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/aggregates/
다행히도 1개는 있는데,
json_agg와 비슷해보이는 JSONBAgg가 있긴한데...잘모르겠다
일단 내가 원하는 결과가 나오는지 SQL로 작성해 본다.

SQL version

어차피 FK로 연결되어있으니 일단 간단하게 짜본다

SELECT parent_id, ARRAY_AGG(name), ARRAY_AGG(tel), ARRAY_AGG(address)
FROM common_university
GROUP BY parent_id


내가 원하는 결과가 나왔다.
json_agg도 같을까?

SELECT parent_id, JSON_AGG(name), JSON_AGG(tel), JSON_AGG(address)
FROM common_university
GROUP BY parent_id


비슷한거 같다. 말 그대로 json 형식으로 뿌려주냐의 차인듯

실행계획은 이러하다


생각보다 비용이 많이드는건가? 싶기도 한데 regroup으로 다 처리하는 것보단 나아보인다.

Django로 넘어가서 다시 짜보도록하자.

Django version

공식문서에서는 JSONBAgg에 대해서 자세하게 나오지않아서,
일단 나중에 내부를 뜯어보기로하고
현재는 ArrayAgg로 사용해보았다.

University.objects.filter(
                parent__name__contains=search_text
            ).values('parent').annotate(
                univ_id=ArrayAgg('id', ordering=F('name')),
                univ_name=ArrayAgg('name', ordering=F('name')),
                univ_address=ArrayAgg('address', ordering=F('name')),
                univ_tel=ArrayAgg('tel', ordering=F('name')),
                univ_location_url=ArrayAgg('location_url', ordering=F('name')),
                is_joined=ArrayAgg('is_joined', ordering=F('name'))
            ).values(
                'parent__name',
                'parent__set_type',
                'parent__logo',
                'parent__page_url',
                'parent__plan_url',
                'parent__admissions_url',
                'univ_name',
                'univ_address',
                'univ_tel',
                'univ_location_url',
                'univ_id',
                'is_joined'
            ).order_by('parent__name', 'univ_name')

N관계에 있는 것들은 모두 ArrayAgg함수로 모아주어 처리하였고,
중간에 정렬이 되어있지않길래 ordering을 이용해서 이름순으로 정렬을 해주었다.

템플릿에 적용해주자.

다행히도 서로 리스트 인덱스를 맞춰주기 때문에,
캠퍼스 이름이 있어도 주소가 없는 경우 None 처리가 되어서 본인의 인덱스 자리를 채워준다
(의문 : 그럼 하나하나 ordering을 처리 하지않아도 되지않을까? 저게 각각의 오더링인지 아님 모든 그룹핑된 컬럼에 적용되는건지 확실하지않다)

이걸 이용해서 템플릿에 적용해본다.
쿼리셋을 받아와 for 문을 이용해서

상위의 것은 가장 처음부터 for문을 돌려줄때 기입하고

하위의 것은 그 안에 for문을 넣어서 돌려준다(2중 for문)
하위의 것은 forloop.counter0을 이용해서 해당 인덱스의 데이터를 가져와서 출력하게 해주면 끗

그전에 template tag를 이용해서 리스트의 해당 인덱스의 데이터를 리턴하는 만들도록해야한다!
(사진의 index 함수)

목표의 결과

이뤄낸 목표의 결과

나의 목표결과
1. [ V ] 최대한 템플릿을 건드리지 않아야하며(앞단에서 처리하는 것을 최소화)
----> 템플릿 줄수는 388줄에서 217줄로 줄였다
2. [ V ] ORM 한줄로 원하는 모든걸 끝내야하며
3. [ V ] 뷰의 소스를 최대한 줄인다

목표를 모두 이루었다 ^*^

profile
Python이 하고싶은데 자꾸 Flutter 시켜서 빡쳐서 만든 블로그

0개의 댓글