[Django]ORM 사용하는 것 대신 PostgreSQL DB에 직접 접근하기

David Im·2022년 4월 9일
0

본 글은 야간모드에 최적화 되어있습니다. 우측 상단에서 해 혹은 달모양을 클릭시어 velog 설정을 야간모드로 해주시면 더욱 편안하게 읽으실 수 있습니다.

DB의 접속국가 정보 관련 API를 만들어보고 있던 와중에 데이터에 대한 count를 세는 것이 필요하여

아래처럼 filter에 조건을 주고, annotate를 취하여 count를 세는 방식을 취하려고 했다.

country = IPdata.objects.filter(log_date__gte = start_datetime,
log_date__lte = end_datetime).exclude(ip_address='')\
.values('country').annotate(count=Count('ip_address',distinct=True)).order_by('-count')

살짝의 코드 설명을 하자면,

  1. filter와 exclude의 조건

    log_date가 GET으로 불러온 지정 시작시간보다 크고, 지정 마감시간보다 작은 값들이면서 IP주소가 기록되지 않은 건 제외

  1. 결과에 대한 count 재정리

    values는 국가별로, IP주소의 갯수를 세며, 중복은 제거하고, count 수가 많은것부터 내림차순으로 정렬한다.

하지만 이렇게 했더니 제대로 된 결과가 나오지 않았다.

국가별로 count가 된 조건중에서 다시한번 IP에 대해 count를 했어야하는데, 조건에는 그렇지 못했던 것이다.

이중으로 annotate를 하고, count를 Python에서 적용할 수있는 방법을 아직 모르기때문에

다른방법을 찾아보다가 아래와 같은 방법이 있음을 알게 되었다.

( 찾아보려고 검색을 해보긴했는데, 잘 나오지 않았다... ㅠ )

Python으로 DB에 직접 Query하여 결과를 조회하는 방법

SQL문으로 생각을 하는건 쉬웠는데, 그것을 python 방식으로 옮길 방법을 못찾다 보니, DB에 직접 쿼리를 날려 조회를 해오는 방식도 있음을 알게되었다.

작성하는 내용은 Django Python 코드 기준이고, 사용중인 DB의 경우 PostgreSQL DB이다.

(현재 글에서는 settings.py에 DB 설정이 모두 되어있다는 가정하에 작성한다.)

from django.db import connection

with connection.cursor() as cursor:
    cursor.execute(
       # 'SQL Query Example Code'
       	 'select country, count(*) \
          from (select country, ip_address from IPdata\
          where log_date between %s and %s and ip_address <> %s group by country, ip_address)\
          a group by country order by count desc', [start_datetime, end_datetime, ''])
       data = dictfetchall(cursor)

Django에서 직접 지원해주는 connection 모듈을 import하여 사용하는 방식으로

DB에 직접 접근하여 SQL Query 자체로 날려 결과를 조회해올수 있게 해준다.


사용방법을 차근차근 알아보니

connection module이 import되고 나서, SQL Query가 필요한곳에서(ex : if문) 아래와 같이 작성해주면된다.

with connection.cursor() as cursor:
   cursor.execute( 'SQL Query' , [string or variable value] )
   data = dictfetchall(cursor)

1. cursor를 얻어온 이후 connection을 설정하고 그 값이 존재한다면

2. 작성한 SQL Query를 날려준다.

3. 요청한 SQL Query의 값을 dict형태로 받는다.

* SQL Query를 직접 작성 할 시 주의 할 점이 몇가지 있다.
1) SQL Query의 경우에는 작은따옴표(' ') 묶어 주어야한다.

2) SQL Query 안에 쓰이는 변수의 경우에는 String형식이나 int의 경우 %s, %d와 같이 지정해주어야한다.

3) %s, %d 등으로 지정해준 값의 경우에는 Query작성 이후 []로 묶어서 앞에쓰인 %s 혹은 %d 순서대로 해당하는 

     variable을 지정해주거나, 값을 지정해주어야한다.

마지막의 주의사항대로 하여 아래 조건을 적용한 예제 코드로 보면 아래와 같은 모양새일 것이다.

학생테이블(StudentTable)에서 학생(student)들이 접속하여 남긴 게시글(board_text) 횟수에 대해 조회시작일(first_date) ~ 조회마지막일(last_date) 까지 가장많은 학과의 학생부터 게시글 횟수로 COUNT하여 조회한다

cursor.execute( 
          # student의 수를 count한다
          'SELECT student, count(*)

          # '아래의 select ~ from ~ where 문의 안에서 처리할 것이고,'
          # '해당 하위 SQL문은, StudentTable에서 student와 board_text Relation에 대해서만'
          FROM (select student, board_text from StudentTable

          # 'date Relation의 값이 first_date와 last_date 사이인 값이면서,'
          # 'board_text Relation의 값이 ''이 아니고,'
          # 'student와 board_text 별로 GROUP BY(중복제거까지)를 처리한다.'
          WHERE date BETWEEN %s AND %s AND board_text <> %s GROUP BY student, board_text)\

          # '위의 조건대로 출력된 하위 SQL문의 결과에 대해 다시한번 student별로 GROUP BY처리하며,'
          # '내림차순으로 정렬하여 출력한다.'
          A GROUP BY student ORDER BY count DESC', [first_date, last_date, ''])

3번 항목의 dictfetchall의 경우 해당하는 내용에 대해 SQL을 조회하고,

데이터를 받아와 dict형식으로 정리해주는 부분이라고 볼수 있었다.

StackOverFlow를 참고해보니, dictfetchall 말고도 여러 종류가 있었지만, 내가 사용할 dictfetchall()의 경우에는 아래와 같았다.

dictfetchall() = 키, value를 포함하는 사전 목록 형식으로 일치하는 모든 레코드를 제공합니다.

출처 : https://stackoverflow.com/questions/15679782/please-explain-these-python-fetch-types
def dictfetchall(cursor):
    # "Returns all rows from a cursor as a dict"
    desc = cursor.description
    return [dict(zip([col[0] for col in desc], row)) for row in cursor.fetchall()]

이렇게 DB에 직접 쿼리를 날려 데이터를 받아오면 내가 원하는 조건을 SQL Query로 직접날려서 검색한것이다보니,

이쁘게 원하는 값들을 JSON형태로 잘 보여준다.

아마 더 좋은 코드가 있을것 같지만, 아직 나의 경험으로는 더 좋은 코드를 찾는 것은 무리일듯싶지만,

더 좋은 코드가 있을거라고 확신한다. 뭔가 Django 내에서 처리하는것과 DB에 직접액세스해서 가져오는것은 느낌도 좀 다르고,

직접 액세스라고하니 위험해보이기도 해서 시간이되면 리팩토링을 꼭 해야한다고 생각하는 부분이다.

일단 급한불은 껐다지만, 개발자한테 그런게 어디있담.
더 좋은 코드를 만들 수 있도록 방법을 찾는게 개발자아니겠는가..

이와 관련하여 PostgreSQL은 아니고 MySQL이지만 사용법은 굉장히 비슷하기에,

(사실 거의 동일하다고 봐도 무방할 것 같다 ㅋㅋ...)

간단하게 튜토리얼을 정리해둔 블로그도 있으니, 참고하면 좋을 것 같다.

profile
코더보다 개발자로, 결과와 과정의 시너지를 만들어 가고 싶은 주니어 개발자

0개의 댓글