├── configuration
├── __init__.py
├── python_study
│ ├── __init__.py
│ └── snippet.py
├── wsgi.py
├── urls.py
└── settings.py
├── orm_practice_app
├── __init__.py
├── management
│ ├── __init__.py
│ └── commands
│ │ ├── __init__.py
│ │ └── bulk_create.py
├── migrations
│ ├── __init__.py
│ ├── 0002_auto_20210929_0331.py
│ └── 0001_initial.py
├── tests.py
├── admin.py
├── apps.py
├── post_construct.py
├── orm2.py
├── models.py
├── views.py
└── queryset_pratice.py
├── db.sqlite3
├── .gitignore
├── requirements.txt
├── docker-compose.yaml
├── manage.py
└── README.md
/configuration/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/orm_practice_app/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/configuration/python_study/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/orm_practice_app/management/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/orm_practice_app/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/orm_practice_app/management/commands/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/db.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KimSoungRyoul/PyConKR2020-DjangoORM/HEAD/db.sqlite3
--------------------------------------------------------------------------------
/orm_practice_app/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 |
3 | .idea/*
4 | /venv/*
5 | .volume
6 |
--------------------------------------------------------------------------------
/orm_practice_app/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | # Register your models here.
4 |
--------------------------------------------------------------------------------
/orm_practice_app/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class OrmPraticeAppConfig(AppConfig):
5 | name = 'orm_practice_app'
6 |
--------------------------------------------------------------------------------
/configuration/wsgi.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from django.core.wsgi import get_wsgi_application
4 |
5 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'configuration.settings')
6 |
7 | application = get_wsgi_application()
8 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | asgiref==3.4.1
2 | Django==3.2.7
3 | django-extensions==3.1.3
4 | django-extensions-shell==1.7.4.1
5 | django-query-logger==0.1.2
6 | # psycopg2==2.9.1 # 설치 편의성을 위해 주석처리합니다.
7 | psycopg2-binary==2.9.1
8 | pytz==2021.1
9 | six==1.15.0
10 | sqlparse==0.3.1
11 | sqlformatter==1.4
--------------------------------------------------------------------------------
/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: "3.8"
2 | services:
3 | postgres:
4 | image: postgres
5 | container_name: postgres_for_orm_pratice
6 | environment:
7 | - POSTGRES_PASSWORD=password
8 | ports:
9 | - '5439:5432'
10 | volumes:
11 | - .volume/postgres:/var/lib/postgresql/data
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == '__main__':
6 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'configuration.settings')
7 | try:
8 | from django.core.management import execute_from_command_line
9 | except ImportError as exc:
10 | raise ImportError(
11 | "Couldn't import Django. Are you sure it's installed and "
12 | "available on your PYTHONPATH environment variable? Did you "
13 | "forget to activate a virtual environment?"
14 | ) from exc
15 | execute_from_command_line(sys.argv)
16 |
--------------------------------------------------------------------------------
/configuration/urls.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.urls import path
3 |
4 | from orm_practice_app import queryset_pratice, views
5 |
6 | urlpatterns = [
7 | path('admin/', admin.site.urls),
8 |
9 | path('bulk_create/', queryset_pratice.asdf),
10 |
11 | path('i-am-api/', views.i_am_function_view),
12 | path('i-am-api2/', views.i_am_function_view2),
13 | path('i-am-api3/', views.i_am_function_view3),
14 | path('i-am-api3-1/', views.i_am_function_view3_1),
15 | path('i-am-api4/', views.i_am_function_view4),
16 | path('i-am-api5/', views.i_am_function_view5),
17 | ]
18 |
--------------------------------------------------------------------------------
/configuration/python_study/snippet.py:
--------------------------------------------------------------------------------
1 | from django.utils.functional import cached_property
2 |
3 |
4 | class ASDF(object):
5 | aaa = 2
6 |
7 | @cached_property
8 | def bbb(self):
9 | return self.aaa + 1
10 |
11 |
12 | import weakref
13 |
14 | a_set = {0, 1}
15 | wref = weakref.ref(a_set)
16 | print(wref)
17 | print(wref())
18 | print(a_set)
19 |
20 | print('여전히 존재: ', wref())
21 |
22 | print(wref() is None)
23 |
24 | print(wref() is None)
25 |
26 | a = [1, 2, 3, ]
27 | b = a.append(4)
28 | b
29 |
30 | charles = {'name': 'asdf', 'num': 123}
31 |
32 |
33 | # 가변 객체와 불가변 객체의 차이
34 | l1 = [1, 2, ]
35 | print(id(l1))
36 | l1 += [3, 4]
37 | print(id(l1))
38 |
39 | t1 = (1, 2,)
40 | print(id(t1))
41 | t1 += (3, 4,)
42 | print(id(t1))
43 |
44 |
45 | a_param=[1,2,3]
46 | b_param=(1,2,)
47 | print(id(a_param))
48 | print(id(b_param))
49 | def hi(lll, t1):
50 | print(id(lll), id(t1))
51 |
--------------------------------------------------------------------------------
/orm_practice_app/post_construct.py:
--------------------------------------------------------------------------------
1 | from random import randint
2 |
3 | from django.contrib.auth.hashers import make_password
4 | from django.contrib.auth.models import User
5 |
6 | from orm_practice_app.models import Company, Product, Order, OrderedProduct
7 |
8 | for i in range(1, 1001):
9 | User.objects.bulk_create([
10 | User(
11 | username='username' + str(idx * i),
12 | email='soungryoul.kim@deliveryhero.co.kr',
13 | password=make_password('django_password'),
14 | is_active=True,
15 | ) for idx in range(1, 31)
16 | ])
17 |
18 | for i in range(1, 101):
19 | Company.objects.bulk_create([
20 | Company(
21 | name='company_name' + str(idx),
22 | tel_num='070-123-4567',
23 | address='서초구 ~~ 마제스타시티',
24 | ) for idx in range(1, 51)
25 | ])
26 |
27 | for i in range(1, 10001):
28 | Product.objects.bulk_create([
29 | Product(
30 | name='product_name' + str(idx * i),
31 | price=randint(10000, 100001),
32 | product_owned_company__id=randint(0, 5000),
33 | ) for idx in range(1, 11)
34 | ])
35 |
36 | for i in range(1, 10001):
37 | order = Order.objects.bulk_create([
38 | Order(
39 | descriptions='주문의 상세내용입니다...' + str(idx * i),
40 | order_owner__id=randint(1, 50000),
41 | ) for idx in range(1, 11)
42 | ])
43 |
44 | OrderedProduct.objects.bulk_create([
45 | OrderedProduct(
46 | product_cnt=randint(1, 30),
47 | amount_of_credited_mileage=randint(100, 4000),
48 | related_order=order,
49 | related_product_id=randint(1, 100000)
50 | )
51 | ])
52 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Django_ORM_pratice_project
2 |
3 |
4 | 이 프로젝트는 장고 ORM을 공부하기 위해 만든 개인 프로젝트
5 |
6 | 적당한 더미데이터와 적당한 모델들을 만들어놓고
7 |
8 | 쿼리셋 수행 결과를 기록해놓음
9 |
10 | ---
11 | ### [PyCon2020 발표자료PDF 다운로드 바로가기](https://github.com/KimSoungRyoul/PyCon2020-DjangoORM/issues/7)
12 |
13 |
14 |
15 |
16 | ### [[YouTube] Django ORM (QuerySet)구조와 원리 그리고 최적화전략 - PyCon Korea 2020](https://www.youtube.com/watch?v=EZgLfDrUlrk)
17 |
18 |
19 |
20 |
21 |
22 | ---
23 |
24 | ### 이 Repo 관련 자료는 한빛미디어 [백엔드 개발자를 위한 핸즈온 장고] 책의 초안으로 활용되었고 더 쉽게 정리해서 출간했습니다. [2023-06]
25 | * ### [django-backend-starter](https://github.com/KimSoungRyoul/django-backend-starter) Repo에서 Django ORM 뿐만 아니라 전체적인 내용을 체계적으로 관리해보려합니다.
26 |
27 |
28 |
29 | ---
30 |
31 |
32 | #### QuerySet과 SQL 매칭 결과 (Postgresql 기준이지만 기초적인 SQL문법수행이라 다른DB들과 결과는 동일)
33 | * 굳이 이 프로젝트를 clone 안해도 issue창에 내용들을 정리해놔서 충분히 도움이 될거라 생각됩니다.
34 |
35 | https://github.com/KimSoungRyoul/Django_ORM_pratice_project/issues
36 | https://github.com/KimSoungRyoul/Django_ORM_pratice_project/blob/master/orm_practice_app/queryset_pratice.py (쿼리셋 연습장)
37 |
38 |
39 | ### Quick Start (이 프로젝트 써보기)
40 |
41 | 1. `git clone git@github.com:KimSoungRyoul/Django_ORM_pratice_project.git`
42 | 2. `pip install -r requirements.txt`
43 | 3. `docker-compose up -d` // 커맨드 수행시 port: 5439로 postgres가 열립니다. local에 postgres설치되어있다면 스킵하도됨
44 | 4. `python manage.py migrate`
45 | 5. `python manage.py bulk_create` // DB에 적당히 더미데이터 생성합니다.
46 | 6. `python manage.py shell_plus` // 쉘플러스 접속해서 자기가 원하는QuerySet을 실행해봅니다 (jupiter도 좋고 shell_plus도 좋고!)
47 |
48 |
49 |
50 | ### queryset 관련 도움이 되는 글들
51 |
52 | * [Django ORM CookBook 전체적인 QuerySet 사용법을 설명하는 책입니다](https://books.agiliq.com/projects/django-orm-cookbook/en/latest/)
53 |
54 |
55 | * [Django에서는 QuerySet이 당신을 만듭니다 (1)](https://medium.com/deliverytechkorea/django-queryset-1-14b0cc715eb7)
56 |
57 | * [Django에서는 QuerySet이 당신을 만듭니다 (2)](https://medium.com/deliverytechkorea/django%EC%97%90%EC%84%9C%EB%8A%94-queryset%EC%9D%B4-%EB%8B%B9%EC%8B%A0%EC%9D%84-%EB%A7%8C%EB%93%AD%EB%8B%88%EB%8B%A4-2-5f6f8c6cd7e3)
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/orm_practice_app/orm2.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.models import User
2 | from django.db.models import Subquery, F, Count, Avg
3 | from django.db.models.functions import Substr
4 | from django.db.models import prefetch_related_objects
5 |
6 | from orm_practice_app.models import UserInfo, Product, Company
7 |
8 | users = User.objects.filter(id__lte=30)
9 |
10 | UserInfo.objects.filter(owned_user__in=Subquery(User.objects.filter(id__lte=30).values('id'))).explain()
11 |
12 | """
13 | SELECT "orm_practice_app_userinfo"."id",
14 | "orm_practice_app_userinfo"."owned_user_id",
15 | "orm_practice_app_userinfo"."tel_num"
16 |
17 | FROM "orm_practice_app_userinfo"
18 |
19 | WHERE "orm_practice_app_userinfo"."owned_user_id"
20 | IN (SELECT U0."id" FROM "auth_user" U0 WHERE U0."id" <= 30)
21 |
22 | """
23 |
24 | # EXPLAIN QUERY PLAN
25 | """
26 |
27 | Execution time: 0.000090s [Database: default]
28 |
29 | '3 0 0 SEARCH TABLE orm_practice_app_userinfo USING INDEX orm_practice_app_userinfo_owned_user_id_e85907f1 (owned_user_id=?)
30 | 7 0 0 LIST SUBQUERY 1
31 | 9 7 0 SEARCH TABLE auth_user AS U0 USING INTEGER PRIMARY KEY (rowid)'
32 |
33 | """
34 |
35 | User.objects.annotate(first=Substr("first_name", 1, 1), last=Substr("last_name", 1, 1)).filter(first=F("last"))
36 |
37 | """
38 | SELECT "auth_user"."id",
39 | "auth_user"."password",
40 | "auth_user"."last_login",
41 | "auth_user"."is_superuser",
42 | "auth_user"."username",
43 | "auth_user"."first_name",
44 | "auth_user"."last_name",
45 | "auth_user"."email",
46 | "auth_user"."is_staff",
47 | "auth_user"."is_active",
48 | "auth_user"."date_joined",
49 | SUBSTR("auth_user"."first_name", 1, 1) AS "first",
50 | SUBSTR("auth_user"."last_name", 1, 1) AS "last"
51 |
52 | FROM "auth_user"
53 | WHERE SUBSTR("auth_user"."first_name", 1, 1) = (SUBSTR("auth_user"."last_name", 1, 1))
54 | """
55 |
56 | duplicates = User.objects.values('first_name').annotate(name_count=Count('first_name')).filter(name_count__gt=1)
57 |
58 |
59 |
60 |
61 | Product.objects.aggregate(Avg('price'))
62 |
63 | product_list = list(Product.objects.filter(id__lte=10))
64 |
65 | execute_prefetch= True
66 |
67 | if execute_prefetch:
68 | # 원하는 시점에 +1 쿼리 prefetch 쿼리를 실행할수 있다.
69 | prefetch_related_objects(product_list, 'product_owned_company')
70 |
71 | # 위에서 prefetch 쿼리가 실행되어서 n+1 problem이 발생하지 않는다.
72 | for product in product_list:
73 | print(product.product_owned_company)
74 |
75 | else:
76 | # 여기서는 +1 쿼리가 발생안해서 n+1Problem 발생한다
77 | for product in product_list:
78 | print(product.product_owned_company)
79 |
--------------------------------------------------------------------------------
/orm_practice_app/migrations/0002_auto_20210929_0331.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.2.7 on 2021-09-29 03:31
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("orm_practice_app", "0001_initial"),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name="aab",
15 | name="id",
16 | field=models.BigAutoField(
17 | auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
18 | ),
19 | ),
20 | migrations.AlterField(
21 | model_name="company",
22 | name="id",
23 | field=models.BigAutoField(
24 | auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
25 | ),
26 | ),
27 | migrations.AlterField(
28 | model_name="mileage",
29 | name="id",
30 | field=models.BigAutoField(
31 | auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
32 | ),
33 | ),
34 | migrations.AlterField(
35 | model_name="order",
36 | name="id",
37 | field=models.BigAutoField(
38 | auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
39 | ),
40 | ),
41 | migrations.AlterField(
42 | model_name="orderedproduct",
43 | name="id",
44 | field=models.BigAutoField(
45 | auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
46 | ),
47 | ),
48 | migrations.AlterField(
49 | model_name="product",
50 | name="id",
51 | field=models.BigAutoField(
52 | auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
53 | ),
54 | ),
55 | migrations.AlterField(
56 | model_name="user",
57 | name="id",
58 | field=models.BigAutoField(
59 | auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
60 | ),
61 | ),
62 | migrations.AlterField(
63 | model_name="useraddress",
64 | name="id",
65 | field=models.BigAutoField(
66 | auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
67 | ),
68 | ),
69 | migrations.AlterField(
70 | model_name="userinfo",
71 | name="id",
72 | field=models.BigAutoField(
73 | auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
74 | ),
75 | ),
76 | ]
77 |
--------------------------------------------------------------------------------
/orm_practice_app/management/commands/bulk_create.py:
--------------------------------------------------------------------------------
1 | from random import randint
2 |
3 | from django.contrib.auth.hashers import make_password
4 | from orm_practice_app.models import User
5 | from django.core.management import BaseCommand
6 | from django.utils import timezone
7 |
8 | from orm_practice_app.models import Company, Order, OrderedProduct, Product, UserInfo
9 |
10 |
11 | class Command(BaseCommand):
12 |
13 | def handle(self, *args, **options):
14 | user_cnt = 100
15 | company_cnt = 100
16 | product_cnt = 100
17 | order_cnt = 1000
18 |
19 | suffix = User.objects.last().id if User.objects.exists() else 0
20 |
21 | for i in range(0, user_cnt, 20):
22 | User.objects.bulk_create([
23 | User(
24 | username='username' + str(suffix + idx + i),
25 | email='soungryoul.kim@deliveryhero.co.kr' + str(idx + i),
26 | password=make_password('django_password'),
27 | is_active=True,
28 | userinfo=UserInfo.objects.create(tel_num=f"010-2222-34{i}"),
29 | ) for idx in range(1, 21)
30 | ])
31 |
32 | users = User.objects.all()
33 |
34 | for i in range(0, company_cnt, 20):
35 | Company.objects.bulk_create([
36 | Company(
37 | name='company_name' + str(i + idx),
38 | tel_num='070-123-4567',
39 | address='서초구 ~~ 마제스타시티',
40 | ) for idx in range(1, 21)
41 | ])
42 | companies = Company.objects.all()
43 |
44 | for i in range(0, product_cnt, 100):
45 | Product.objects.bulk_create([
46 | Product(
47 | name='product_name' + str(i + idx),
48 | price=randint(10000, 100001),
49 | product_owned_company=companies[randint(0, company_cnt - 1) // 2],
50 | ) for idx in range(1, 101)
51 | ])
52 | products = Product.objects.all()
53 |
54 | for i in range(0, order_cnt, 99):
55 | orders = Order.objects.bulk_create([
56 | Order(
57 | descriptions='주문의 상세내용입니다...' + str(i + idx),
58 | reg_date=timezone.now(),
59 | order_owner=users[randint(0, user_cnt - 1) // 2],
60 | ) for idx in range(1, 101)
61 | ])
62 |
63 | OrderedProduct.objects.bulk_create([
64 | OrderedProduct(
65 | product_cnt=randint(1, 30),
66 | amount_of_credited_mileage=randint(100, 4000),
67 | related_order=Order.objects.get(id=idx),
68 | related_product=products[randint(0, product_cnt - 1) // 2],
69 | ) for idx in range(1, 100)
70 | ])
71 |
72 | self.stdout.write(self.style.SUCCESS('적당히 더미데이터 만들어짐....'))
73 |
--------------------------------------------------------------------------------
/orm_practice_app/models.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 |
3 | from django.contrib.auth.models import User, AbstractUser
4 | from django.db import models
5 |
6 | """
7 | 사업자 1명은 상품(Product) N개를 가질수 있다
8 | 주문 1개는 상품(Product) 와 M:N 관계 (RelationTable=>OrderedProduct)
9 | 주문 1개는 그 주문으로 발생한 마일리지정보와 1대1관계다.
10 | """
11 |
12 |
13 | class Company(models.Model):
14 | name: str = models.CharField(max_length=128, null=False)
15 | tel_num: str = models.CharField(max_length=128, null=True)
16 | address: str = models.CharField(max_length=128, null=False)
17 |
18 |
19 | class Product(models.Model):
20 | name: str = models.CharField(null=False, max_length=128)
21 | price: int = models.PositiveIntegerField(null=False, default=0)
22 | product_owned_company: Company = models.ForeignKey(Company, on_delete=models.CASCADE, null=True, blank=False)
23 |
24 |
25 | class OrderedProduct(models.Model):
26 | product_cnt: int = models.PositiveIntegerField(null=False, default=1)
27 | amount_of_credited_mileage: int = models.PositiveIntegerField(null=False)
28 |
29 | related_product: Product = models.ForeignKey(Product, on_delete=models.CASCADE)
30 | related_order = models.ForeignKey('Order', on_delete=models.CASCADE, null=True)
31 |
32 |
33 | class Order(models.Model):
34 | descriptions: str = models.CharField(null=False, default='비어있음..', max_length=128)
35 | reg_date: datetime = models.DateTimeField(auto_created=True)
36 | order_owner: User = models.ForeignKey(to='orm_practice_app.User', on_delete=models.CASCADE, null=True, blank=False)
37 | product_set_included_order: set = models.ManyToManyField(to=Product, related_name='ordered_product_set',
38 | through='OrderedProduct',
39 | through_fields=('related_order', 'related_product'),
40 | )
41 |
42 |
43 | class UserAddress(models.Model):
44 | user_info = models.ForeignKey(to="UserAddress", on_delete=models.CASCADE)
45 |
46 | city = models.CharField(help_text='서울시,안양시,...', max_length=128, null=False)
47 | gu = models.CharField(help_text='서초구, 강남구,...,', max_length=128, null=False, default='')
48 | detail = models.CharField(help_text='104동 101호', max_length=128, null=False, default='')
49 |
50 |
51 | class Mileage(models.Model):
52 | owned_userinfo = models.ForeignKey(to='orm_practice_app.User', on_delete=models.CASCADE, null=True)
53 | related_order = models.OneToOneField(Order, on_delete=models.CASCADE, null=False)
54 | amount = models.PositiveSmallIntegerField(default=0)
55 | descriptions = models.CharField(max_length=128, null=True)
56 |
57 |
58 | class UserInfo(models.Model):
59 | tel_num = models.CharField(max_length=128, null=True)
60 |
61 |
62 | class User(AbstractUser):
63 | userinfo = models.OneToOneField('orm_practice_app.UserInfo', on_delete=models.CASCADE, null=False)
64 | aab = models.ForeignKey("AAB", on_delete=models.CASCADE, null=True)
65 |
66 |
67 | class AAB(models.Model):
68 | name = models.CharField(max_length=32, default="dsfsdf")
69 |
70 |
71 | #
72 | # Order.objects.get(id=3).product_set_included_order.annotate(num_name_blabla=Count('name')).aggregate(Avg('num_name_blabla'))
73 |
74 |
75 | class Asf(object):
76 | asdf = ''
77 |
78 | #
79 | # order_list = (
80 | # Order.objects
81 | # .select_related('order_owner')
82 | # .filter(order_owner__username='username4')
83 | # .prefetch_related('product_set_included_order')
84 | # )
85 | #
86 | # mil_list = (
87 | # Mileage.objects.prefetch_related('owned_userinfo','related_order__product_set_included_order')
88 | # )
--------------------------------------------------------------------------------
/configuration/settings.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
4 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
5 |
6 | # Quick-start development settings - unsuitable for production
7 | # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
8 |
9 | # SECURITY WARNING: keep the secret key used in production secret!
10 | SECRET_KEY = 'yo#)2s(m2i$&7ogyomx@ll1^we(re41%ves(=)-tk7qycr=+b4'
11 |
12 | # SECURITY WARNING: don't run with debug turned on in production!
13 | DEBUG = True
14 |
15 | ALLOWED_HOSTS = []
16 |
17 | # Application definition
18 |
19 | INSTALLED_APPS = [
20 | 'django.contrib.admin',
21 | 'django.contrib.auth',
22 | 'django.contrib.contenttypes',
23 | 'django.contrib.sessions',
24 | 'django.contrib.messages',
25 | 'django.contrib.staticfiles',
26 | 'django_extensions',
27 | 'django_extensions_shell',
28 |
29 | 'orm_practice_app',
30 | ]
31 |
32 | MIDDLEWARE = [
33 | 'django.middleware.security.SecurityMiddleware',
34 | 'django.contrib.sessions.middleware.SessionMiddleware',
35 | 'django.middleware.common.CommonMiddleware',
36 | 'django.middleware.csrf.CsrfViewMiddleware',
37 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
38 | 'django.contrib.messages.middleware.MessageMiddleware',
39 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
40 | ]
41 |
42 | ROOT_URLCONF = 'configuration.urls'
43 |
44 | TEMPLATES = [
45 | {
46 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
47 | 'DIRS': [os.path.join(BASE_DIR, 'templates')]
48 | ,
49 | 'APP_DIRS': True,
50 | 'OPTIONS': {
51 | 'context_processors': [
52 | 'django.template.context_processors.debug',
53 | 'django.template.context_processors.request',
54 | 'django.contrib.auth.context_processors.auth',
55 | 'django.contrib.messages.context_processors.messages',
56 | ],
57 | },
58 | },
59 | ]
60 |
61 | WSGI_APPLICATION = 'configuration.wsgi.application'
62 |
63 | # Database
64 | # https://docs.djangoproject.com/en/2.1/ref/settings/#databases
65 |
66 | DATABASES = {
67 | 'default': {
68 | 'ENGINE': 'django.db.backends.postgresql',
69 | 'HOST': '127.0.0.1',
70 | 'NAME': 'postgres',
71 | 'USER': 'postgres',
72 | 'PASSWORD': 'password',
73 | 'PORT': 5439,
74 | }
75 | }
76 |
77 | # Password validation
78 | # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
79 |
80 | AUTH_PASSWORD_VALIDATORS = [
81 | {
82 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
83 | },
84 | {
85 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
86 | },
87 | {
88 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
89 | },
90 | {
91 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
92 | },
93 | ]
94 |
95 | # Internationalization
96 | # https://docs.djangoproject.com/en/2.1/topics/i18n/
97 |
98 | LANGUAGE_CODE = 'en-us'
99 |
100 | TIME_ZONE = 'UTC'
101 |
102 | USE_I18N = True
103 |
104 | USE_L10N = True
105 |
106 | USE_TZ = True
107 |
108 | # Static files (CSS, JavaScript, Images)
109 | # https://docs.djangoproject.com/en/2.1/howto/static-files/
110 |
111 | STATIC_URL = '/static/'
112 |
113 | AUTH_USER_MODEL = "orm_practice_app.User"
114 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
115 |
116 | # 이 옵션은 프로젝트내에서 수행되는 SQL을 전부 로깅해주는 옵션입니다.
117 | LOGGING = {
118 | 'version': 1,
119 | 'disable_existing_loggers': False,
120 | 'handlers': {
121 | 'sqlhandler': {
122 | 'level': 'DEBUG',
123 | 'class': 'logging.StreamHandler',
124 | 'formatter': 'sqlformatter'
125 | }
126 | },
127 | 'formatters': {
128 | 'sqlformatter': {
129 | '()': 'sqlformatter.SqlFormatter',
130 | 'format': '%(levelname)s %(message)s',
131 | },
132 | },
133 | 'loggers': {
134 | 'django.db.backends': {
135 | 'handlers': ['sqlhandler'],
136 | 'level': 'DEBUG',
137 | },
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/orm_practice_app/views.py:
--------------------------------------------------------------------------------
1 | import json
2 | from typing import List, Dict, Any, Type
3 |
4 | from django.core.handlers.wsgi import WSGIRequest
5 | from django.core.serializers.json import DjangoJSONEncoder
6 | from django.db.models import QuerySet, Q
7 | from django.forms import model_to_dict
8 | from django.http import HttpResponse, QueryDict
9 | from django.core.serializers import serialize
10 |
11 | from orm_practice_app.models import User, Order, Company, Product
12 |
13 |
14 | def i_am_function_view(request: WSGIRequest):
15 | # HTTP Request 잡아서 처리하는 로직
16 | query_params: QueryDict = request.POST
17 |
18 | # User를 선언하는 시점에 users는 다만 쿼리셋에 지나지 않았다.
19 | users: QuerySet = User.objects.all()
20 | if isinstance(users, QuerySet):
21 | print("users 는 아직 쿼리셋이기때문에 이 print문이 출력됩니다.")
22 |
23 | # list()로 쿼리셋을 불렀을때 users는 List[Model]이 된다.
24 | user_list: List[User] = list(users) # 리스트로 묶는 시점에 실제 SQL이 호출됩니다.
25 | if isinstance(user_list, QuerySet):
26 | print("user_list는 쿼리셋이 아닙니다. 이 print문은 출력안됨")
27 |
28 | # 직렬화 로직
29 | user_list_dict: List[Dict[str, Any]] = [
30 | model_to_dict(user, fields=('id', 'username', 'is_staff', 'first_name', 'last_name', 'email'))
31 | for user in user_list
32 | ]
33 | # Dict로 직렬화한 데이터를 json 포맷을 가진 문자열로 풀어준다.
34 | user_list_json_array: str = json.dumps(user_list_dict, indent=1, cls=DjangoJSONEncoder)
35 |
36 | # 이 문자열을 httpResponse body(content)에 담아서 반환한다.
37 | return HttpResponse(content=user_list_json_array, content_type="application/json")
38 |
39 |
40 | def i_am_function_view2(request: WSGIRequest):
41 |
42 | print('i_am_function_view2 호출.....')
43 | # User를 선언하는 시점에는 SQL이 호출되지 않음
44 | users: QuerySet = User.objects.all()
45 |
46 | # 아래 쿼리셋들을 선언만 해놓고 사용하지 않음 , 이러면 SQL이 호출되지 않는다.
47 | orders: QuerySet = Order.objects.all()
48 | companies: QuerySet = Company.objects.all()
49 |
50 |
51 | print('')
52 | user_list: List[User] = list(users)
53 |
54 | # 직렬화 로직
55 | user_list_dict: List[Dict[str, Any]] = [
56 | model_to_dict(user, fields=('id', 'username', 'is_staff', 'first_name', 'last_name', 'email'))
57 | for user in user_list
58 | ]
59 | user_list_json_array: str = json.dumps(user_list_dict, indent=1, cls=DjangoJSONEncoder)
60 |
61 | return HttpResponse(content=user_list_json_array, content_type="application/json")
62 |
63 |
64 |
65 |
66 | def i_am_function_view3(request: WSGIRequest):
67 |
68 | # User를 선언하는 시점에는 SQL이 호출되지 않음
69 | users: QuerySet = User.objects.all()
70 |
71 | # 0번째 User를 얻어오고싶어서 users쿼리셋은 SQL을 호출
72 | first_user: User = users[0]
73 |
74 | # 바로 윗줄에서 user1명밖에 가져오지 않아서 모든 user를 얻으려면 어쩔수 없이 다시 SQL을 호출해야함
75 | user_list: List[User] = list(users)
76 |
77 |
78 | # 직렬화 로직
79 | user_list_dict: List[Dict[str, Any]] = [
80 | model_to_dict(user, fields=('id', 'username', 'is_staff', 'first_name', 'last_name', 'email'))
81 | for user in user_list
82 | ]
83 | user_list_json_array: str = json.dumps(user_list_dict, indent=1, cls=DjangoJSONEncoder)
84 |
85 | return HttpResponse(content=user_list_json_array, content_type="application/json")
86 |
87 |
88 |
89 | # N+1 Problem 예제
90 | def i_am_function_view3_1(request: WSGIRequest):
91 |
92 | # User를 선언하는 시점에는 SQL이 호출되지 않음
93 | users: QuerySet = User.objects.all()
94 |
95 | # 개발자 관점에는 각user의 모든 userinfo가 필요한 것을 알지만 QuerySet은 그걸 모른다.
96 | for user in users:
97 | # QuerySet입장에서 user의 userinfo 가 필요한 시점은 여기다.
98 | # 따라서 userinfo를 알기위해 SQL을 for문이 돌때마다(N번) 호출한다.
99 | user.userinfo
100 |
101 | user_list: List[User] = list(users)
102 |
103 | # 직렬화 로직
104 | user_list_dict: List[Dict[str, Any]] = [
105 | model_to_dict(user)
106 | for user in user_list
107 | ]
108 | user_list_json_array: str = json.dumps(user_list_dict, indent=1, cls=DjangoJSONEncoder)
109 |
110 | return HttpResponse(content=user_list_json_array, content_type="application/json")
111 |
112 |
113 |
114 | def i_am_function_view4(request: WSGIRequest):
115 |
116 | # User를 선언하는 시점에는 SQL이 호출되지 않음
117 | users: QuerySet = User.objects.all()
118 |
119 |
120 | user_list: List[User] = list(users)
121 | # 바로 위에서 users쿼리셋은 모든 user를 가져오는 SQL을 이미 호출함 따라서 0번째 user는 users쿼리셋에 캐싱된 값을 재활용함 (SQL호출 X)
122 | first_user: User = users[0]
123 |
124 | # 이 예제를 통해 배울점: 쿼리셋을 호출하는 순서가 바뀌는 것만으로도 QuerySet캐싱때문에 발생하는 SQL이 달라질수있다.
125 |
126 |
127 | # 직렬화 로직
128 | user_list_dict: List[Dict[str, Any]] = [
129 | model_to_dict(user, fields=('id', 'username', 'is_staff', 'first_name', 'last_name', 'email'))
130 | for user in user_list
131 | ]
132 | user_list_json_array: str = json.dumps(user_list_dict, indent=1, cls=DjangoJSONEncoder)
133 |
134 | return HttpResponse(content=user_list_json_array, content_type="application/json")
135 |
136 |
137 | def i_am_function_view5(request: WSGIRequest):
138 |
139 | # company pk가 20아하인 Product들을 전부 조회
140 | company_queryset: QuerySet = Company.objects.filter(id__lte=20).values_list("id",flat=True)
141 |
142 | product_queryset: QuerySet =Product.objects.filter(product_owned_company__id__in=company_queryset)
143 |
144 | product_list: List[Product] = list(product_queryset)
145 | print(product_list)
146 |
147 | normal_joined_queryset = Order.objects.filter(descriptions__isnull=False, product_set_included_order__name="asdfsdf")
148 |
149 | subquery_executed_queryset = Order.objects.filter(descriptions__isnull=False).exclude(product_set_included_order__name="asdfsdf")
150 |
151 | subquery_executed_queryset2= Order.objects.filter(Q(descriptions__isnull=False), ~Q(product_set_included_order__name="asdfsdf"))
152 |
153 | normal_joined_queryset2 = Order.objects.filter(Q(descriptions__isnull=False)).exclude(order_owner__userinfo__tel_num="010-2222-342")
154 |
155 | list(normal_joined_queryset)
156 | list(subquery_executed_queryset)
157 | list(subquery_executed_queryset2)
158 | list(normal_joined_queryset2)
159 | return HttpResponse(content="", content_type="application/json")
160 |
--------------------------------------------------------------------------------
/orm_practice_app/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1 on 2020-08-17 07:31
2 |
3 | from django.conf import settings
4 | import django.contrib.auth.models
5 | import django.contrib.auth.validators
6 | from django.db import migrations, models
7 | import django.db.models.deletion
8 | import django.utils.timezone
9 |
10 |
11 | class Migration(migrations.Migration):
12 |
13 | initial = True
14 |
15 | dependencies = [
16 | ('auth', '0012_alter_user_first_name_max_length'),
17 | ]
18 |
19 | operations = [
20 | migrations.CreateModel(
21 | name='User',
22 | fields=[
23 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
24 | ('password', models.CharField(max_length=128, verbose_name='password')),
25 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
26 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
27 | ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
28 | ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
29 | ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
30 | ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
31 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
32 | ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
33 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
34 | ],
35 | options={
36 | 'verbose_name': 'user',
37 | 'verbose_name_plural': 'users',
38 | 'abstract': False,
39 | },
40 | managers=[
41 | ('objects', django.contrib.auth.models.UserManager()),
42 | ],
43 | ),
44 | migrations.CreateModel(
45 | name='AAB',
46 | fields=[
47 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
48 | ('name', models.CharField(default='dsfsdf', max_length=32)),
49 | ],
50 | ),
51 | migrations.CreateModel(
52 | name='Company',
53 | fields=[
54 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
55 | ('name', models.CharField(max_length=128)),
56 | ('tel_num', models.CharField(max_length=128, null=True)),
57 | ('address', models.CharField(max_length=128)),
58 | ],
59 | ),
60 | migrations.CreateModel(
61 | name='Order',
62 | fields=[
63 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
64 | ('reg_date', models.DateTimeField(auto_created=True)),
65 | ('descriptions', models.CharField(default='비어있음..', max_length=128)),
66 | ('order_owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
67 | ],
68 | ),
69 | migrations.CreateModel(
70 | name='UserInfo',
71 | fields=[
72 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
73 | ('tel_num', models.CharField(max_length=128, null=True)),
74 | ],
75 | ),
76 | migrations.CreateModel(
77 | name='UserAddress',
78 | fields=[
79 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
80 | ('city', models.CharField(help_text='서울시,안양시,...', max_length=128)),
81 | ('gu', models.CharField(default='', help_text='서초구, 강남구,...,', max_length=128)),
82 | ('detail', models.CharField(default='', help_text='104동 101호', max_length=128)),
83 | ('user_info', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='orm_practice_app.useraddress')),
84 | ],
85 | ),
86 | migrations.CreateModel(
87 | name='Product',
88 | fields=[
89 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
90 | ('name', models.CharField(max_length=128)),
91 | ('price', models.PositiveIntegerField(default=0)),
92 | ('product_owned_company', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='orm_practice_app.company')),
93 | ],
94 | ),
95 | migrations.CreateModel(
96 | name='OrderedProduct',
97 | fields=[
98 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
99 | ('product_cnt', models.PositiveIntegerField(default=1)),
100 | ('amount_of_credited_mileage', models.PositiveIntegerField()),
101 | ('related_order', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='orm_practice_app.order')),
102 | ('related_product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='orm_practice_app.product')),
103 | ],
104 | ),
105 | migrations.AddField(
106 | model_name='order',
107 | name='product_set_included_order',
108 | field=models.ManyToManyField(related_name='ordered_product_set', through='orm_practice_app.OrderedProduct', to='orm_practice_app.Product'),
109 | ),
110 | migrations.CreateModel(
111 | name='Mileage',
112 | fields=[
113 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
114 | ('amount', models.PositiveSmallIntegerField(default=0)),
115 | ('descriptions', models.CharField(max_length=128, null=True)),
116 | ('owned_userinfo', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
117 | ('related_order', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='orm_practice_app.order')),
118 | ],
119 | ),
120 | migrations.AddField(
121 | model_name='user',
122 | name='aab',
123 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='orm_practice_app.aab'),
124 | ),
125 | migrations.AddField(
126 | model_name='user',
127 | name='groups',
128 | field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups'),
129 | ),
130 | migrations.AddField(
131 | model_name='user',
132 | name='user_permissions',
133 | field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'),
134 | ),
135 | migrations.AddField(
136 | model_name='user',
137 | name='userinfo',
138 | field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='orm_practice_app.userinfo'),
139 | ),
140 | ]
141 |
--------------------------------------------------------------------------------
/orm_practice_app/queryset_pratice.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 |
3 | from django.contrib.auth.models import User
4 | from django.db.models import Q, FilteredRelation, F, Sum, Avg, Subquery, Prefetch, QuerySet
5 |
6 | from orm_practice_app.models import Company, Product, Order, OrderedProduct
7 |
8 |
9 | def asdf():
10 | Product.objects.filter(name='product_name3', product_owned_company__name='company_name20').select_related(
11 | 'product_owned_company')
12 |
13 | Company.objects.prefetch_related('company_set').filter(product__name='product_anme8')
14 |
15 | Product.objects.filter(product_owned_company__name='company_name133')
16 | Order.objects.filter(order_owner__is_active=True)
17 | User.objects.filter(order__descriptions__contains='asdf')
18 | OrderedProduct.objects.filter(related_order__order_owner__user_permissions__isnull=True)
19 | OrderedProduct.objects.filter(product_cnt=30).prefetch_related('related_order')
20 |
21 | # product_set을 prefetched_related로 가져오는 쿼리셋 선언
22 | company_queryset: QuerySet = Company.objects.prefetch_related('product_set').filter(name='company_name1')
23 | # 쿼리셋을 수행하면 아래와 같은 쿼리 2개 발생 문제 없음
24 | comapny_list: List[Company]= list(company_queryset)
25 | """
26 | SELECT "orm_practice_app_company"."id", "orm_practice_app_company"."name", "orm_practice_app_company"."tel_num", "orm_practice_app_company"."address"
27 | FROM "orm_practice_app_company"
28 | WHERE "orm_practice_app_company"."name" = 'company_name1' LIMIT 21;
29 | -- prefetch_related() 구절이 아래 SQL을 부른다 --
30 | SELECT "orm_practice_app_product"."id", "orm_practice_app_product"."name", "orm_practice_app_product"."price", "orm_practice_app_product"."product_owned_company_id"
31 | FROM "orm_practice_app_product"
32 | WHERE "orm_practice_app_product"."product_owned_company_id" IN (1, 21);
33 |
34 | """
35 | # 위와같은 방식에는 문제가 없다
36 | # 하지만?
37 | company_queryset: QuerySet = Company.objects.prefetch_related(
38 | Prefetch('product_set', queryset=Product.objects.filter(product__name='product_name3'))).filter(
39 | name='company_name1')
40 | # 이렇게 product관련 조건절이 추가된다면 어떨까
41 | # 쿼리셋을 수행하면 아래와 같은 잘못된 쿼리가 발생한다.
42 | comapny_list: List[Company] = list(company_queryset)
43 | """
44 | SELECT "orm_practice_app_company"."id", "orm_practice_app_company"."name", "orm_practice_app_company"."tel_num", "orm_practice_app_company"."address"
45 | FROM "orm_practice_app_company"
46 | INNER JOIN "orm_practice_app_product" ON ("orm_practice_app_company"."id" = "orm_practice_app_product"."product_owned_company_id")
47 | WHERE ("orm_practice_app_company"."name" = 'company_name1' AND "orm_practice_app_product"."name" = 'product_name3') LIMIT 21;
48 | -- prefetch_related가 쿼리를 발생시켰지만 윗 쿼리에서 불필요한 조인이 발생한다. --
49 | SELECT "orm_practice_app_product"."id", "orm_practice_app_product"."name", "orm_practice_app_product"."price", "orm_practice_app_product"."product_owned_company_id"
50 | FROM "orm_practice_app_product"
51 | WHERE "orm_practice_app_product"."product_owned_company_id" IN (1);
52 |
53 | """
54 | # 이는 가장 흔히 발생할수 있는 문제다. 해당 쿼리셋을 수행했을때
55 | # 1.조인을 하던지
56 | # 2.추가쿼리에서 조건절이 붙던지
57 | # 둘중 하나만 수행되었어야 효율적인 쿼리다. 그러나 생각없이 prefetch_related를 붙이면 이런식으로 잘못된 쿼리가 수행될수있다
58 |
59 |
60 |
61 |
62 |
63 |
64 | # 1-2 이 쿼리는 prefetch_related를 사용했음에도 불구하고 QuerySet 평가시 추가적인 쿼리가 불필요하다 판단하여 inner join 전략을 택한다. 이경우는 .prefetch_related('related_order') 이 로직은 무시된다
65 | OrderedProduct.objects.filter(Q(product_cnt=30) & Q(related_order__descriptions='asdf')).prefetch_related('related_order')
66 | """
67 | SELECT *
68 | FROM "orm_practice_app_orderedproduct"
69 | INNER JOIN "orm_practice_app_order" ON ("orm_practice_app_orderedproduct"."related_order_id" = "orm_practice_app_order"."id")
70 | WHERE ("orm_practice_app_orderedproduct"."product_cnt" = 30 AND "orm_practice_app_order"."descriptions" = 'asdf')
71 | """
72 |
73 | # 1-3 이 쿼리는 의도한대로 +1개의 쿼리로 related_order를 조회한다 filter절에서 related_order에 대해 별다른 내용이 없어서 반항없이 개발자의 의도대로 따라준다.
74 | OrderedProduct.objects.filter(product_cnt=30).prefetch_related('related_order')
75 | """
76 | SELECT *
77 | FROM "orm_practice_app_orderedproduct"
78 | WHERE "orm_practice_app_orderedproduct"."product_cnt" = 30 LIMIT 21;
79 |
80 | SELECT *
81 | FROM "orm_practice_app_order"
82 | WHERE "orm_practice_app_order"."id" IN (135, 776, 404, 535, 151, 280, 666, 155, 29, 675, 548, 298, 45, 48, 177, 306, 336, 729, 605, 226, 739);
83 |
84 | """
85 |
86 | # 이러면 prefetch_related()를 붙혀준 의도대로 +1 쿼리로 'related_order'를 조회한다 그러나 완벽히 의도한 쿼리가 생성되지 않는다.
87 | OrderedProduct.objects.filter(Q(product_cnt=30) | Q(related_order__descriptions='asdf')).prefetch_related('related_order')
88 | """
89 | SELECT *
90 | FROM "orm_practice_app_orderedproduct"
91 | INNER JOIN "orm_practice_app_order" ON ("orm_practice_app_orderedproduct"."related_order_id" = "orm_practice_app_order"."id")
92 | WHERE ("orm_practice_app_orderedproduct"."product_cnt" = 30 OR "orm_practice_app_order"."descriptions" = 'asdf');
93 |
94 | SELECT *
95 | FROM "orm_practice_app_order"
96 | WHERE "orm_practice_app_order"."id" IN (135, 776, 404, 535, 151, 280, 666, 155, 29, 675, 548, 298, 45, 48, 177, 306, 336, 729, 605, 226, 739);
97 | """
98 | # prefetch_related()로 추가되는 쿼리에 조건을 걸어주고 싶다면 Prefetch()를 사용해야한다
99 | OrderedProduct.objects.filter(Q(product_cnt=30)).prefetch_related(Prefetch('related_order', queryset=Order.objects.filter(descriptions='asdf')))
100 | """
101 | SELECT *
102 | FROM "orm_practice_app_orderedproduct"
103 | WHERE "orm_practice_app_orderedproduct"."product_cnt" = 30 ;
104 |
105 | SELECT *
106 | FROM "orm_practice_app_order"
107 | WHERE ("orm_practice_app_order"."descriptions" = 'asdf'
108 | AND "orm_practice_app_order"."id" IN (515, 644, 135, 391, 267, 526, 529, 660, 21, 663, 280, 422, 47, 707, 336, 593, 98, 228, 486, 374, 379));
109 |
110 |
111 | """
112 |
113 | # 앞 쿼리들의 결과에서도 봤듯이 OrderProduct->Order 참조에 관련된 쿼리는 정방향 참조이기때문에 충분히 inner join 전략을 택할수 있다
114 | # 그래서 앞에서 prefetch_related()를 붙이지 않거나 prefetch_related를 붙이더라도 +1 query를 만들지 않고 Django QuerySet은 최대한 inner join전략을 택하려고 노력한다.
115 | OrderedProduct.objects.filter(Q(product_cnt=30) & Q(related_order__descriptions='asdf')).select_related(
116 | 'related_order')
117 | """
118 | SELECT *
119 | FROM "orm_practice_app_orderedproduct"
120 | INNER JOIN "orm_practice_app_order" ON ("orm_practice_app_orderedproduct"."related_order_id" = "orm_practice_app_order"."id")
121 | WHERE ("orm_practice_app_orderedproduct"."product_cnt" = 30 AND "orm_practice_app_order"."descriptions" = 'asdf')
122 |
123 | """
124 |
125 | # 2-1 이러면 대참사가 발생한다. foransdmf 이것을 N+1 Select Problem 이라고 한다.
126 | companys = Company.objects.filter(name__startswith='company_name')
127 | for company in companys:
128 | print(company.product_set[0])
129 | """
130 | SELECT * FROM "orm_practice_app_company" WHERE "orm_practice_app_company"."name"::text LIKE 'company\_name%';
131 | SELECT * FROM "orm_practice_app_product" WHERE "orm_practice_app_product"."product_owned_company_id" = 301;
132 | SELECT * FROM "orm_practice_app_product" WHERE "orm_practice_app_product"."product_owned_company_id" = 302;
133 | SELECT * FROM "orm_practice_app_product" WHERE "orm_practice_app_product"."product_owned_company_id" = 303;
134 | SELECT * FROM "orm_practice_app_product" WHERE "orm_practice_app_product"."product_owned_company_id" = 304;
135 | SELECT * FROM "orm_practice_app_product" WHERE "orm_practice_app_product"."product_owned_company_id" = 305;
136 | SELECT * FROM "orm_practice_app_product" WHERE "orm_practice_app_product"."product_owned_company_id" = 306;
137 | """
138 |
139 | # 2-2 이러면 딱 2개의 쿼리만 발생한다 prefetch_related를 가장 적절하게 활용한 좋은 예제이다.
140 | # prefetch_related()는 역참조해야 하는 상황에서 아래와 같은 N+1문제를 방지한다.
141 | # 단순 one table join과 같은 상황에서는 django orm이 최대한 inner join를 우선적으로 고민하고 불가능하면 left outer join로
142 | companys = Company.objects.filter(name__startswith='company_name').prefetch_related('product_set')
143 | for company in companys:
144 | print(company.product_set[0])
145 |
146 | # 3-1 product_owned_company필드에 null=True 옵션이 있다 이런경우는 outer join
147 | Product.objects.filter(price__gt=24000).select_related('product_owned_company')
148 | """
149 | SELECT *
150 | FROM "orm_practice_app_product"
151 | LEFT OUTER JOIN "orm_practice_app_company" ON ("orm_practice_app_product"."product_owned_company_id" = "orm_practice_app_company"."id")
152 | WHERE "orm_practice_app_product"."price" > 24000
153 | """
154 | Product.objects.filter(price__gt=24000, product_owned_company__isnull=False).select_related('product_owned_company')
155 | Product.objects.filter(price__gt=24000, product_owned_company__isnull=False).select_related('product_owned_company')
156 | """
157 | SELECT *
158 | FROM "orm_practice_app_product"
159 | INNER JOIN "orm_practice_app_company" ON ("orm_practice_app_product"."product_owned_company_id" = "orm_practice_app_company"."id")
160 | WHERE ("orm_practice_app_product"."price" > 24000 AND "orm_practice_app_product"."product_owned_company_id" IS NOT NULL);
161 | """
162 |
163 | # 4-1 Join Table에 제약 주기 FilteredRelation()는 Django2.0부터 가능
164 | Product.objects.select_related('this_is_join_table_name').annotate(this_is_join_table_name=FilteredRelation('product_owned_company',
165 | condition=Q(
166 | product_owned_company__name='company_name34'),),
167 | ).filter(this_is_join_table_name__isnull=False)
168 | """
169 | SELECT "orm_practice_app_product"."id", "orm_practice_app_product"."name", "orm_practice_app_product"."price", "orm_practice_app_product"."product_owned_company_id"
170 | FROM "orm_practice_app_product"
171 | INNER JOIN "orm_practice_app_company" this_is_join_table_name
172 | ON ("orm_practice_app_product"."product_owned_company_id" = this_is_join_table_name."id" AND ( this_is_join_table_name."name" = 'company_name34')
173 | )
174 | WHERE this_is_join_table_name."id" IS NOT NULL ;
175 |
176 |
177 | """
178 |
179 | # 내가 원한다고 쿼리를 나눌수있는게 아니다.
180 | OrderedProduct.objects.filter(product_cnt=23000,
181 | related_product__product_owned_company__name__contains='comapny_name').prefetch_related(
182 | 'related_product')
183 | """
184 | SELECT *
185 | FROM "orm_practice_app_orderedproduct"
186 | INNER JOIN "orm_practice_app_product" ON ("orm_practice_app_orderedproduct"."related_product_id" = "orm_practice_app_product"."id")
187 | INNER JOIN "orm_practice_app_company" ON ("orm_practice_app_product"."product_owned_company_id" = "orm_practice_app_company"."id")
188 | WHERE ("orm_practice_app_orderedproduct"."product_cnt" = 23000 AND "orm_practice_app_company"."name"::text LIKE '%comapny\_name%')
189 |
190 | """
191 |
192 | # 결론은 무거울 것으로 예상되는 QuerySet은 직접 쿼리는 찍어서 확인을 해야한다.
193 | OrderedProduct.objects.filter(product_cnt=23000,
194 | related_product__product_owned_company__name__contains='comapny_name').prefetch_related(
195 | 'related_product', 'related_product__product_owned_company')
196 |
197 | """
198 | SELECT "orm_practice_app_orderedproduct"."id", "orm_practice_app_orderedproduct"."product_cnt", "orm_practice_app_orderedproduct"."amount_of_credited_mileage", "orm_practice_app_orderedproduct"."related_product_id", "orm_practice_app_orderedproduct"."related_order_id"
199 | FROM "orm_practice_app_orderedproduct"
200 | INNER JOIN "orm_practice_app_product" ON ("orm_practice_app_orderedproduct"."related_product_id" = "orm_practice_app_product"."id")
201 | INNER JOIN "orm_practice_app_company" ON ("orm_practice_app_product"."product_owned_company_id" = "orm_practice_app_company"."id")
202 | WHERE ("orm_practice_app_orderedproduct"."product_cnt" = 23000 AND "orm_practice_app_company"."name"::text LIKE '%comapny\_name%')
203 | """
204 |
205 | OrderedProduct.objects.filter(product_cnt=23000).prefetch_related(
206 | Prefetch('related_product__product_owned_company',
207 | queryset=Company.objects.filter(name__contains='comapny_name')))
208 | """
209 | SELECT "orm_practice_app_orderedproduct"."id", "orm_practice_app_orderedproduct"."product_cnt",
210 | "orm_practice_app_orderedproduct"."amount_of_credited_mileage", "orm_practice_app_orderedproduct"."related_product_id",
211 | "orm_practice_app_orderedproduct"."related_order_id"
212 | FROM "orm_practice_app_orderedproduct"
213 | INNER JOIN "orm_practice_app_product" ON ("orm_practice_app_orderedproduct"."related_product_id" = "orm_practice_app_product"."id")
214 | INNER JOIN "orm_practice_app_company" ON ("orm_practice_app_product"."product_owned_company_id" = "orm_practice_app_company"."id")
215 | WHERE ("orm_practice_app_orderedproduct"."product_cnt" = 23000 AND "orm_practice_app_company"."name"::text LIKE '%comapny\_name%')
216 | """
217 |
218 |
219 | order_list: Order = Order.objects.filter(id=3).prefetch_related('product_set_included_order')
220 | """
221 | SELECT "orm_practice_app_order"."id", "orm_practice_app_order"."reg_date", "orm_practice_app_order"."descriptions", "orm_practice_app_order"."order_owner_id"
222 | FROM "orm_practice_app_order" WHERE "orm_practice_app_order"."id" = 3 ;
223 |
224 | SELECT ("orm_practice_app_orderedproduct"."related_order_id") AS "_prefetch_related_val_related_order_id",
225 | "orm_practice_app_product"."id", "orm_practice_app_product"."name", "orm_practice_app_product"."price", "orm_practice_app_product"."product_owned_company_id"
226 | FROM "orm_practice_app_product"
227 | INNER JOIN "orm_practice_app_orderedproduct" ON ("orm_practice_app_product"."id" = "orm_practice_app_orderedproduct"."related_product_id")
228 | WHERE "orm_practice_app_orderedproduct"."related_order_id" IN (3);
229 | """
230 |
231 | Order.objects.filter(id=4, product_set_included_order__product_owned_company=3)
232 | """
233 | SELECT "orm_practice_app_order"."id", "orm_practice_app_order"."reg_date", "orm_practice_app_order"."descriptions", "orm_practice_app_order"."order_owner_id"
234 | FROM "orm_practice_app_order"
235 | INNER JOIN "orm_practice_app_orderedproduct" ON ("orm_practice_app_order"."id" = "orm_practice_app_orderedproduct"."related_order_id")
236 | INNER JOIN "orm_practice_app_product" ON ("orm_practice_app_orderedproduct"."related_product_id" = "orm_practice_app_product"."id")
237 | WHERE ("orm_practice_app_order"."id" = 4 AND "orm_practice_app_product"."product_owned_company_id" = 3)
238 | """
239 |
240 | Order.objects.filter(id=4).prefetch_related('product_set_included_order')
241 | """
242 | SELECT * FROM "orm_practice_app_order"
243 | WHERE "orm_practice_app_order"."id" = 4 LIMIT 21;
244 | SELECT * FROM "orm_practice_app_product"
245 | INNER JOIN "orm_practice_app_orderedproduct" ON ("orm_practice_app_product"."id" = "orm_practice_app_orderedproduct"."related_product_id")
246 | WHERE "orm_practice_app_orderedproduct"."related_order_id" IN (4);
247 |
248 | """
249 |
250 | order_product = OrderedProduct.objects.filter(related_order=4).select_related('related_order', 'related_product')
251 | """
252 | SELECT "orm_practice_app_orderedproduct"."id", "orm_practice_app_orderedproduct"."product_cnt",
253 | "orm_practice_app_orderedproduct"."amount_of_credited_mileage", "orm_practice_app_orderedproduct"."related_product_id", "orm_practice_app_orderedproduct"."related_order_id",
254 | "orm_practice_app_product"."id", "orm_practice_app_product"."name", "orm_practice_app_product"."price", "orm_practice_app_product"."product_owned_company_id",
255 | "orm_practice_app_order"."id", "orm_practice_app_order"."reg_date", "orm_practice_app_order"."descriptions", "orm_practice_app_order"."order_owner_id"
256 | FROM "orm_practice_app_orderedproduct"
257 | INNER JOIN "orm_practice_app_order" ON ("orm_practice_app_orderedproduct"."related_order_id" = "orm_practice_app_order"."id")
258 | INNER JOIN "orm_practice_app_product" ON ("orm_practice_app_orderedproduct"."related_product_id" = "orm_practice_app_product"."id")
259 | WHERE "orm_practice_app_orderedproduct"."related_order_id" = 4
260 | """
261 |
262 | order_queryset = Order.objects.filter(descriptions__contains='상세내용입니다').prefetch_related(
263 | 'product_set_included_order')
264 |
265 | for order in order_queryset[:10]:
266 | order.product_set_included_order.all()
267 |
268 | """
269 | SELECT "orm_practice_app_order"."id", "orm_practice_app_order"."reg_date", "orm_practice_app_order"."descriptions", "orm_practice_app_order"."order_owner_id"
270 | FROM "orm_practice_app_order"
271 | INNER JOIN "orm_practice_app_orderedproduct" ON ("orm_practice_app_order"."id" = "orm_practice_app_orderedproduct"."related_order_id")
272 | WHERE ("orm_practice_app_order"."id" = 4 AND "orm_practice_app_orderedproduct"."related_product_id" IS NOT NULL) ;
273 |
274 |
275 | """
276 |
277 | Product.objects.filter(id=4).select_related('product_owned_company')
278 | """
279 | SELECT "orm_practice_app_product"."id", "orm_practice_app_product"."name", "orm_practice_app_product"."price",
280 | "orm_practice_app_product"."product_owned_company_id", "orm_practice_app_company"."id", "orm_practice_app_company"."name",
281 | "orm_practice_app_company"."tel_num", "orm_practice_app_company"."address"
282 | FROM "orm_practice_app_product"
283 | LEFT OUTER JOIN "orm_practice_app_company" ON ("orm_practice_app_product"."product_owned_company_id" = "orm_practice_app_company"."id")
284 | WHERE "orm_practice_app_product"."id" = 4;
285 | """
286 |
287 | Product.objects.annotate(custom_field=F('name') + F('price')).filter(id=3)
288 |
289 | # 일단 데이터를 다 가져와서 sum(product_set.value_list('price'))
290 | Product.objects.filter(id__lte=10).aggregate(total_price=Avg('price'))
291 | """
292 | SELECT AVG("orm_practice_app_product"."price") AS "total_price"
293 | FROM "orm_practice_app_product"
294 | WHERE "orm_practice_app_product"."id" <= 10
295 |
296 | """
297 |
298 | # 서브쿼리로 가져오기
299 | users = User.objects.filter(id__lte=20)
300 | Order.objects.filter(order_owner__in=Subquery(users.values('id')))
301 | """
302 | SELECT "orm_practice_app_order"."id", "orm_practice_app_order"."reg_date",
303 | "orm_practice_app_order"."descriptions", "orm_practice_app_order"."order_owner_id"
304 | FROM "orm_practice_app_order"
305 | WHERE "orm_practice_app_order"."order_owner_id" IN (SELECT U0."id" FROM "auth_user" U0);
306 | """
307 |
308 | Order.objects.filter(order_owner_id__lte=20)
309 |
310 |
311 | # right outer 가능한가? 불가
312 | Product.objects.filter(product_owned_company__name='company_name3').select_related('product_owned_company')
313 |
314 | # many to many join
315 | Order.objects.prefetch_related('product_set_included_order').filter(id=1)
316 |
317 |
318 | aa=Order.objects.filter(descriptions__isnull=False,product_set_included_order__name="asdfsdf")
319 |
320 | bb= Order.objects.filter(Q(descriptions__isnull=False)).exclude(order_owner__userinfo__tel_num="010-2222-342")
321 |
322 |
--------------------------------------------------------------------------------