Python/Django

Django ORM

DevelopC 2022. 9. 23. 15:16
728x90

Django ORM

  • Django ORM 사용 법에 대하여 설명합니다.
  • ORM 이란 Database Table을 프로그래밍에 친숙 한 Class/Object 문법으로 다룰 수 있게 합니다.
  • 프로그램 문법과 DB 사이에 느슨한 결합이 가능합니다.
    • 선언을 통한 테이블 연결: 컬럼 명이 바뀌면 Class 선언 한 부분만 수정하면 됨.
    • 연결된 테이블과 DBMS 문법에 맞는 쿼리 생성: MySQL, SqlLite, PostgreSQL 등 DBMS 가 바뀌어도 코드가 수정될 필요 없다.
    • 실제 컬럼 명과 다른 이름으로, 네이밍 룰을 쉽게 적용하고 바꿀 수 있다.
  • Lazy loading
    • 실제 데이터를 참조해야 하는 시점에 쿼리가 실행됨

기본 문법

모델 선언

테이블 1개를 클래스 1개로 만들고 클래스의 속성으로 사용할 컬럼들을 정의한다.

from django.db import models


class Member(models.Model):
    """
    회원 테이블
    """
    # 자동 증가형
    member_no = models.BigAutoField(primary_key=True)
    member_name = models.CharField(max_length=20)
    company_name = models.CharField(max_length=100)
    
    class Meta:
        # True 인경우 python manage.py migrate 의 명령어를 통해서 실제 데이터베이스 테이블에 반영합니다.
        # 컬럼 추가 및 컬럼 삭제 - production 환경에서는 False로 설정하는걸 추천합니다.
        db_table = 'tb_member' # 테이블 명 
        
        
class Board(models.Model):
    """
    게시판
    """
    board_no = models.BigAutoField(primary_key=True)
    subject = models.CharField(max_length=255)
    # Member 테이블을 참조
    member_no = models.ForeignKey(Member, on_delete=models.CASCADE)
    # 날짜 형 
    insert_timestamp = models.DateTimeField()

    class Meta:
        manages = False 
        db_table = 'tb_board'

Model Class를 이용한 쿼리 문법

# 모델 클래스로 정의하면 django가 objects 라는 Manager를 자동으로 추가 해준다, django.db.models.Manager 
Member.objects

# using()을 선언하지 않으면 databases['default']의 기본 DB를 이용하고, DB를 변경하기 위해서는 해당 alias를 선언하여 바꿀 수있다. 
Member.objects.using('slave')

# tb_member 테이블에 데이터 조회
Member.objects.all()

# tb_member 테이블에 데이터 등록 
Member.objects.create()

# tb_member 테이블에 데이터 수정
Member.objects.update()

# tb_member 테이블에 데이터 삭제
Member.objects.delete()

Select Query

데이터를 전부 조회하여 데이터 순환

members = Member.objects.all()
for m in members:
    print(m.member_name)

쿼리 결과가 1건일 때 조회하여 참조

실제 objects.all()[0]과 같이 동작하므로 데이터가 없을 때와 2건 이상 리턴되었을 때에 대한 예외처리가 반드시 필요하다. 주로 아이디나 유니크 기준으로 조회할 경우 사용한다. 

try:
    member = Member.objects.get(member_no=1)
    print(member.member_name) 
except Member.DoesNotExist:
    print('없는 회원입니다')
except Member.MultipleObjectsReturned:
    print('두 명의 회원 정보가 리턴 되었습니다')

필터 조건 추가하여 쿼리

  • 기본 사용은 filter() 메서드에 COULMN=VALUE로 사용하고 여러 개의 조건은 컴마를 기준으로 연결한다.
  • 컬럼명 뒤에 이어 언더바 두 개를 추가하면 내장된 기능을 사용하거나 조인된 컬럼에 접근 가능하다.
  • or 조건을 걸거나 복잡한 쿼리를 구성하기 위해서는 Q 메서드를 사용한다.
# select * from tb_member where member_name='tony';
Member.objects.filter(member_name='tony')

# select * from tb_member where member_name='tony' and company_name='company';
Member.objects.filter(member_name='tony', company_name='company')

# select * from tb_member where member_name like '%tony%';
Member.objects.filter(member_name__contains='tony')

# select * from tb_member where member_name is not null;
Member.objects.filter(member_name__isnull=False)

# select * from tb_member where member_name in ('tony', 'moon', 'sun');
Member.objects.filter(member_name__in=('tony', 'moon', 'sun'))

from django.db.models import Q
# select * from tb_member where member_name='tony' or member_name='sun';
Member.objects.filter(Q(member_name='tony')|Q(member_name='sun'))

# select * from tb_member where company_name='company' and (member_name='tony' or member_name='sun');
Member.objects.filter(Q(
                        Q(company_name='company')),
                        Q(Q(member_name='tony')|Q(member_name='sun'))
                      )  

# select * from tb_member where company_name='company' and not (member_name='sun') 
Member.objects.filter(company_name='company').exclude(member_name='sun')

# 조건에 따른 쿼리 분기 
members = Member.objects.all()
if not is_admin:
    members = members.filter(member_no=my_member_no)
for m in members:
    print(m.member_name)

자주 사용하는 내장 필터

키워드 설명 예시
__gt 기준보다 큰 값을 검색 # WHERE age > 10
filter(age__gt=10)
__gte 기준보다 같거나 큰 값을 검색 # WHERE age >= 10
filter(age__gte=10)
__lt 기준보다 작은 값을 검색 # WHERE age < 10
filter(age__lt=10)
__lte 기준보다 작거나 같은 값을 검색 # WHERE age <= 10
filter(age__lte=10)
__contains 해당 문자열을 포함하는 값 검색 # WHERE name like '%moon%'
filter(name__contais='moon')
__icontains 해당 문자열을 대소문자 구분없이 포함하는 값 검색 # WHERE name like '%moon%' or name like '%MOON%' 
filter(name__icontais='moon')
__startswith 해당 문자열로 시작하는 값 검색 # WHERE name like 'moon%'
filter(name__startswith='moon')
__istartswith 해당 문자열로 대소문자 구분없이 시작하는 값 검색 # WHERE name like 'moon%' or like 'MOON%' 
filter(name__istartswith='moon')
__endswith 해당 문자열로 끝나는 값 검색 # WHERE name like '%moon'
filter(name__endwith='moon')
__iendswith 해당 문자열로 대소문자 구분없이 끝나는 값 검색 # WHERE name like '%moon' or like '%MOON' 
filter(name__iendwith='moon')
__isnull null 여부에 따라 검색 # WHERE member_name is not null
filter(member_name__isnull=False)

조인 쿼리

Foreign Key를 기준으로 조인할 수 있습니다.

# 가정: tb_board 테이블에 tb_member 테이블의 member_no 컬럼을 저장하고 있다. 
class Member(models.Model):
    member_no = models.BigAutoField(primary_key=True)
    member_name = models.CharField(max_langth=100)
    class Meta:
        db_table = 'tb_member'

class Board(models.Model):
    board_no = models.BigAutoField(primary_key=True)
    member_no = models.ForeignKey(Member, null=True)
    summary = models.CharField(max_langth=100)
    class Meta:
        db_table = 'tb_board'
 
# 1) 기본 문법. 모델클래스.objects.select_related() 또는 모델클래스.objects.prefetch_related()
# 조회 하는 테이블에서 다른 테이블을 참조하고 있을 때 ex) tb_board 테이블에 member_no가 있다 
boards = Board.objects.select_related('member_no').all()
for b in boards:
    # 1 depth에는 내 테이블 내 컬럼 데이터 (기본 사용 법)
    print(boards.subject)
    # 참조하는 컬럼의 명에 attribute로 조인된 결과를 조회할 수 있다, like tb_board.tb_member.member_name
    print(boards.member_no.member_name)

# 2) 조인하여 가져온 테이블을 기준으로 조건이 필요한 경우. filter(조인한컬럼이름__조인대상테이블의컬럼=조건)
# select * from tb_board inner join tb_member where tb_board.subject='테스트 제목' and tb_member.member_name='moon'
Member.objects.select_related('member_no').filter(subject='테스트 제목', member_no__member_name='moon')

# 3) Inner Join / Left Outer Join 
# ORM을 사용하면 query를 자동으로 생성해주기 때문에 의도하는 쿼리를 만들기 위해서는 테이블 정의와 filter를 이용한 조건들을 명확히 사용해야 한다
# 3-1) A가 B를 참조하는 ForeignKey 관계에서, 참조하는 기준 컬럼의 값이 필수인 경우 inner join 이 수행된다
class Board:
    # Board 가 Member 를 참조할 때 null을 허용하지 않는 설정 
    member_no = models.ForeignKey(Member, null=False)
    
# select count(*) from tb_board inner join tb_member; tb_board 와 tb_member 모두 값이 있을 때만 검색 됨 
Board.objects.select_related('member_no').count()

# 3-2) 필수가 아닌 null 허용인 경우 left outer join 으로 동작한다 
# A가 B를 참조하는 ForeignKey 관계에서, 참조하는 기준 컬럼의 값이 필수가 아닌 경우 left outer join 이 수행된다
class Board:
    # Board 가 Member 를 참조할 때 null을 허용하는 설정
    member_no = models.ForeignKey(Member, null=True)
    
# select count(*) from tb_board left outer join tb_member; tb_board 값이 있다면 tb_member 값이 없어도 검색 됨 
Board.objects.select_related('member_no').count()


# 3-3) 참조 관계에서 역으로 조회 할 때 prefetch_related()와 related_name 값을 기준으로 조인한다 
# A가 B를 참조할 때, B를 기준으로 조인 ex) 회원의 게시물. 게시물쪽에서 회원을 알고 있다
class Member:
    member_no = models.BigAutoField(primary_key=True)

class Board:
    # 참조하는 쪽에서 related_name 로 별칭을 지어준다 이 이름은 프로젝트 내 중복되지 않는다 
    member_no = models.ForeignKey(Member, null=True, related_name='members_board')
    summary = models.CharField(max_length=200)
    is_deleted = models.CharField(max_length=1, default='N')

member_board = Member.objects.prefetch_related('members_board')
for m in members_board.all():
    print(m.member_name)

    # prefetch_related 의 경우 배열 형태로 넘어오므로 순환 참조 해야 한다 
    for b in m.members_board.all():
        # prefetch_related 할 지라도 해당 값을 참조하지 않으면 쿼리에 포함되지 않는다 filter()조건 참조도 가능 
        print(b.summary)


# filter 된 결과를 참조하기 위하여는 Prefetch()를 사용한다 
# A가 B를 참조할 때 B를 기준으로 참조 하는데 A의 일부 데이터 만을 참조
# select * from tb_member left outer join tb_board on (tb_member.member_no=tb_board.member_no and tb_board.is_deleted='N')
Member.objects.prefetch_related(Prefetch('members_board', Board.objects.filter(is_deleted='N)))

기타 SELECT 관련 기능

# 정렬
# select * from tb_member order by membr_no asc
Member.objects.all().order_by(member_no)

# select * from tb_member order by membr_no desc
Member.objects.all().order_by(-member_no)

# offset & limit 
# select * from tb_member limit 1
Member.objects.all()[:1]

# select * from tb_member offset 5 limit 5
Member.objects.all()[5:10]

# select for update, 업데이트를 하기 위하여 select 할 경우 select for update 라는 명령을 통해 해당 Row Lock 수행 (다른 select 불가), 중복 업데이트 방지
# select gold from tb_member where mem_seq = 1 FOR UPDATE;
Member.objects.select_for_update().filter(member_no=1)
Member.objects.filter(member_no=1).update(member_name='test')

Insert Query

class Member(models.Mode):
    member_no = models.Integer(primary_key=True)
    member_id = models.CharField(max_langth=255, unique=True)
    member_name = models.CharField(max_langth=100)
    class Meta:
        db_table = 'tb_member'

# 기본 사용법, 모델클래스.objects.create(컬럼=값, 컬럼2=값, 컬럼3=값..) 
new_memeber_obj = Member.objects.create(member_name='tony', nickname='moon',  is_adult='Y')

# 생성 되는 리턴받는 결과는 회원 객체 
print(new_memeber_obj.member_name)

# 기본 사용법 + 간지 한 스푼
member_dict = {
    'member_name': 'tony', 
    'nickname': 'moon', 
    'is_adult': 'Y', 
}
# **를 사용해서 dictionary 자료형을 key=value 만 아규먼트 처럼 전달 ex) func(**member_dict{'test1': 'tony', 'test2': 'moon', }) => func(test1='tony', test2='moon')
new_memeber_obj = Member.objects.create(**member_dict)
print(new_memeber_obj.member_name)

# 대량 등록, 생성 후 리턴 결과는 회원 객체 list  
member_list = []
member_list.append(Member(member_name='sun', nickname='sun'))
member_list.append(Member(member_name='moon', nickname='moon'))

# [<Member: Member object (None)>, <Member: Member object (None)>]
Member.objects.bulk_create(member_list)

# 조회 결과 있으면 객체 리턴, 없으면 생성
# obj 에는 select 후 fetch까지 한 결과 오브젝트 리턴. objects.get()과 같은 동작을 하므로 같은 예외처리 필요  
# created 에는 생성했는지 여부 boolean 리턴 
# defaults 에는 없을 경우 생성할 때의 값 지정 
obj, created = Member.objects.get_or_create( 
    member_id='moon',
    defaults={'member_name': 'moon', age='20'},
)

Update Query

# update tb_member set member_name='sun' where nickname='moon';
result = Member.objects.filter(nickname='moon').update(member_name='sun')
print(result)  # 업데이트 된 수

Delete Query

# delete from tb_member where member_no=1;
result = Member.objects.filter(member_no=1).delete()
# 삭제된 된 데이터 숫자와 CASCASE DELETE 된 연관 모델 삭제 수
print(result)

# ForeginKey 로 묶인 테이블에 on_delete=CASCASE 설정인 경우, 함께 삭제 된다. 
(0.007) DELETE FROM `tb_board` WHERE `tb_board`.`member_no` IN (1); args=(1,)
728x90

'Python > Django' 카테고리의 다른 글

Django Rest Framework Filter  (0) 2022.10.05
Django Rest Framework ViewSet  (0) 2022.09.28
Django Rest Framework Excel Renderer 클래스  (0) 2017.08.02
Django ORM기반 ERD 생성  (0) 2017.07.06