Django - Coalesce를 사용하여 aggregate가 None을 반환하는 것을 방지하기

|


django의 aggregate를 활용하면 원하는 필드의 합계, 평균, 최대, 최소값을 구하는 등의 작업을 쉽게 할 수 있다. 참고 : django aggregation 예를 들면 아래의 코드를 통해서 WishBook 모델에서 2017년 8월에 생성된 레코드를 필터링하고 price 필드의 합을 구할 수 있다.

>> WishBook.objects.filter(created_at__year='2017', created_at__month='08').aggregate(total=Sum('price'))
# {'total': 85210}

문제는 필터링을 할 때 조건에 맞는 레코드가 없는 경우 Sum, Max 등의 결과는 None을 리턴하게 된다. (Count는 0을 리턴한다.)

>> WishBook.objects.filter(created_at__year='2017', created_at__month='09').aggregate(total=Count('price'))
# {'total': 0}

>> WishBook.objects.filter(created_at__year='2017', created_at__month='09').aggregate(total=Sum('price'))
# {'total': None}

None을 리터하게되면 필터링 결과값이 없을 때 아래와 같이 표시되는 문제가 발생한다.

before

이를 해결하려면 크게 2가지 방법을 사용할 수 있다.

  • 1) A or B를 활용하여 A가 null 인 경우 B를 리턴하기
  • 별도의 임포트가 필요하지 않다.
total_price = WishBook.objects.filter(created_at__year='2017', created_at__month='09').aggregate(total=Sum('price'))['total'] or 0
  • 2) Coalesce(A, B, C..)를 활용하여 왼쪽부터 인자값을 검사하고 null이 아닌 첫번째 값을 리턴하기
  • Coalesce는 Django 1.8 부터 지원되고, django.db.models.functions에서 임포트하여 사용할 수 있다.
  • 위의 1) 방법은 별도의 임포트가 필요 없기 때문에 간편하고, 2) 방법은 null일 가능성이 있는 후보들이 여러개일때 사용하면 편리하겠다는 생각이 든다.
from django.db.models.functions import Coalesce

total_price = cls.objects.filter(created_at__year=year, created_at__month=month).aggregate(total=Coalesce(Sum('price'), 0))['total']

Coalesce는 SQL COALESCE 함수와 동일한 기능을 한다고 한다. Coalesce(a, b, c, d, 0)와 같이 2개 이상의 인자를 받고, 왼쪽부터 차례대로 검사하여 null이 아닌 첫번째 값을 리턴한다. 이 경우 a, b, c, d 가 모두 None인 경우 마지막 값인 0을 리턴한다.

Django 공식문서를 보면 아래와 같이 활용이 가능하다.

>>> # Get a screen name from least to most public
>>> from django.db.models import Sum, Value as V
>>> from django.db.models.functions import Coalesce
>>> Author.objects.create(name='Margaret Smith', goes_by='Maggie')
>>> author = Author.objects.annotate(
...    screen_name=Coalesce('alias', 'goes_by', 'name')).get()
>>> print(author.screen_name)
Maggie

>>> # Prevent an aggregate Sum() from returning None
>>> aggregated = Author.objects.aggregate(
...    combined_age=Coalesce(Sum('age'), V(0)),
...    combined_age_default=Sum('age'))
>>> print(aggregated['combined_age'])
0
>>> print(aggregated['combined_age_default'])
None

파이썬 - try, except, finally 안에서 return 사용시 동작순서

|

개인적인 연습 내용을 정리한 글입니다.
더 좋은 방법이 있거나, 잘못된 부분이 있으면 편하게 의견 주세요. :)

의문

아래와 같이 try ~ except 구문에서 return을 사용하면 finally 문은 어떻게 될까? return문이 사용되면 함수가 그자리에서 바로 종료되지 않을까?

def divide(x, y):
    try:
        result = x / y
    except ZeroDivisionError:
        print("0으로 나눌 수 없어요!")
        return False
    else:
        print("결과:", result)
        return True
    finally:
        print("나누기 연산을 종료합니다.")

확인

확인해보니 finally문은 try ~ except 구문이 종료되기 전에 무조건 실행된다고 한다. return 이외에 continue, break 에서도 동일하게 동작한다.

A finally clause is always executed before leaving the try statement, whether an exception has occurred or not. When an exception has occurred in the try clause and has not been handled by an except clause (or it has occurred in a except or else clause), it is re-raised after the finally clause has been executed. The finally clause is also executed “on the way out” when any other clause of the try statement is left via a break, continue or return statement.

위의 코드를 실행해보니 아래와 같다.

>>> divide(6, 0)
0으로 나눌  없어요!
나누기 연산을 종료합니다.
False

>>> divide(6, 3)
결과: 2.0
나누기 연산을 종료합니다.
True

>>> divide("칠", "삼")
나누기 연산을 종료합니다.
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-12-f3d6ad32f1c7> in <module>()
----> 1 divide("칠", "삼")

<ipython-input-7-1579274aac92> in divide(x, y)
      1 def divide(x, y):
      2     try:
----> 3         result = x / y
      4     except ZeroDivisionError:
      5         print("0으로 나눌 수 없어요!")

TypeError: unsupported operand type(s) for /: 'str' and 'str'

참고문서

6개월간의 TIL 회고 (꾸준히 하면 좋은 일이 생긴다)

|



git


어쩌다 시작하게 되었나

2016년 퇴사 후 비전공자로 개발공부를 하면서 여러번 좌절하고 중간에 도망(?)을 가기도 했었다. github 커밋로그를 보면 학습 과정의 부침을 그대로 볼 수 있는데 아주 간단하게 정리하면 아래와 같다.

  • 과거 : 일문과 졸업 후 스타트업에서 여러가지 일을 맡아서 함 (마케팅, 정산, 고객 CS, 서비스 운영 등등)
  • 2016년 9월 : 프론트엔드 공부 시작 (개발자로 살려면 평생 공부해야겠구나)
  • 2016년 11월 : 도망! (다른 일 + 여행 + 앉아서 일하는 게 얼마나 편했던 것인지 알게 됨)
  • 2017년 2월 : 백엔드 공부 시작, 블로그 시작 (더 이상 도망갈 곳은 없다)
  • 2017년 8월 현재 : 1년 만에 다시 회사에 다니게 되었다.

대부분 혼자서 공부하는 시간이 많다 보니 동기부여 가 될만한 것이 무엇이 있을까 찾아보다가 블로그를 하기로 마음먹었다. 하지만 시작이 쉽지는 않았다. Jekyll을 사용해서 github 블로그를 만드는 데 며칠이 걸렸다. 글을 쓰는 건 더 힘들었다. 흔한 네이버 블로그도 해본 적이 없고 SNS에 글도 잘 올리지 않는 성격이라 고작 몇 줄 쓰는데 너무 많은 시간이 걸렸다. 결국, 초등학생 일기에 가까운 문장으로 매일매일 무엇을 했는지 적어나갔다.


Today I Learned (=초딩일기)

views
2월 : 공부는 안하고 결제만 했던 날



views
3월 : 처음으로 메모장을 만든 날



views
4월 : 자료구조 알고리즘은 어려워



views
5월 : 알고리즘 문제를 푼 날



views
6월 : 내년에도 장고를 쓰고 싶다 (그리고 현실이 되었습니다)



views
7월 : 연습 프로젝트를 배포한 날



views
8월 : 파이콘에 참여한 날

6개월간의 TIL 작성을 통해서 얻은 것, 느낀 것

1. 나에게 맞는 학습 방법이 무엇인지 조금은 알았다.

개발은 공부할게 너무너무 많아서 자연스럽게 효율적인 학습 방법에 대해서 고민하게 된다. 공부했던 내용을 매일 정리하다보니 언제 성취감을 느끼고 학습 효율이 좋은지 발견할 수 있었다.

2. 매일 공부하는 습관을 얻었다.

중간에 github에 커밋하는 것 자체가 목표가 되어버리는 시기도 있었다. (일명 잔디심기) 하지만 시간이 지나면서 의미있는 커밋, 커밋메시지를 중시하게 되었다. 무엇보다 매일 조금씩이라도 공부하는 습관을 얻을 수 있었다.

3. 면접 기회를 얻었다.

블로그를 통해서 면접 기회를 얻을 수 있었다. 아직 한참 부족하다는 생각에 어딘가에 지원할 생각도 못하고 있었는데 놀라운 일이었다. 개발자로서 기술면접, 알고리즘 문제풀이 등 다양한 면접 절차를 경험하면서 무엇을 더 준비해야 하는지 배울 수 있었다. 그 과정중에 새로운 사람들도 많이 만났다.


2017년 2월 16일에 첫 번째 TIL을 작성했으니 오늘은 마침 딱 6개월이 되는 날이다. 그리고 개발자로서 가장 일하고 싶은 회사 중 하나였던 8퍼센트에 처음 출근하는 날이다. 동기부여를 얻으려고 시작했던 TIL 덕분에 신입 개발자로서 일할 수 있게 되었다. 무엇보다도 재미있었다. 매일매일 뭘 했는지 떠올리고 기록하면서 조금씩 나아지고 있다는 성취감을 얻었다. 오늘부터는 회사에 적응하고, 일을 배우는 데 집중하기 위해서 매일 기록하는 것은 잠시 멈추려고 한다. 언젠가 다시 동기부여가 필요해질 때, 초등학생 일기 같은 이 글들이 조금이라도 도움이 되지 않을까 생각한다.

170814-0815_TIL

|

8월 15일 (화)

  • 파이콘 튜토리얼에 참여하여 구글 api, 공공 api를 사용하는 연습을 했다.
  • AskDjango 강의를 들었다. Generic CBV View - Editing / Delete 앞으로 간단한 생성, 수정, 삭제 뷰는 cbv를 적극 활용해야겠다.
  • Two Scoops of Django 10장 클래스 기반 뷰의 모범적인 이용을 읽었다.

8월 14일 (월)

170807-0813_TIL (파이콘, 샐러리, 소스트리)

|

8월 13일 (일)

  • 파이콘 둘째날 행사에 참여했다. 아주 흥미로운 주제의 이야기를 들을 수 있었다.

8월 12일 (토)

  • 파이콘에 처음으로 참여했다. 프로그램에서 다루는 내용 중 아직 모르는 부분이 많았지만 사람들이 무엇에 관심이 있는지, 무엇을 중요하게 생각하는지 엿볼 수 있었다. 경험이 많을수록 더 재미있게 즐길 수 있겠다는 생각이 들었다. 내년을 기대해보자!
  • 다양한 회사들의 개발 프로세스에 대해서도 들을 수 있었다. 다들 코드리뷰와 테스트코드 작성이 중요하다고 생각하지만 실무에서는 원하는 만큼 실천하기는 어렵다는 이야기가 많았다.
  • Celery와 Redis를 활용하여 이메일 발송 작업이 비동기로 처리되도록 구현했다.
    • 일단 원하는 대로 구현은 했지만 Celery 문서를 읽고 이해하는데 어려움을 겪었다.

8월 11일 (금)

  • 도서관리 프로젝트에 쪽지 수신시 이메일 알람 기능을 추가했다. 생각보다 이메일 발송에 걸리는 시간이 길다. (3-5초) 어떻게 개선할 수 있을까? 이럴때 쓰는게 말로만 듣던 celery일까? Celery를 이용한 긴 작업 처리
  • 대여중인 도서가 연체일에 도달했을 때 자동으로 메일을 보내도록 하는 기능을 추가하고 싶다. 어떻게 해야할까? 쪽지를 보낼 때는 쪽지를 저장하는 시점을 이벤트 핸들러로 사용했었다. 연체 알림은 매일 0시에 서버에서 연체자에게 일괄적으로 메일을 보내도록 할 수 있을까? 찾아봐야지

8월 9일-11일

  • 시골집으로 여름휴가를 다녀왔다. 우비 입고 비를 잔뜩 맞으면서 영화를 보고, 반딧불이도 봤다. 책을 가져갔지만 몇장 읽지는 않았다.

8월 8일 (화)


8월 7일 (월)

  • git에 대해서 공부했다.
    • 생활코딩 sourcetree 강의를 들었다. 이전에 생활코딩에서 지옥에서 온 Git 강의를 듣고 CLI 환경에서 GIT을 사용하는 방법에 대해서 공부했었다. sourcetree를 사용해서 GUI 환경에서 git을 다루는 방법을 배우니 reset, revert 등에 대한 개념을 좀 더 구체적으로 이해할 수 있었다. 필요한 부분은 pro git을 통해서 참고하니 도움이 많이 된다. (예전에 잠깐 읽었을 때는 참 재미 없었는데..)
    • Git - 브랜치 5 : 충돌의 최소화를 들으니 해커톤에서 내 코드가 왜 자꾸 충돌을 발생시켰는지 이해가 조금은 간다. 자주 master의 변경사항을 branch에 반영해서 충돌을 미리 해결하고 최소화 할 수 있어야겠다.
    • 협업하기 강의를 통해서 git 협업의 기본 순서 꿀팁을 얻을 수 있었다.
      • Pull : 작업을 시작하기 전 혹시 모를 원격저장소의 변경사항을 반영하기 위해서 pull
      • work > commit
      • pull : 작업중에 혹시 발생했을지 모를 원격저장소의 변경사항을 반영하기 위해서 push 직전에 pull
      • push : 바로 push를 진행
    • 해커톤을 하면서 branch, merge, conflict, reset, revert 작업의 필요성을 크게 느꼈다. 일부러라도 개인프로젝트를 진행할때 branch를 이용해봐야지.