티스토리 뷰

🐍/Django

[Django] SQL 스크립트 돌리기 -> ORM 으로

사용자 eungding 2021. 8. 1. 20:26
728x90
반응형

성경 API, 성경 데이터 이것저것 찾아보다가 SQL 스크립트가 공유되고 있는 것을 발견!

 

-  개역한글 SQL 공유해주신 블로그 (저작권 이슈 없음!!)

-  개역개정 SQL 공유해주신 사이트 (저작권 이슈 확인 안됨)

 

 

확장자는 sql이고 

 

열어보면 TABLE 만들고 레코드 insert 하는 SQL문들이 잔뜩 있다. 

 

 

✔️ 이 스크립트로 나의 장고 DB에 데이터를 추가하고 

✔️ 이 형식에 맞춰서 장고 모델을 만들고 ORM 방식으로 get 요청을 처리하는 것을 해볼 것이다. 

 

 

[1] SQL 스크립트를 돌려서 DB에 데이터 추가하기

 

1️⃣  executescript (파이썬 공식 문서)   /   How to execute an external SQL file using sqlite3 in Python (블로그) 

2️⃣  Performing raw SQL queries  (장고 공식 문서) 

 

 

이렇게 두개를 알아야한다. 

장고 ORM을 쓸 때, 뒷단에서는 SQL문으로 처리되고 있는 것을 알았지만 

직접 SQL문을 쓸 수 있게도 해주다니 좋다 ^__^ 

 

bible_api 라는 장고프로젝트 안에 아까 다운받은 sql 파일을 넣어주고 

run_sql.py라는 파일을 만들어주고 아래와 같이 코드를 작성해줬다. 

 

from django.db import connection
import os

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'bible_api.settings')

def my_custom_sql(sql_file_name):
    with connection.cursor() as cursor:
        sql_file = open(sql_file_name)
        sql_as_string = sql_file.read()
        cursor.executescript(sql_as_string)

my_custom_sql("bible_korHRV.sql")

 

그리고 이 파일을 돌리면 

python run_sql.py

 

장고 프로젝트 안의 DB에 데이터가 추가된다. 

장고 프로젝트안에 있는 db.sqlite3 파일을 'DB Browser for SQLite' 라는 프로그램에 끌어다두면

이런 테이블이 추가되었고 레코드들도 추가된 것을 볼 수 있다. 

 

 

 

[2] ORM 방식으로 전환하기 

 

이렇게 추가된 데이터를 ORM 방식으로 쓸 수 있게 해보자 (SQL문은 더 이상 쓰고 싶지않다)

 

 1️⃣  Integrating Django with a legacy database (장고 공식문서)

 2️⃣  Model Meta options  (장고 공식문서)

 

이 두 문서를 봐줘야한다. 

 

장고는 기존 데이터베이스를 보고 모델을 자동생성해주는 inspectdb라는 유틸리티를 제공해준다. 

이 명령어를 돌리면 콘솔에서 자동생성되는 모델이 출력되고 

python manage.py inspectdb

 

이 명령어를 돌리면 models.py 라는 파일로도 만들어준다 (파일안에는 자동생성된 모델이 담겨있음) 

python manage.py inspectdb > models.py

 

참고로 두 명령어 모두,

장고에 기본적으로 있는 테이블들까지도 모델로 auto-genereate 해준다. 

(참고로 모델은 테이블에, 필드는 칼럼에 매핑됩니다.) 

 

 

아무튼 첫번째 명령어를 돌려서

콘솔에 이렇게 출력된 것을 복사하여 

 

 

models.py에 붙여넣기 하고 조금 바꿔줬다. 

 

from django.db import models

class BibleKorhrv(models.Model):
    book = models.IntegerField()
    chapter = models.IntegerField()
    verse = models.IntegerField()
    content = models.TextField()

    fake_id = models.IntegerField(primary_key=True)

    class Meta:
        managed = True
        db_table = 'bible_korHRV'
        constraints = [
            models.UniqueConstraint(fields=['book', 'chapter', 'verse'], name='unique')
        ]

 

# 1.  타입수정 

 

우선 원래 sql문과 타입이 다르게 추정된 것을 맞춰주었다. 

이렇게 3개 NUMERIC인데 TEXT로 추정되었기 때문이다. 

 

 

# 2. UniqueConstraint

 

UniqueConstraint 을 추가해준 것은

원래 sql문에서 book, chapter, verse 의 조합을 PRIMARY KEY로 쓰고 있어서

일단 모델에도 반영해줬다.

 

 

하지만 UniqueConstraint가 primary key를 대체해주지는 않는다. (참고:  unique_together does not replace primary key )

대신 비슷한 효과로 UniqueConstraint 로 지정된 필드의 조합과 똑같은 조합이 INSERT 되려고 할 때 Unique 에러를 내준다.
중복되는 primary key가 INSERT 되려고 할 때와 똑같은 에러이다.  (참고: https://araikuma.tistory.com/692)

 

 

많이 찾아봤지만 장고에는 하나의 필드가 아니라 여러 필드를 조합해서 primary key로 쓸 수 있는 방법은 없는 것 같다.

 

 

# 3.  fake_id 필드 추가 

 

장고의 모델에는 기본적으로 id 필드가 생긴다. (auto-incrementing primary key)

마이그레이션 파일을 보면  id 필드가 생겨있고

UniqueConstraint가 있음에도 id필드가 primary_key로 되어있다. 

(위에서 말한 UniqueConstraint가 primary key 대체가 아닌 것 같다는 근거 중 하나)

 

모델

 

 

마이그레이션

 

만약 모델을 정의할 때 custom primary key를 정의해주면 (그 방법은 해당 필드에 primary_key=True 를 명시하면 됨) 

id필드는 자동으로 안생긴다. 

 

모델

 

마이그레이션

 

 

id 값을 고유값으로 안써줄 것이기 때문에 id 필드가 자동으로 생기는 것을 원치 않는다. 

그래서 fake_id 라는 필드를 추가했다. 

 

DB에는 fake_id 라는 필드가 없으니 추가해줘야한다. 

run_sql.py 로 이동하여 아래 코드를 추가해준다. 

 

def make_fake_id_column(table_name):
    with connection.cursor() as cursor:
        column_name = 'fake_id'
        
        sqlite_script = f'''
        ALTER TABLE {table_name} ADD COLUMN {column_name} INT;
        UPDATE {table_name} SET {column_name}=0;
        '''

        cursor.executescript(sqlite_script)


make_fake_id_column("bible_korHRV")

 

돌려주어서 DB에 필드 & 값이 추가되게 해준다. 

python run_sql.py

 

 

 

 

참고로 아래 스크립트로 fake_id가 아니라

auto increment로 동작하는 id 필드를 추가하고 

레코드 순서대로 id 필드에 값을 다르게 넣어주고 싶었으나..

 

mysql에서는 SET을 단독으로 사용가능한데

sqlite 에서는 안된다고 하여 하지 못했다..  🥲

 

my_sql_script = f'''
ALTER TABLE {table_name} DROP {column_name};
ALTER TABLE {table_name} ADD COLUMN {column_name} INT AUTO_INCREMENT;
SET @cnt = 0;
UPDATE {table_name} SET {column_name}=@cnt:=@cnt+1;
'''

 

 

# 4.  마이그레이션 

 

마이그레이션 해준다. 

 

 python manage.py makemigrations
 python manage.py migrate

 

 

 

[3] 잘되는 지 확인 

 

앱에 아주 간단하게 코드를 만들고 (예외처리 모두 생략) 

 

from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index')
]

 

from . import models
from django.http import JsonResponse

def index(request):
    book = request.GET['book']
    chapter = request.GET['chapter']
    verse = request.GET['verse']
    content = models.BibleKorhrv.objects.get(book=book, chapter=chapter, verse=verse).content

    return JsonResponse({'content': content})

 

서버를 돌린 후 

python manage.py runserver

 

 

테스트를 해보고 잘 동작하는 지 확인한다. 

 

 

 

[ 결론 ] 

 

장고의 특성 상 

SQL 스크립트 중에서도 하나의 필드만 Primary Key로 쓰는 SQL 문을 선택하면 더 편하다. 

 

바로 이런 것...!!

장고 모델에서 idx 필드를 primary_key=True 로 해주면 완전 간단, 깔끔.. 

 

https://sir.kr/g5_tip/4160

 

하지만 이 파일은 저작권 때문에 쓰기가 좀 그렇다,,, 

이번에는 저작권 이슈 없는 저 파일을 쓸 수 밖게 없었긴 한데  

다음에도 이런 작업을 해야하면 sql 스크립트를 잘 고를테다 🥸

 

 

[ 추가 ]

 

fake_id 이긴 하지만 primary key가  모두 0인 게 찝찝했던 나는

결국 shell을 돌리고 각 object마다 fake_id를 다른 값으로 넣어줬다. 

 

python manage.py shell
>>> from bible.models import BibleKorhrv
>>> for index, objet in enumerate(BibleKorhrv.objects.all()):
       new = BibleKorhrv(book=object.book, chapter=object.chapter, verse=object.verse, content=object.content, fake_id=index+1) 
       object.delete()
       new.save()

 

 

원래 delete & create가 아니라 update를 해주려고 했지만,  update를 하면 unique 에러가 나서 update로 해주지 못했다. 

흠... update가 아니라 새로 insert하는 것처럼 여겨서 에러가 나는 것 같은데, 정확한 원인은 파악하지 못했다.  

(처음부터 모델을 만들고 데이터를 쌓은 게 아니라 쌓인 데이터로부터 모델을 만들어서 뭔가 다른 점이 있는 듯 하다..)

 

>>> from bible.models import BibleKorhrv
>>> for index, objet in enumerate(BibleKorhrv.objects.all()):
       object.fake_id = index + 1 
       object.save()

 

 

 

 

 

 

 

 

 

 

728x90
반응형
댓글
댓글쓰기 폼