- Queryset API?
- Queryset에서 and, or 조건으로 데이터 조회하는 방법.
- Queryset으로 join 기능 구현 방법.
- Queryset 다중 join 기능 구현 방법.
- 하나의 table에서 2개 이상의 FK가 타 table의 PK를 동시에 relation해야 할 경우 django 에서 생기는 문제 조치 방법.
models.py를 통해 class를 정의하고 migration을 통해 table을 생성을 할 수 있다고 배웠다. 그러면 table에 대한 CRUD를 하려면 어떻게 해야 할까?
이를 위해 Django는 Queryset API를 제공한다. 다음 코드를 보자.class Users(models.Model): name = models.CharField(max_length=50) phone_number = models.CharField(max_length=20) email = models.CharField(max_length=50) password = models.CharField(max_length=300) class Meta: db_table = 'users'
위는 models.py에 정의된 Users class 이다. 이는 migrate시 database에 'users' table로 생성될 것이다. 그리고 Users class는 아래와 같이 사용할 수도 있다.
# 회원가입시 중복되는 전화번호, 사용자 이름, 이메일이 있으면 에러 메시지 응답. if Users.objects.filter(name=user_account['name']).exists(): return JsonResponse({"message":"same name is already exist."}, status=400) if Users.objects.filter(name=user_account['phone_number']).exists(): return JsonResponse({"message":"same phone_number is already exist."}, status=400) if Users.objects.filter(name=user_account['email']).exists(): return JsonResponse({"message":"same email is already exist."}, status=400) Users( name = user_account['name'], email = user_account['email'], phone_number = user_account['phone_number'], password = data['password'], ).save()
위는 Users class를 이용해서 users table의 데이터를 조회하고 추가도 하는 로직이다.
models.py에 정의한 class로 table에 대한 제어가 가능한데 이것이 Queryset API 기능이다.
select * from users where (name=value1 or email=value1 or phone_number=value1) and password='password';
위는 Table에서 and와 or 조건을 이용하여 해당 row를 조회하는 sql 이다.
이를 Queryset으로 구현해보면 아래와 같다.from django.db.models import Q rows = Users.objects.filter(Q(name=login_info['account']) | Q(email=login_info['account']) | Q(phone_number=login_info['account']), password=login_info['password'])
'filter' 함수는 sql의 select와 같은 조회 기능이며 매개변수로 *args 형식을 받는다.
',' 기준으로 입력되는 값들이 자동적으로 and 조건이 된다.
or 조건의 경우 django.db.models.Q 기능을 이용, 위 코드처럼 Q()에 하나의 조건을 넣고 여러 Q를 '|' 로 묶으면 된다.
Queryset는 SQL의 join 기능도 제공한다. 먼저 example sql을 보자.
select posts.name, users.name from posts where posts.id=1 join users ON posts.user_id = users.id
posts table에서 'id=1' 인 post의 name을 가져온다. 이 때 post의 user_id(FK)에 매칭되는 users가 있다면 해당 user의 name을 get, 둘을 join한 조회 결과를 제공한다.
이제 Queryset 으로 구현한 code를 보자.rows = Posts.objects.select_related('user').filter(id=1) for row in rows: print(row.name) # post 이름 print(row.user.name) # 해당 post를 작성한 user의 이름
select_related()이 sql의 'join'의 역할을 하는 함수이고 argument는 ForeignKey(여기서는 'user_id') column 명이 들어간다.
[참고할 사항]
위에 select_related시 'user'를 인자로 넣었는데 실제 column명은 'user_id' 이다. 혼란을 발생시키는 요인이므로 어떤 이유인지에 대해 조금 설명하려 한다.
위 사진에서 Posts class를 보면 'user'가 FK로 지정되고 있다. 이번엔 database를 보자.
posts table에는 'user_id' 로 들어가 있다. 왜 틀린걸까?이유는 django에선 FK로 생성되는 column은 migration시 column + '_id'를 자동으로 붙여주기 때문이다. Queryset은 models에 선언된 형식을 따라야 한다.
class ShowAllPosts(View): def get(self, request): posts = [] entries = Posts.objects.select_related('content').select_related('content__user').all() for row in entries: posts.append({"article":row.content.article, "image_url":row.image_url, "created_at":row.content.created_at , "user":row.content.user.name}) return JsonResponse({"posts":posts}, status=200)
위 code를 보면 select_related 를 두번 실행하고 있는데 'content'에 대한 join을 실행 후 join 결과물에서 content가 relation하고 있는 user에 대해 한번 더 join을 하고 있다.
join 결과물을 다시 join 하는 경우부터 '__' keyword를 이용, relation 관계인 두 key를 연결한다.[추가 정보]
최근에 멘토님으로부터 위와 같은 경우 select_related를 한번만 호출하는 것도 가능하다는 것을 배웠다.Posts.objects.select_related('content', 'content__user').all()
sql로 실현은 가능하나 django는 경고 문구를 발생시키면서 처리하지 않는다. 아래 코드를 보자.
class Follows(models.Model): followed_user = models.ForeignKey(Users, related_name='related_followed_user', on_delete=models.CASCADE) following_user = models.ForeignKey(Users, related_name='related_following_user', on_delete=models.CASCADE)
위 code는 2개의 column이 Users의 PK를 relation 하도록 시도하고 있다. argument로 'related_name' 이라는 것이 들어가 있는데 만약 이 argument를 넣어주지 않으면 django는 Error 처리한다.
여러개의 FK가 하나의 PK를 같이 바라봐야 하는 경우 'related_name'을 적절히 사용하자.
related_name 관련 django document