인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

인프런 커뮤니티 질문&답변

pplkjh2님의 프로필 이미지
pplkjh2

작성한 질문수

파이썬/장고 웹서비스 개발 완벽 가이드 with 리액트 (장고 4.2 기준)

07-33 데이터 로드 에러

작성

·

99

0

올려주신 코드에서 ManyToMany 관계를 맺기 전에 pk(즉, id) 값이 필요하기 때문에 bulk_create를 수행하고 tag_set.add 매서드를 호출한다고 강의에서 설명해주셨습니다.

def create_posts(orig_post_list):
    """포스팅 생성"""

    category_dict = {category.name: category for category in Category.objects.all()}
    tag_dict = {tag.name: tag for tag in Tag.objects.all()}

    user_list = list(User.objects.all())

    post_list = []
    for orig_post in orig_post_list:
        post = Post(
            category=category_dict[orig_post["category_name"]],
            author=choice(user_list),
            title=orig_post["title"],
            status=choice([Post.Status.DRAFT, Post.Status.PUBLISHED]),
            content=orig_post["content"],
        )
        post._tag_list = orig_post["tag_list"]
        post.slugify()
        post_list.append(post)

    if post_list:
        print(f"{len(post_list)} 개의 포스팅 생성")
        Post.objects.bulk_create(post_list, batch_size=1000)

        for post in post_list:
            _tag_list = [tag_dict[tag_name] for tag_name in post._tag_list]
            post.tag_set.add(*_tag_list)

그러나 해당 코드를 실행 시 여전히 tag_set.add를 위해서는 id가 필요하다고 에러가 발생합니다.

사용 중인 DB는 mysql 이며, 일단 임시 변통으로 bulk_create 전에 id를 직접 할당하여 해결하였습니다.

for idx, orig_post in enumerate(orig_post_list):
    post.id = idx + 1 # id는 0이 될 수 없음  

그러나 이는 --clear argument를 주었기 때문에 가능한 행위였습니다.

 

수정하기 위해서 하기와 같은 방법이 적절할까요? 그리고 왜 제 경우만 에러가 발생하는 걸까요?

    if post_list:
        print(f"{len(post_list)} 개의 포스팅 생성")
        Post.objects.bulk_create(post_list, batch_size=1000)

        tag_lists = [t for t in orig_post["tag_list"]]
        post_list = Post.objects.all() # id 로드를 위함
      
        for post, tag_list in zip(post_list, tag_lists):
            _tag_list = [tag_dict[tag_name] for tag_name in tag_list]
            post.tag_set.add(*_tag_list)

답변 2

0

이진석님의 프로필 이미지
이진석
지식공유자

안녕하세요.

id 값은 models.BigAutoField로서 데이터베이스에서 정해주는 것인데요.

mysql에서 지정해준 id 값을 반환해줄 수 없기 때문에, select LAST_INSERT_ID() as id; 를 통해 최근 insert id를 조회하더라도, 추정치로 id 값을 지정해야하기 때문에, id 값을 잘못 지정될 가능성이 있습니다.

그러니, 아래와 같이 mysql일 경우에만 다시 쿼리하는 방식이 낫지 않을까 생각이 듭니다.
(connect은 from django.db import connection 코드를 통해 임포트합니다. default 데이터베이스의 vendor를 반환합니다.)

image

살펴보시고, 댓글 남겨주세요.

0

이진석님의 프로필 이미지
이진석
지식공유자

안녕하세요.

 

post_list 상의 순서와 Post.objects.all() 쿼리셋을 통해 조회된 순서가 다를 수 있으므로, 가장 아래에서 보여주신 코드에서는 태그가 엉뚱하게 지정될 수 있습니다.

그리고, 모델의 id 필드는 models의 BigAutoField로서 데이터베이스로부터 자동할당받는 상황인데요. 직접 .id 값을 할당하시게 되면, 엉뚱한 id로서 데이터베이스에 저장될 수 있으므로, 주의하셔야될 코드입니다.

 

질문을 정리해보자면, post_list 리스트의 각 Post 모델 인스턴스에는 .id 값이 할당되지 않은 상황이고, bulk_create 호출 후에 각 Post 모델 인스턴스에 id 값이 할당되지 않은 상황이라는 말씀하시죠?

Post.objects.bulk_create(post_list, batch_size=1000)

 

확인해보니, sqlite3와 postgresql에서는 INSERT 쿼리 시에 데이터베이스로부터 삽입된 행의 id를 반환받을 수 있는 데, mysql에서는 INSERT 쿼리 시에 삽입된 행의 id를 반환받는 기능이 제공되지 않아서 그런 것으로 보여집니다.

 

아래는 동일한 코드로 bulk_create을 수행했을 때, 수행되는 쿼리 비교입니다.

  • PostgreSQL에서는 삽입된 행의 id 반환

    • INSERT INTO "blog_post" ("title") VALUES ('t1') RETURNING "blog_post"."id"

  • SQLite3에서는 삽입된 행의 id 반환

    • INSERT INTO "blog_post" ("title") VALUES ('t1') RETURNING "blog_post"."id";

  • MySQL에서는 INSERT 쿼리 만으로 삽입된 id를 반환받을 수 없음.

    • INSERT INTO blog_post (`title`) VALUES ('t1');

       

 

mysql에서는 쿼리에서 LAST_INSERT_ID()를 통해 조회가 가능한 것으로 보여지는 데요. mysql에서의 이 이슈를 오늘 파악해보고, 추가로 답변드리겠습니다.

 

학습에 불편을 끼쳐드려 죄송합니다.

 

이진석 드림

 

참고) 관련 강의 영상 - 07-33 관계 모델을 효율적으로 조회하기 (select_related와 prefetch_related) - 포스팅 목록 조회

 

pplkjh2님의 프로필 이미지
pplkjh2

작성한 질문수

질문하기