[TIL] # 30 Django CRUD (2)

ddalkigum·2020년 12월 29일
1

TIL

목록 보기
30/50
post-thumbnail

select_related 와 prefetch_related를 중점적으로
추가적인 내용을 정리 해보려 합니다

지극히 주관적이고, 제 생각이 많이 들어가 있습니다

users, posts, comments

세가지 테이블을 가지고 진행하겠습니다

Select_related가 어떻게 작동하는지는 지난 블로그에서 볼 수 있습니다

사실 이해를 했다고 생각하고 다시 한번 공부를 했는데
내가 다시 공부하면서 느낀점은

내가 알던거랑 다른데? 였다

우선 Select_related에 관해서 검색하면 가장 먼저 보이는 것이 JOIN일 것이다
그리고 맞는 사실이다

  1. JOIN을 사용해서 관계되어 있는 쿼리를 가지고 올 때 사용한다

  2. 장고에서는 select_related를 사용하지 않아도 JOIN이 필요하면 자동적으로 사용한다

  3. 여기서 추가적으로 query를 가지고 올 경우 prefetch_related를 사용한다

  4. 정참조일 경우 가지고 올 수 있다.

  5. 아주 복잡한 ORM의 경우 RIGTH OUTER JOIN이 발생할 수 있겟지만
    왠만한 경우에서 RIGTH OUTER JOIN이 발생하는 경우는 거의 없다

정참조
Post에서 user_id를 이용해서 여러 포스트를 만들 수 있는데
여기서 user_id를 사용하고 있는 post에서 user의 column값을 참조하는 것
즉 "다"의 테이블에서 "일"의 테이블을 참조 하는 것

예시

Post.objects.select_related("user").filter(id = 1)
SELECT `posts`.`id`, `posts`.`content`, `posts`.`user_id`, `posts`.`created`, `posts`.`updated`, `posts`.`photo`, `users`.`id`, `users`.`email`, `users`.`password` 
FROM `posts` INNER JOIN `users` 
ON (`posts`.`user_id` = `users`.`id`)

예시가 그렇게 훌륭하지는 않지만
코드만 본다면 post에서 FK로 연결되어있는 user를 참조한다
post의 id가 1인 쿼리를 가지고 온다

이런 방식으로 흘러간다

select_related를 사용하지 않아도 JOIN을 사용하는 경우

Post.objects.filter(id = 1, comment__content = "endendend")
SELECT `posts`.`id`, `posts`.`content`, `posts`.`user_id`, `posts`.`created`, `posts`.`updated`, `posts`.`photo` 
FROM `posts` INNER JOIN `comments` 
ON (`posts`.`id` = `comments`.`post_id`) 
WHERE (`comments`.`content` = endendend AND `posts`.`id` = 1)

위와 같은 상황에서는 select_related를 사용하지 않았는데도
장고에서 필요에 의해 JOIN을 사용한 걸 확인할 수 있다

장고가 알아서 판단하고 사용해주기는 하지만
아쉬운 점은 내가 정확하게 쓰고있는건가?
생각이 들때가 있다

post에서 글을 작성한 유저들의 이메일을 가지고 오고 싶다면

posts = Post.objects.select_related("user")
for post in posts:
	post.user.email
    
>>>kim@naver.com
>>>lee@gmail.com
>>>kim@naver.com
>>>jung@naver.com
>>>kim@gmail.com

이런식으로 활용이 가능하다 물론 select_related를 사용하지 않더라도

posts = Post.objects.all()
for post in posts:
	post.user.email

이런식으로 가지고 올 수는 있지만

select_related를 사용함으로써 내가 JOIN을 사용해서 가지고 왔다는 걸
더 확실하게 보여 줄 수 있게된다

이렇게만 보면 select_related의 경우 INNER JOIN을 사용하는 상황밖에 나오지 않는다

그럼 OUTER JOIN의 경우는 어떻게 표현을 해야 할까?

OUTER JOIN의 경우 우선 SQL문으로 보게 되면 null값을 포함한 상태로 출력이 된다

JOIN에 관한 SQL문
https://velog.io/@ddalkigum/TIL-28-SQL%EB%AC%B8-2

따라서 OUTER JOIN을 원한다면
null = True인 FK를 포함할 경우 사용 할 수 있다

하지만 장고에서는 코드를 적는 특성상 RIGHT OUTER JOIN은 불가능 하다
부모 ntt는 항상 왼쪽에 존재하기 떄문에

부모라고 표현하기 애매하지만,,, 음.. 주인? 부모 아니면 주인 정도로 이해하면 될 것같다
글의 주인, 코멘트의 주인 정도

역참조의 경우

post에서 user로 정참조가 아닌 post에서 comment로 역참조는 select_related로는 불가능하다

post = Post.objects.select_related("comment_set")

장고에서 아주 친절하게 FieldError를 내보내 주신다


이 친구는 정말 이해하기 힘들었다
어디에 쓰이는지 부터해서, 왜 사용하는지

사용할 때마다 왜 쿼리문이 다르게 출력이 되는지

장고가 알아서 해주기 때문에... 가 답이지 않을까 싶다

  1. 다대다 상황에서 사용가능하다

  2. 일대다 관계에서 역참조를 할 경우 사용 가능하다

  3. 위에서 언급했던 select_related 후에 추가 쿼리를 가지고 올때 사용 가능하다

  4. 장고는 친절한데, 내가 확실하게 알기가 어렵다

이해하기 힘들었던 부분

posts = Post.objects.select_related("user").prefetch_related("comment_set")
SELECT `posts`.`id`, `posts`.`content`, `posts`.`user_id`, `posts`.`created`, `posts`.`updated`, `posts`.`photo`, `users`.`id`, `users`.`email`, `users`.`password` 
FROM `posts` INNER JOIN `users` 
ON (`posts`.`user_id` = `users`.`id`)

뭐지 싶었다 내가 prefetch_related를 사용했는데
왜 select_related를 사용했을 때랑 쿼리문이 같지??

정말 너무.... 이해하기 힘들엇다
난 분명히 comment를 역참조해서 ( 물론, 코멘트에 관한걸 사용하지는 않았지만 )
결과물을 가지고 오려하는데 왜 쿼리문이 같을까?

생각을 떨쳐낼 수가 없엇다

post = Post.objects.prefetch_related("comment_set").filter(comment__id = 1)
SELECT `posts`.`id`, `posts`.`content`, `posts`.`user_id`, `posts`.`created`, `posts`.`updated`, `posts`.`photo` 
FROM `posts` INNER JOIN `comments` 
ON (`posts`.`id` = `comments`.`post_id`) 
WHERE `comments`.`id` = 1

이렇게 역참조를 진행하여 comment의 id가 1인 post를 가지고 왔다

prefetch_related 도 select_related처럼 굳이 사용하지 않아도
장고에서 처리해주기는 하지만, 위에서도 말했듯이

내 의도를 알아 볼수 있게 만들어 주는게 좋다고 생각한다

prefetch_related를 사용하지 않을 경우

post = Post.objects.filter(comment__id = 1)
SELECT `posts`.`id`, `posts`.`content`, `posts`.`user_id`, `posts`.`created`, `posts`.`updated`, `posts`.`photo` 
FROM `posts` INNER JOIN `comments` 
ON (`posts`.`id` = `comments`.`post_id`) 
WHERE `comments`.`id` = 1

우리 눈으로 보기에는 굉장히 간단한 코드인데
장고 내부에서는 알아서 판단해서 INNER JOIN을 사용하는 걸 볼 수 있다

post = Post.objects.filter(comment__id = 1).prefetch_related("comment_set")
SELECT `posts`.`id`, `posts`.`content`, `posts`.`user_id`, `posts`.`created`, `posts`.`updated`, `posts`.`photo` 
FROM `posts` INNER JOIN `comments` 
ON (`posts`.`id` = `comments`.`post_id`) 
WHERE `comments`.`id` = 1

완전히 같은 내용의 쿼리문을 다양하게 표현이 가능하다

이 부분도 좀 의아했던 부분인데,,, 장고가 알아서 해준다는 걸 알고
아 ... 이것도 마찬가지 인거구나 싶엇다

user = User.objects.prefetch_related("like_set").filter(id = 1)
likes = user[0].like_set.all()
# user의 쿼리문 

SELECT `likes`.`id`, `likes`.`user_id`, `likes`.`post_id` 
FROM `likes` 
WHERE `likes`.`user_id` = 1

# likes를 가지고 오는데 까지의 쿼리문 

SELECT `users`.`id`, `users`.`email`, `users`.`password` 
FROM `users` 
WHERE `users`.`id` = 1

좋아요 관련 유저를 찾기 위해 다대다 필드에서 역참조를 진행해서
likes를 누른 유저들을 찾아 주는 것인데

보면 알겠지만 쿼리문이 오히려 짧아 졋다

내가 아무리 길게 코드를 넣어도 판단해서 query문을 날려준다

이렇게 쿼리문을 보게 되면 내가 효율적으로 짜지 못했구나 ... 싶기도 하고
다른 곳에서 시작해서 가지고 오는 방법으로 사용하면 더 효율적일 수 있겟다

여러 생각이 든다

좀 더 효율적인 코드를 짜고 싶다
많은 코드들을 보고 경험해보고, 어떻게 짜야 더 효율적일지 생각해보자


지극히 제 주관적인 글입니다...
혹시나 틀린 부분이있다면 댓글 부탁드려요 😁

profile
딸기검 -본캐🐒 , 김준형 - 현실 본캐 🐒

2개의 댓글

comment-user-thumbnail
2020년 12월 29일

ㅋㅎ👀 딸기검님이 너무 어려워 하는 문제가 있다니요😓
왜 이게 안될까? 하는 고민이 부족하다고 느끼고 있는데 준형님 글 보면서 나도 이렇게 고민해봐야지~ 하고 선한영향력(feat. JY) 받아가요 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ👍🏻

1개의 답글