ORM 1

서근재·2024년 1월 15일

Django

목록 보기
1/4

ORM


  • ORM(Object Relational Mapping)은 어플리케이션과 데이터베이스 연결 시 SQL언어가 아닌 어플리케이션 개발언어로 데이터베이스를 접근할 수 있게 해주는 툴임
  • 개발자가 SQL을 적지 않고도 프로그래밍 언어로 DML을 수행할 수 있음
  • 객체지향적인 코드를 통해 데이터를 다루기 때문에 코드 가독성이 높고, 이는 생산성을 높여줌

하지만 장점만 있는 것은 아니다.
1. 쿼리하는 데이터가 복잡할 수록 ORM이 불편한 경우가 있다.
2. ORM 제약으로 인해 SQL의 모든 기능을 활용하지 못한다.
3. SQL을 제대로 이해하지 못하고 작성하는 ORM은 매우 비효율적인 쿼리를 만들어 낸다.

ORM의 기초


Queryset
Django에서 하나의 모델 클래스는 하나의 데이터베이스 테이블에 해당한다.

그리고 모델 클래스를 통해 생성한 하나의 인스턴스는 하나의 테이블 레코드에 해당한다.

django,DB

SQL은 기본적으로 테이블 단위로 쿼리하기 때문에 Django에서 역시 모델 클래스 단위로 쿼리한다.
Django의 ORM 문법이 다음과 같이 클래스부터 시작하는 이유다.

>>> Table

ORM에서 가장 처음 클래스부터 명시하는 것은 SQL의 FROM절을 적는 것과 같음
데이터베이스에서 데이터를 가져오려면 Manager를 통해 QuerySet을 만들어야 함

Table이라는 모델 클래스의 objects라는 attribute가 Django ORM Manager에 해당한다.

>>> Table.objects

Django의 모든 모델 클래스는 기본적으로 objects라는 이름의 Manager를 가지고 있다.
Manager

>>> Table.objects.all()
>>> SELECT * FROM TABLE

반환값을 변수에 할당할 수 있으며, iterable한 객체이다.

	>>> queryshot = Table.objects.all()

위와 같이 조건을 작성하면 쿼리셋이 완성된다.
all()은 테이블의 모든 데이터를 가져오는 역할을 한다.


>>> Table.objects.filter(is_active=True)
>>> SELECT * FROM Table WHERE is_active = 1

filter 메소드를 이용하면 SQL의 WHERE 절에 해당하는 쿼리가 생성이 가능하다.

집계 함수 aggregate()

SQL 집계 함수를 사용하면 집합에 대한 연산이 쉽게 가능한데 ,대표적인 집계 함수는 Sum, Count, Avg, Min, Max 등이 있다.

특징으로 레코드로부터 하나의 결과값을 반환함.
ORM에서는 aggregate()메서드를 통해 이를 지원함

>>> from django.db.models import Count
>>> Table.objects.aggregate(Count('id'))
{"id_count": 3}
>>> SELECT COUNT(id) AS id__count FROM User

주의사항
aggregate()를 이용했을 때와 Count()를 이용했을 때 생성되는 SQL이 다르다.
SQL Count() 함수에 컬럼명을 인자로 전달하면, 해당 컬럼을 기준으로 레코드 개수를 센다.
특정 컬럼 대신 *를 인자로 전달하면 모든 레코드의 개수를 센다.
그런데 특정 컬럼을 기준으로 집계할 때, 해당 컬럼이 NULL 값을 갖는 레코드는 집계에서 제외한다.

>>> from django.db.models import Avg
>>> Goods.object.aggregate(Avg('price')
{'price_avg': 15000}
>>> SELECT AVG(price) FROM Goods


위 테이블은 null 값이여서 평균을 계산할 때 집계에서 제외되지만
만약 값이 0이라면 price_avg : 10000이 될 것이다.

BETWEEN


gt,gte,lt,lte
날짜를 기준으로 조회하는 함수이고 SQL에서 비교 표현식으로 대체된다.

from datetime import date
Table.objects.filter(joined_at__gte=date(2022,1,1),joined_at__lte=date(2022,12,31))
SELECT * FROM Table WHERE joined_at_>-'2022-01-01' AND joined_at <= '2022-12-31'
    

range()

특정 기간에 대해서 조회할 수 있도록하는 함수

Table.objects.fliter(joined_at_range=(date(2022,1,1),date(2022,12,31)))
SELECT * FROM User WHERE joined_at BETWEEN '2022-01-01' AND '2022-12-31'

조회 조건이 되는 마지막 날짜를 잘못 지정하는 것을 조심해야 하고, 조건의 시작 값과 끝 값이 결과 집합에 포함된다

UNION


메소드를 이용하면 쿼리셋의 결과를 합칠 수 있음
union_example

books = Book.objects.all()
ebooks = EBook.objects.all()
books.union(ebooks)
(SELECT * FROM Book) UNION (SELECT * FROM EBook)

두 테이블의 책 제목을 나타내는 컬럼명이 달라도 컬럼 개수가 같으면 union()이 수행이 가능하다. ex) book은 title, e-book은 name인 경우

SQL UNION문은 레코드의 유니크 검사가 꼭 필요한게 아니라면 all = True 옵션을 사용함

Select


annotate()
UNION 된 결과에 각 레코드가 일반 책인지 전자 책인지를 추가로 출력한다

annotate

books = Book.objects.annotate(book_type=Value('일반책'))
ebooks = EBook.objects.annotate(book_type=Value('전자책'))
books.union(ebooks, all=True)
(SELECT id, title, price,'일반책' AS book_type FROM Book)
UNITON ALL
(SELECT id, name, price,'전자책' AS book_type FROM EBook)

위 예시는 Value() 오브젝트 값을 출력했지만, F() 오브젝트를 이용하면 컬럼 간의 연산도 가능하다.

annotate_F

책 테이블에 가격과 할인 금액 컬럼이 있고, 판매 가격 = 가격 - 할인 금액이라고 하면

다음과 같이 F() 오브젝트를 이용하면 컬럼 간 연산 결과를 새로운 컬럼으로 출력할 수 있다.

from django.db.models import F
Book.objects.annotate(sale_price=F('price') - F('discount'))
SELECT id, title, price,discount,price - discount AS sale_price FROM Book

union()이 수직으로 레코드를 확장한다면 annotate는 수평으로 컬럼을 확장시킨다.

values() vs only()

특정 컬럼의 결과만 출력하고 싶은 경우 values() 또는 values_list()를 사용할 수 있다.

>>> Table.objects.values('id','name')
<QuerySet [{'id': 1, 'name': 'Guido'}]>
>>> Table objects.values_list('id','name')
<QuerySet [(1,"Guido")]>

values()values_list()는 비슷하게 작동하지만 values()dictvalues_list()tuple을 반환한다.

반환하는 데이터 타입은 모두 QuerySet이지만 내부 아이템이 instance가 아니다.
values()values_list()를 이용한 경우,반환된 쿼리셋을 기존처럼 사용이 불가능하다

기존 쿼리셋은 instance를 통해 ForeignKey로 연결된 객체에 접근할 수 있다.

table = Table.objects.first()
table.profile
<TalbleProfile: Guido's profile>

하지만 values()는 dict를 반환해서 아래와 같이 ForeignKey의 id만 확인할 수 있다.

Table.objects.values('profile').first()
{'profile': 1}

따라서 기존 쿼리셋처럼 사용하면서 SELECT문에 특정 컬럼만 출력하고 싶은 경우 only()를 사용한다.

Table.objects.only('name')
SELECT id, name FROM TABLE Table # id는 항상 포함

이렇게 간단하게 ORM의 쓰임에 대해서 알아 보았다. 다음 시간에는 현재 진행하는 프로젝트에 필요한 ORM 문법들을 알아볼 예정이다.

틀린 부분은 항상 피드백해주시면 고치도록 하겠습니다. 감사합니다😊


참고 : SQL을 이해하고 사용하는 Django ORM

profile
어제보다 한 걸음 더 나은 개발자

0개의 댓글