django prefetch 이슈 해결 정리

x·2023년 2월 7일
0

django

목록 보기
4/5

union 이후 filter 사용 시 에러 django.db.utils.NotSupportedError: Calling QuerySet.filter() after union() is not supported.

해결 방법 : union으로 가져온 모델들의 id로 다시 기존 모델에 접근해서 값 가져오기

https://bitcoden.com/answers/how-to-use-django-querysetunion-in-modeladminformfield_for_manytomany

def get_feeds(self):
    #TODO 1. pet_type: DOG, cateogry 사료에 속하는 것 추출
product_dog_queryset = Product.objects.filter(pet_type='DOG')
    # product_cat_queryset = Product.objects.filter(pet_type='CAT')
    # product_all_queryset = Product.objects.filter(pet_type='ALL')

    category_snack_queryset = Category.objects.filter(name=self.snack_for_dog['gum']).all().union(
        *[Category.objects.filter(name=snack).all() for snack in list(self.snack_for_dog.values())[1:]]
    )

    product_prefetch_snack_for_dog = Prefetch(lookup='skus__product_options__product',
                                              queryset=product_dog_queryset,
                                              to_attr='product_snack_for_dog_list')
    category_prefetch_snack_for_dog = Prefetch(lookup='skus__product_options__product__categories',
                                               queryset=category_snack_queryset,
                                               to_attr='category_snack_for_dog_list')
    dog_snacks = get_read_queryset(IngredientBasedFeed).prefetch_related('skus',
                                                                         'skus__product_options',
                                                                         product_prefetch_snack_for_dog,
                                                                         category_prefetch_snack_for_dog)

    # print(ingredient_based_feeds[0].skus.all()[0].product_options.all()[0].product.categories.all()[0])
    # print(connection.queries)

    # dog_snacks 만 반환하면
    # django.db.utils.NotSupportedError: Calling QuerySet.filter() after union() is not supported. 에러 발생함
    feeds = get_read_queryset(IngredientBasedFeed).filter(id__in=dog_snacks)
    return feeds

ingredient_type에 직접 할당할 수 없음

Direct assignment to the forward side of a many-to-many set is prohibited. Use ingredient_type.set() instead

ingredient_based_feed_recommendations.append(
    IngredientBasedFeedRecommendation(
        product=product,
        ingredient_type=ingredient_types_dct['LOW_FAT']
    )
)
ingredient_based_feed_recommendation = IngredientBasedFeedRecommendation.objects.create(
        product=product,
    )
ingredient_based_feed_recommendation.ingredient_type.add(ingredient_types_dct['LOW_PROTEIN'])
ingredient_based_feed_recommendations.append(
    ingredient_based_feed_recommendation
)

prefetch_related는 불필요한 row들도 가져오는데, python단에서 join하니까 어쩔 수 없는건가? category부터 시작할 때 filter로 name을 특정해도 여러가지 category가 선택됨

# 거꾸로 category -> ingredientbasedfeed 까지 가는거 해보기
ingredient_based_feed_qs = get_read_queryset(IngredientBasedFeed)
ingredient_based_feed_prefetch = Prefetch(lookup='ingredient_based_feed', queryset=ingredient_based_feed_qs)

skus_qs = get_read_queryset(Sku).prefetch_related(ingredient_based_feed_prefetch)#.filter(id=18)
skus_prefetch = Prefetch(lookup='skus', queryset=skus_qs, to_attr='skus_list')

product_options_qs = get_read_queryset(ProductOption).prefetch_related(skus_prefetch)#.filter(id=19425)
product_options_prefetch = Prefetch(lookup='product_product_options', queryset=product_options_qs, to_attr='product_option_list')

products_qs = get_read_queryset(Product).prefetch_related(product_options_prefetch)
products_prefetch = Prefetch(lookup='products', queryset=products_qs, to_attr='product_list')

category_for_pet = self.category_for_pet['DOG']['snack']
category_queryset = get_read_queryset(Category).prefetch_related(products_prefetch).union(
    *[get_read_queryset(Category).filter(name=c).all() for c in
      list(category_for_pet.values())[8]]
)

코드

def ttest(self):
        # prefetch_related에 Prefetch 사용해서 조건 걸어보기
        # category_qs = get_read_queryset(Category).filter(name='저키/트릿')
        # category_prefetch = Prefetch(lookup='categories', queryset=category_qs, to_attr='category_list')
        #
        # product_qs = get_read_queryset(Product).prefetch_related(category_prefetch)
        # product_prefetch = Prefetch(lookup='product', queryset=product_qs, to_attr='product_list')
        #
        # product_options_qs = get_read_queryset(ProductOption).prefetch_related(product_prefetch).filter(id=19425)
        # product_options_prefetch = Prefetch(lookup='product_options', queryset=product_options_qs, to_attr='product_option_list')
        #
        # skus_qs = get_read_queryset(Sku).prefetch_related(product_options_prefetch).filter(id=18)
        # skus_prefetch = Prefetch(lookup='skus', queryset=skus_qs, to_attr='skus_list')
        #
        # TODO : 그냥 for문 돌면서 체크해야할듯 ㅠㅠㅠ
        # feeds = get_read_queryset(IngredientBasedFeed).prefetch_related(skus_prefetch)
        # skus = []
        # product_options = []

        # #
        # for feed in feeds:
        #     if feed and feed.skus_list:
        #         # skus.append(feed.skus_list)
        #         for sku in feed.skus_list:
        #             if sku and sku.product_option_list:
        #                 product_options.append(sku.product_option_list)
        # print(f'with prefetch_related counts : {len(connection.queries)}')
        # print(connection.queries)

        # 거꾸로 category -> ingredientbasedfeed 까지 가는거 해보기
        ingredient_based_feed_qs = get_read_queryset(IngredientBasedFeed)
        ingredient_based_feed_prefetch = Prefetch(lookup='ingredient_based_feed', queryset=ingredient_based_feed_qs)

        skus_qs = get_read_queryset(Sku).prefetch_related(ingredient_based_feed_prefetch)#.filter(id=18)
        skus_prefetch = Prefetch(lookup='skus', queryset=skus_qs, to_attr='skus_list')

        product_options_qs = get_read_queryset(ProductOption).prefetch_related(skus_prefetch)#.filter(id=19425)
        product_options_prefetch = Prefetch(lookup='product_product_options', queryset=product_options_qs, to_attr='product_option_list')

        products_qs = get_read_queryset(Product).prefetch_related(product_options_prefetch)
        products_prefetch = Prefetch(lookup='products', queryset=products_qs, to_attr='product_list')

        category_for_pet = self.category_for_pet['DOG']['snack']
        category_queryset = get_read_queryset(Category).prefetch_related(products_prefetch).union(
            *[get_read_queryset(Category).filter(name=c).all() for c in
              list(category_for_pet.values())[8]]
        )
        # category_queryset.get(name='파우더'), category_queryset[0].product_list[0].product_product_options.all()[0].skus.all()[0].ingredient_based_feed.all()
        return

union 사용 시 문제점 : django.db.utils.NotSupportedError: Calling QuerySet.filter() after union() is not supported.
or문은 느려서 union을 사용하려고 했다. 그러나 위 에러가 발생했다. 결과로 나온 값의 id를 다시 filtering해서 filter(id__in = qs) 하면 된다고 하지만 prefetch_related를 쓰면 inner join이 안되고 전체 row가 다 딸려와서 하는 id만 뽑아내기 어려웠다.

어쩔 수 없이 or문을 썼다. https://stackoverflow.com/questions/59495529/django-query-for-multiple-fields-with-list-values

no data e : Cannot filter a query once a slice has been taken.

해결 방법 안먹힘 https://stackoverflow.com/questions/24690755/django-prefetch-related-with-limit

→ no data e : (1235, "This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'") → product_ids를 list(product_ids)로 수정

product_ids = get_read_queryset(Product).all().order_by('-point').values_list('id', flat=True)[:300]
        # product_prefetch = Prefetch(lookup='product', queryset=product_qs, to_attr='product_list')

        ingredient_based_feed_recommendations = get_read_queryset(IngredientBasedFeedRecommendation).filter(
            ingredient_types__id__in=ingredient_type_ids,
            product__id__in=product_ids
        ).prefetch_related(
            # product_prefetch,
            feed_category_type_prefetch,
            pet_type_prefetch,
        )

Product 에서 관련 M:M 테이블을 prefetch_related로 가져오려고 하니까 불필요한 product도 가져오게 됨.

id를 직접 지정해서 id에 해당하는 product만 가져오도록 수정함


기타

migration 파일 나누지 않으면 모델 rename 시 이전 의존성 migration 파일에서 rename된 모델 찾지 못해서 model resolve 문제 생김

모델 rename 작업과 foreignkey 추가 작업을 하나의 마이그레이션 파일에 했을 때 마주한 문제

마이그레이션 파일 나눠서 작업 함

0개의 댓글