├── 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 | aaa 13 | 14 | 15 | 16 | ### [[YouTube] Django ORM (QuerySet)구조와 원리 그리고 최적화전략 - PyCon Korea 2020](https://www.youtube.com/watch?v=EZgLfDrUlrk) 17 | aabba 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 (rowidOrderedProduct) 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 | --------------------------------------------------------------------------------