One to Many Relationship

ORM์—์„œ์˜ or ์—ฐ์‚ฐ

Q๋ฅผ ํ™œ์šฉํ•œ๋‹ค

In [1]: from django.db.models import Q                  

In [2]: User.objects.filter(Q(age=30) | Q(last_name='๊น€'
   ...: )).count()                                      
Out[2]: 25

QuerySet

In [4]: User.objects.all()                              
Out[4]: <QuerySet [<User: User object (1)>, <User: User object (2)>, <User: User object (3)>, <User: User object (4)>, <User: User object (5)>, <User: User object (6)>, <User: User object (7)>, <User: User object (8)>, <User: User object (9)>, <User: User object (10)>, <User: User object (11)>, <User: User object (12)>, <User: User object (13)>, <User: User object (14)>, <User: User object (15)>, <User: User object (16)>, <User: User object (17)>, <User: User object (18)>, <User: User object (19)>, <User: User object (20)>, '...(remaining elements truncated)...']>

In [5]: type(User.objects.all())                        
Out[5]: django.db.models.query.QuerySet
  • Query (method) ํ•  ๋•Œ

  • ์กฐํšŒ (loop up)

    • get()

      • Returns the object matching the given lookup parameters

      • return์˜ค์ง ํ•˜๋‚˜ or Error ๋ฐœ์ƒ

      • ex) RUD (Read / Update / Delete)

    • filter()

      • Returns a new QuerySet containing objects that match the given lookup parameters.

        • (์—†์œผ๋ฉด ๋น„์–ด์žˆ๋Š” QuerySet)

      • ex) Search

      • AND

        • method chaining

        • filter. filter. ....

      • OR

        • Q Object

        • (Q ( ) | Q ( ) )

      • LIKE

        • ex) age__lte

        • ex) name__startswith

    • exclude()

      • Returns a new QuerySet containing objects that do not match the given lookup parameters.

ex)

In [1]: Article.objects.all()                                                                                      
Out[1]: SELECT "articles_article"."id",
       "articles_article"."title",
       "articles_article"."content"
  FROM "articles_article"
 LIMIT 21

Execution time: 0.000412s [Database: default]
<QuerySet []>

In [2]: Article.objects.create(title='1st post',content='haha')                                                    
INSERT INTO "articles_article" ("title", "content")
VALUES ('1st post', 'haha')

Execution time: 0.024278s [Database: default]
Out[2]: <Article: #1 (1st post - haha)>

Aggregation

  • ๊ฐœ๋ณ„ Object CRUD๋ฅผ django query

  • QuerySet์„ ํ•ฉ์ณ์ง„ ๊ฒฐ๊ณผ๋กœ ๋ณด๊ณ ์‹ถ์„ ๋•Œ ์‚ฌ์šฉ

    • ex)

      # Average price across all books.
      >>> from django.db.models import Avg
      >>> Book.objects.all().aggregate(Avg('price'))
      {'price__avg': 34.35}
      
      # Max price across all books.
      >>> from django.db.models import Max
      >>> Book.objects.all().aggregate(Max('price'))
      {'price__max': Decimal('81.20')}
      
      # Difference between the highest priced book and the average price of all books.
      >>> from django.db.models import FloatField
      >>> Book.objects.aggregate(
      ...     price_diff=Max('price', output_field=FloatField()) - Avg('price'))
      {'price_diff': 46.85}
      
      # All the following queries involve traversing the Book<->Publisher
      # foreign key relationship backwards.
      
      # Each publisher, each with a count of books as a "num_books" attribute.
      >>> from django.db.models import Count
      >>> pubs = Publisher.objects.annotate(num_books=Count('book'))
      >>> pubs
      <QuerySet [<Publisher: BaloneyPress>, <Publisher: SalamiPress>, ...]>
      >>> pubs[0].num_books
      73
      
      # Each publisher, with a separate count of books with a rating above and below 5
      >>> from django.db.models import Q
      >>> above_5 = Count('book', filter=Q(book__rating__gt=5))
      >>> below_5 = Count('book', filter=Q(book__rating__lte=5))
      >>> pubs = Publisher.objects.annotate(below_5=below_5).annotate(above_5=above_5)
      >>> pubs[0].above_5
      23
      >>> pubs[0].below_5
      12
      
      # The top 5 publishers, in order by number of books.
      >>> pubs = Publisher.objects.annotate(num_books=Count('book')).order_by('-num_books')[:5]
      >>> pubs[0].num_books
      1323

Annotate

ex) COUNT( )

SELECT country, COUNT(country) FROM countries;
  • DB์—์„œ ๊ฐ€์ ธ์˜จ ๊ฒฐ๊ณผ์—์„œ ํ•„์š”ํ•œ ๊ฐ’์„ ์กฐ์ž‘ํ•˜์—ฌ ๊ฐ€์ ธ์˜ด

1:N (one to many)

  • 1 has many N

  • N must belong to 1

    • (๊ทธ๋ž˜์„œ cascading์ด ๊ฐ€๋Šฅํ•˜๋‹ค)

    • ex)

      • Article has many Comments

    • Comment belongs to Article

  • Foreign Key๋Š” N ์—๊ฒŒ ์ค€๋‹ค

from django.db import models

# 1
class Reporter(models.Model):
    username = models.CharField(max_length=10)

# N
class Article(models.Model):
    title = models.CharField(max_length=10)
    content = models.TextField()
    reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE)
    #reporter๋Š” ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  ์ด๋ฆ„์ธ๋ฐ, ๋ชจ๋ธ๋ช…๊ณผ ๊ฐ™๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด best practice!
  • articles_article table์— reporter_id column์ด ์ถ”๊ฐ€๋œ๋‹ค

  • reporter์˜ ๊ฒฝ์šฐ article_set์œผ๋กœ N๊ฐœ (QuerySet)๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

  • article์˜ ๊ฒฝ์šฐ reporter`๋กœ 1์— ํ•ด๋‹นํ•˜๋Š” object๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค

  • on_delete : ์ฐธ์กฐ ๋Œ€์ƒ์ด ์‚ญ์ œ๋˜๋Š” ๊ฒฝ์šฐ

    • CASCADE

      • ํ•ด๋‹น ๊ฐ์ฒด(reporter)๊ฐ€ ์‚ญ์ œ ๋˜์—ˆ์„ ๋•Œ ์ฐธ์กฐํ•˜๋Š” ๊ฐ์ฒด๋„(article) ๋ชจ๋‘ ์‚ญ์ œ

    • PROTECT

      • ์ฐธ์กฐํ•˜๋Š” ๊ฐ์ฒด(article)๊ฐ€ ์กด์žฌํ•˜๋ฉด ์‚ญ์ œ ๊ธˆ์ง€

    • SET_NULL

      • NULL ๊ฐ’์œผ๋กœ ์น˜ํ™˜

      • NOT NULL option์ด ์žˆ๋Š” ๊ฒฝ์šฐ ํ™œ์šฉ ํ•  ์ˆ˜ ์—†์Œ

    • SET_DEFAULT

      • default๊ฐ’(article)์„ ์ฐธ์กฐํ•˜๊ฒŒ ํ•จ

-- sql

CREATE TABLE "aricles_article" (
 "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    "title" VARCHAR(10) NOT NULL,
    "content" TEXT NOT NULL,
    "reporter_id" INTEGER NOT NULL REFERENCES "artices_reporter" ("id") DEFERRABLE INITIALLY DEFERRED
);

๊ธฐ๋ณธ ์ฟผ๋ฆฌ

1. ์ค€๋น„

Reporter.objects.create(username='์š”ํŠธ๋งจ')
Reporter.objects.create(username='chloe')
Reporter.objects.create(username='camila')
Reporter.objects.create(username='bella')

r1 = Reporter.objects.get(pk=1)

2. article ์ƒ์„ฑ (N)

In [3]: article = Article()                             

In [4]: article.title = '์ œ๋ชฉ1'                         

In [5]: article.content = '๋‚ด์šฉ1'                       

In [6]: r1 = Reporter.objects.get(pk=1)                 

In [7]: article.reporter = r1                           
  # reporter_id๋Š” ์ˆซ์ž(INTEGER)๋ฅผ ์ €์žฅ
  # article.reporter_id = 1
  
In [8]: article.save()                                  

In [9]: article                                         
Out[9]: <Article: Article object (1)>

In [10]: article.reporter                               
Out[10]: <Reporter: Reporter object (1)>

In [11]: article.reporter.username                      
Out[11]: '์š”ํŠธ๋งจ'
a2 = Article.objects.create(title='์ œ๋ชฉ2', conent='๋‚ด์šฉ2', reporter=r1)

3. 1:N ๊ด€๊ณ„ ํ™œ์šฉ

# 1. ๊ธ€์˜ ์ž‘์„ฑ์ž
a2 = Article.objects.get(pk=2)
a2.reporter

# 2. ๊ธ€์˜ ์ž‘์„ฑ์ž์˜ username
a2.reporter.username

# 3. ๊ธ€์˜ ์ž‘์„ฑ์ž์˜ id
a2.reporter.id
a2.reporter_id

# 4. ์ž‘์„ฑ์ž(1)์˜ ๊ธ€
r1 = Reporter.objects.get(pk=1)
r1.article_set.all()
# -> <QuerySet [<Article: Article object(2)>]

# 5. 1๋ฒˆ ๊ธ€์˜ ์ž‘์„ฑ์ž๊ฐ€ ์“ด ๋ชจ๋“  ๊ธ€
a1 = Article.objects.get(pk=1)
a1.reporter.article_set.all()
  • article_set

Comment exercise

In [3]: comment = Comment()                                                                                        

In [4]: comment                                                                                                    
Out[4]: <Comment: Comment #None for Post #None>

In [5]: comment.content = 'Comment for Post #1'                                                                    

In [6]: comment                                                                                                    
Out[6]: <Comment: Comment #None for Post #None>

In [7]: comment.article_id                                                                                         

In [8]: comment.article_id =1                                                                                      

In [9]: comment                                                                                                    
Out[9]: <Comment: Comment #None for Post #1>

์ฃผ์˜ ํ•  ์ 

In [11]: comment.article_pk                                                                                        
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-11-e72fca1b8134> in <module>
----> 1 comment.article_pk

AttributeError: 'Comment' object has no attribute 'article_pk'
  • ๋ฌด์กฐ๊ฑด article_id์ด๋‹ค

    • ๋ณ„๋ช… ์‚ฌ์šฉ ๋ถˆ๊ฐ€!

In [12]: comment.article_id                                                                                        
Out[12]: 1

In [13]: comment.article                                                                                           
SELECT "articles_article"."id",
       "articles_article"."title",
       "articles_article"."content"
  FROM "articles_article"
 WHERE "articles_article"."id" = 1
 LIMIT 21

Execution time: 0.000417s [Database: default]
Out[13]: <Article: #1 (1st post - haha)>

In [16]: article = Article.objects.first()                                                                         
SELECT "articles_article"."id",
       "articles_article"."title",
       "articles_article"."content"
  FROM "articles_article"
 ORDER BY "articles_article"."id" ASC
 LIMIT 1

Execution time: 0.000373s [Database: default]

In [17]: article.comment_set                                                                                       
Out[17]: <django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager at 0x7f9603eb1ac8>

In [18]: article.comment_set.all()                                                                                 
Out[18]: SELECT "articles_comment"."id",
       "articles_comment"."content",
       "articles_comment"."article_id"
  FROM "articles_comment"
 WHERE "articles_comment"."article_id" = 1
 LIMIT 21

Execution time: 0.000308s [Database: default]
<QuerySet [<Comment: Comment #1 for Post #1>]>
  • default related_name์ธ.comment_set์œผ๋กœ ํ˜ธ์ถœํ•˜์ง€ ์•Š๊ธฐ ์œ„ํ•ด models.py ์ˆ˜์ •

    class Comment(models.Model):
        content = models.TextField()
        article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='comments')
                                    # model ์ค‘์— Article์„ ๊ฐ€๋ฆฌํ‚ค๊ณ  ์žˆ๋‹ค
        
        def __str__(self):
            return f'Comment #{self.pk} for Post #{self.article_id}'
    • related_name= 'comments' ๋กœ ์„ค์ •ํ•จ

      • ๋‹จ, ์—ฌ๊ธฐ์„œ๋Š” option์„ ๋ฐ”๊พผ ๊ฒƒ ์ด๋ฏ€๋กœ migration ์•ˆํ•ด๋„ ๋จ!

  • ์ˆ˜์ • ํ›„ comments๋กœ ํ˜ธ์ถœ ๊ฐ€๋Šฅํ•ด์ง

    In [2]: comment = Comment.objects.first()                                                                          
    SELECT "articles_comment"."id",
           "articles_comment"."content",
           "articles_comment"."article_id"
      FROM "articles_comment"
     ORDER BY "articles_comment"."id" ASC
     LIMIT 1
    
    Execution time: 0.000873s [Database: default]
    
    In [3]: article = comment.article                                                                                  
    SELECT "articles_article"."id",
           "articles_article"."title",
           "articles_article"."content"
      FROM "articles_article"
     WHERE "articles_article"."id" = 1
     LIMIT 21
    
    Execution time: 0.000353s [Database: default]
    
    In [4]: article                                                                                                    
    Out[4]: <Article: #1 (1st post - haha)>
    
    In [5]: comment                                                                                                    
    Out[5]: <Comment: Comment #1 for Post #1>
    
    In [6]: comment.article                                                                                            
    Out[6]: <Article: #1 (1st post - haha)>
    
    In [7]: article.comments                                                                                           
    Out[7]: <django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager at 0x7f4a84996320>
    
    In [8]: article.comments.all()                                                                                     
    Out[8]: SELECT "articles_comment"."id",
           "articles_comment"."content",
           "articles_comment"."article_id"
      FROM "articles_comment"
     WHERE "articles_comment"."article_id" = 1
     LIMIT 21
    
    Execution time: 0.000374s [Database: default]
    <QuerySet [<Comment: Comment #1 for Post #1>]>

Data Seeding

CSV -> DB

sqlite> .mode csv

sqlite> .import users.csv users_user

sqlite> select count(*) from users_user;
100
sqlite> .schema users_user
CREATE TABLE IF NOT EXISTS "users_user" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "first_name" varchar(20) NOT NULL, "last_name" varchar(20) NOT NULL, "age" integer NOT NULL, "country" varchar(20) NOT NULL, "phone" varchar(20) NOT NULL, "balance" integer NOT NULL);
sqlite> .headers on
sqlite> .schema users_user
CREATE TABLE IF NOT EXISTS "users_user" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "first_name" varchar(20) NOT NULL, "last_name" varchar(20) NOT NULL, "age" integer NOT NULL, "country" varchar(20) NOT NULL, "phone" varchar(20) NOT NULL, "balance" integer NOT NULL);
sqlite> select*from users_user limit 10;
id,first_name,last_name,age,country,phone,balance
1,"์ •ํ˜ธ","์œ ",40,"์ „๋ผ๋ถ๋„",016-7280-2855,370
2,"๊ฒฝํฌ","์ด",36,"๊ฒฝ์ƒ๋‚จ๋„",011-9854-5133,5900
3,"์ •์ž","๊ตฌ",37,"์ „๋ผ๋‚จ๋„",011-4177-8170,3100
4,"๋ฏธ๊ฒฝ","์žฅ",40,"์ถฉ์ฒญ๋‚จ๋„",011-9079-4419,250000
5,"์˜ํ™˜","์ฐจ",30,"์ถฉ์ฒญ๋ถ๋„",011-2921-4284,220
6,"์„œ์ค€","์ด",26,"์ถฉ์ฒญ๋ถ๋„",02-8601-7361,530
7,"์ฃผ์›","๋ฏผ",18,"๊ฒฝ๊ธฐ๋„",011-2525-1976,390
8,"์˜ˆ์ง„","๊น€",33,"์ถฉ์ฒญ๋ถ๋„",010-5123-9107,3700
9,"์„œํ˜„","๊น€",23,"์ œ์ฃผํŠน๋ณ„์ž์น˜๋„",016-6839-1106,43000
10,"์„œ์œค","์˜ค",22,"์ถฉ์ฒญ๋‚จ๋„",011-9693-6452,49000

Data Integrity

  • the maintenance of, and the assurance of the accuracy and consistency of data over its entire life-cycle

  • a critical aspect to the design, implementation and usage of any system which stores, processes, or retrieves data

Entity Integrity

  • defines each row to be unique within its table.

    • No two rows can be the same.

  • To achieve this, a primary key can be defined.

    • The primary key field contains a unique identifier โ€“ no two rows can contain the same unique identifier.

Referential Integrity

  • concerned with relationships.

    • When two or more tables have a relationship, we have to ensure that the foreign key value matches the primary key value at all times.

    • We donโ€™t want to have a situation where a foreign key value has no matching primary key value in the primary table.

    • This would result in an orphaned record.

So referential integrity will prevent users from:

  • Adding records to a related table if there is no associated record in the primary table.

  • Changing values in a primary table that result in orphaned records in a related table.

  • Deleting records from a primary table if there are matching related records.

Domain Integrity

  • concerns the validity of entries for a given column.

  • Selecting the appropriate data type for a column is the first step in maintaining domain integrity.

  • Other steps could include, setting up appropriate constraints and rules to define the data format and/or restricting the range of possible values.

User-Defined Integrity

  • allows the user to apply business rules to the database that arenโ€™t covered by any of the other three data integrity types.

Django settings.py

https://github.com/django/django/blob/master/django/conf/global_settings.py

์ฐธ๊ณ ํ•˜๊ธฐ

Update, delete๋Š” ๊ฐœ๋ณ„ ๊ฐ์ฒด์™€ ์ฟผ๋ฆฌ์…‹์— ์ ์šฉ๊ฐ€๋Šฅ

Excercises

์ค€๋น„

onetomany app ์ƒ์„ฑ

# models.py
class User(models.Model):
    username = models.CharField(max_length=10)
    
class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    user = models.ForeignKey(User, on_delete=models.CASCADE)

class Comment(models.Model):
    content = models.TextField()
    article = models.ForeignKey(Article, on_delete=models.CASCADE)
    user = models.ForeignKey(User, on_delete=models.CASCADE)
from onetomany.models import User, Article, Comment

# objects
u1 = User.objects.create(username='Kim')
u2 = User.objects.create(username='Lee')

a1 = Article.objects.create(title='1๊ธ€', user=u1)
a2 = Article.objects.create(title='2๊ธ€', user=u2)
a3 = Article.objects.create(title='3๊ธ€', user=u2)
a4 = Article.objects.create(title='4๊ธ€', user=u2)

c1 = Comment.objects.create(content='1๊ธ€1๋Œ“', article=a1, user=u2)
c2 = Comment.objects.create(content='1๊ธ€2๋Œ“', article=a1, user=u2)
c3 = Comment.objects.create(content='2๊ธ€1๋Œ“', article=a2, user=u1)
c4 = Comment.objects.create(content='4๊ธ€1๋Œ“', article=a4, user=u1)
c5 = Comment.objects.create(content='3๊ธ€1๋Œ“', article=a3, user=u2)
c6 = Comment.objects.create(content='3๊ธ€2๋Œ“', article=a3, user=u1)

๋ฌธ์ œ

  1. 1๋ฒˆ ์œ ์ €๊ฐ€ ์ž‘์„ฑํ•œ ๊ธ€๋“ค

    u1.article_set.all()
  2. 2๋ฒˆ ์œ ์ €๊ฐ€ ์ž‘์„ฑํ•œ ๋Œ“๊ธ€์˜ ๋‚ด์šฉ์„ ๋ชจ๋‘ ์ถœ๋ ฅ

    for comment in u2.comment_set.all():
        print(comment.content)
  3. 3๋ฒˆ ๊ธ€์˜ ์ž‘์„ฑ๋œ ๋Œ“๊ธ€์˜ ๋‚ด์šฉ์„ ๋ชจ๋‘ ์ถœ๋ ฅ

    for comment in a3.comment_set.all():
        print(comment.content)
    <div data-gb-custom-block data-tag="for">
    
       {{ comment.content }}
    

```

  1. 1๊ธ€์ด๋ผ๋Š” ์ œ๋ชฉ์ธ ๊ฒŒ์‹œ๊ธ€๋“ค

    Article.objects.filter(title='1๊ธ€')
  2. ๊ธ€์ด๋ผ๋Š” ๋‹จ์–ด๊ฐ€ ๋“ค์–ด๊ฐ„ ๊ฒŒ์‹œ๊ธ€๋“ค

    Article.objects.filter(title__contains='๊ธ€')
  3. ๋Œ“๊ธ€(N)๋“ค ์ค‘์— ํ•ด๋‹น๋˜๋Š” ๊ธ€(1)์˜ ์ œ๋ชฉ์ด 1๊ธ€์ธ ๊ฒƒ

    Comment.objects.filter(article__title='1๊ธ€')
    print(Comment.objects.filter(article__title='1๊ธ€').query)
    • 1:N ๊ด€๊ณ„์—์„œ 1์˜ ์—ด์— ๋”ฐ๋ผ์„œ, ํ•„ํ„ฐ๋ง

      SELECT "onetomany_comment"."id", "onetomany_comment"."content", "onetomany_comment"."article_id", "onetomany_comment"."user_id" FROM "onetomany_comment" INNER JOIN "onetomany_article" ON ("onetomany_comment"."article_id" = "onetomany_article"."id") WHERE "onetomany_article"."title" = 1๊ธ€

+

Django ์—๊ฒŒ ๋งก๊ฒจ์„œ sqlite ์—ด๊ธฐ

python manage.py dbshell

Shell ์—์„œ ORM query ๋ฐ”๋กœ ๋ณด์—ฌ์ฃผ๋Š” option

python manage.py shell_plus --print-sql

Last updated

Was this helpful?