코드로 우주평화

'ManyToManyField' 또는 '중간테이블'로 데이터 가져오는 방법 본문

나는 이렇게 학습한다/Framework

'ManyToManyField' 또는 '중간테이블'로 데이터 가져오는 방법

daco2020 2021. 11. 19. 14:13
반응형

django(장고)에서 만들려는 데이터베이스 테이블의 관계가 n:n 관계, 즉 ManyToMany 관계의 테이블을 생성하는 방법은 크게 3가지가 있다.

 

1번 >>> 중간 테이블을 만들어 중간테이블이 각각 다른 테이블을 FK로 가져오는 방법

2번 >>> models.py 작성시에 다대다 관계의 테이블 중 하나의 테이블에 ManyToManyField를 사용하는 방법(이 방법은 장고가 자동으로 중간테이블을 생성해준다)

3번 >>> 1번, 2번 모두 사용하는 방법으로 중간 테이블을 만들고, ManyToManyField도 사용하면서 중간 테이블 지정을 미리 만들어둔 중간테이블로 지정하는 방법

 

*세 가지 방법에 대한 차이점(아래 예제를 보고 오면 이해가 될거에요!)

더보기

차이점1.

>>> 1번의 경우 A에서 한 번, A_B에서 한 번, A_B에서 B로 한 번, 총 3번의 조회 과정을 거쳐야 한다(B테이블에서 A테이블의 값을 가져오는 방법도 동일)

 

차이점2.

>>> 2번의 경우 중간테이블을 거치지 않으므로 바로 정참조 또는 역참조를 활용하여 2번의 조회만으로 값을 가져올 수 있다. (명령어도 중간테이블만 사용하는 경우보다 단순한 편)

 

차이점3.

>>> 3번의 경우 2번과 동일하게 중간테이블을 거치지 않고 데이터를 조회할 수 있다. 3번은 중간테이블에 FK뿐만 아니라 다른 값도 넣고 싶을 때 사용한다. 단, 값을 넣는 방법은 2번과 달리 1번처럼 중간테이블에 직접 넣어주어야 한다.

 

 


 

예제의 목표

A 테이블과 B 테이블은 다대다 관계이다.
중간테이블 이름은 A_B 이다.
A 테이블 id 1번과 연결된 B 테이블의 객체를 모두 가져와보자.

 

1번 중간테이블만 사용했을 경우 값을 가져오는 방법

중간테이블은 각각 다른 테이블을 정참조하므로 중간테이블의 인스턴스를 구해 정참조식으로 가져올 수 있다.

예시)

 

 

1-1.  A라는 클래스(=테이블)에서 인스턴스(객체)를 하나 가져와 A1 라는 변수에 저장한다.

A1 = A.objects.get(id=1)

#get(id=1) >>> 1번 아이디를 쿼리셋이 아닌 객체로 가져온다

 

1-2.  A_B 중간테이블에서 A테이블 id 1번을 참조하고 있는 인스턴스들을 쿼리셋(여러개)으로 가져와서 A1_B 라는 변수에 저장한다

A1_B = A_B.objects.filter(a=A1)

#filter(a=A1) >>> A테이블에서 가져온 객체와 A_B중간테이블에 일치하는 값(여기서는 id)을 쿼리셋 형태로 가져온다

#or

A1_B = A1.a_b_set.all()

#A입장에서 A_B는 중간테이블이므로 역참조 '클래스명_set' 으로도 객체들을 가져올 수 있다
#"_set" 은 역참조 테이블을 조회하는 마법주문같은거라고 보면 된다.
#만약 related_name="a" 같이 ManyToManyField	안에 지정했다면 역참조라도 굳이 _set을 쓰지 않아도 된다!

 

 

1-3 A1_B 는 쿼리셋(여러개) 형태이므로 for문이나 인덱스를 통해 인스턴스(객체) 한 개로 만들고 B테이블 정참조 식으로 값을 가져온다. (예시는 인덱스를 사용)

A1_B[0].b.name   ['name'은 b테이블 있는 컬럼명이라고 하자]
>>> '변덕순'

#위에서 쿼리셋 형태로 가져온 객체들 중에 첫번째 객체를 이용하여 B테이블에 있는 name 컬럼의 값을 가져온다

 


 

2번 ManyToManyField를 사용 했을 경우 값을 가져오는 방법(중간테이블 지정X)

A테이블이 ManyToManyField를 이용해 B테이블을 참조한다고 해보자. A가 B를 참조하기 때문에 A입장에서 B는 정참조, 그 반대는 역참조이다. 이번에는 A와 B의 관점에서 함께 비교해보겠다.

 

2-1. A 또는 B 테이블에서 인스턴스(객체)를 하나 가져와 A1 또는 B1 이라는 변수에 저장한다.

A1 = A.objects.get(id=1)

#A테이블에 있는 Id 1번의 객체를 가져옴

B1 = B.objects.get(id=1)

#B테이블에 있는 Id 1번의 객체를 가져옴

 

2-2. ManyToManyField를 사용할 경우에는 A 기준 정참조, B 기준 역참조로 관계되는 값을 쿼리셋으로 가져온다.

(다대다 관계이므로 하나가 여러개로 묶여있기 때문에 모두 가져오는 것이다)

A1_B = A1.b.all()

#A테이블의 1번 아이디와 관계있는 B테이블의 모든 객체를 가져온다 (정참조)

B1_A = B1.a_set.all()

#B테이블의 1번 아이디와 관계있는 A테이블의 모든 객체를 가져온다 (역참조)
#"_set" 은 역참조 테이블을 조회하는 마법주문같은거라고 보면 된다.
#만약 related_name="a" 문구를 ManyToManyField 안에 지정했다면 역참조라도 굳이 _set을 쓰지 않아도 된다!

 

2-3. B테이블에서 관계되는 값을 모두 가져왔으므로 다시 정참조나 역참조 할 필요 없이 바로 값을 가져올 수 있다

A1_B[0].name ['name'은 b테이블 있는 컬럼명이라고 하자]
>>> '변덕순'

#이미 b테이블에 있는 객체를 가져왔으므로 바로 값을 가져올 수 있다.

B1_A[0].number ['number'는 a테이블 있는 컬럼명이라고 하자]
>>> '1번'

#이미 a테이블에 있는 객체를 가져왔으므로 바로 값을 가져올 수 있다.

 

*ManyToManyField를 사용할 때 값을 추가하는 방법

더보기

정참조하는 테이블에서의 값추가, 제거

a = Movie.objects.get(id = 1)
b = Movie.objects.get(id = 2)
# 추가
Actor.objects.get(id = 1).movie.add(a,b)
# 제거
Actor.objects.get(id = 1).movie.remove(b)

역참조하는 테이블에서의 값추가, 제거

a = Actor.objects.get(id= 1)
b = Actor.objects.get(id= 2)
# 추가
Movie.objects.get(id = 1).actor_set.add(a,b)
# 제거
Movie.objects.get(id = 1).actor_set.remove(a)

전체 제거

# 정참조의 경우
Actors.objects.get(id = 1).movie.clear()
# 역참조의 경우
Movie.objects.get(id = 1).actor_set.clear()

 

출처 : https://velog.io/@minhyuk_ko/Django-ManyToManyField-%EA%B3%A0%EC%B0%B0

 


 

3번 ManyToManyField를 사용 했을 경우 값을 가져오는 방법(중간테이블 지정O)

ManyToManyField를 사용하면서도 중간테이블을 만들어 사용할 수 있다. 대신 이 경우에는 중간테이블 클래스도 작성해 주어야 하고 ManyToManyField 속성에도 [through="중간테이블(클래스명)"] 을 넣어주어야 한다. 아래 models.py 코드 참고

# models.py

# A 테이블
Class A(models.Model):
  number   = models.CharField(max_length=45)
  b        = models.ManyToManyField("B", through="A_B") # through  ="중간테이블(클래스명)" -> 중간테이블을 지정하는 것
  
  class Meta:
    db_table = 'a'

# B 테이블
class B(models.Model):
  name  = models.CharField(max_length=45)
  
  class Meta:
    db_table = 'b'
  
# A_B 중간테이블
class A_B(models.Model):
  A  = models.ForeignKey('A', on_delete=models.CASCADE)
  B  = models.ForeignKey('B', on_delete=models.CASCADE)
  C  = models.CharField(max_length=45)
  
  class Meta:
    db_table = 'a_b'

굳이 안만들어도 되는 중간테이블을 만드는 이유는 중간테이블이 단순히 다대다 관계의 테이블들을 연결시켜주는 기능 뿐만 아니라 중간테이블 자체에서도 새로운 정보(위의 코드에서는 'C')를 넣고 싶을 때 사용하게 된다. ManyToManyField를 통해 장고가 생성해주는 중간테이블은 위 코드처럼 새로운 C의 컬럼을 생성할 수 없기 때문에 우리가 직접 테이블을 만들어 주는 것이다. 

 

*3번에서 값을 불러오는 방법은 2번과 동일하기 때문에 해당 과정은 생략한다.

*3번의 경우, ManyToManyField를 사용하지만 중간테이블을 직접 만들고 C라는 컬럼이 추가되었기 때문에 값 입력하는 방법은 2번과 달리 중간테이블에 직접 값을 넣어야 한다.

 

 

자, 그렇다면 이 세 가지 방법에 대한 차이점은 무엇일까? 맨위에 정리한 차이점을 다시 보러가자~! 이제 이해가 쏙쏙 될 것 이다.

반응형