DEV Community

Cover image for One-To-Many Database Relationships Complete How To
brandon_wallace
brandon_wallace

Posted on Edited on

One-To-Many Database Relationships Complete How To

INTRODUCTION

In this article I will show you how to set up a one-to-many database relationship, how you would know when you would need to use one, as well as some example queries.

We will use:

  • Python 3.9+
  • PostgreSQL (database)
  • Flask (framework)
  • Flask-SQLAlchemy (object-relation mapper)
  • Pipenv (virtual environment)

Once your C.R.U.D. ( Create Read Update Delete ) applications start getting more complex, you will find that you need to link tables together within the database. In databases it is very common task to create relationships between tables. A one-to-many relationship is a method that will allow you to link a parent to a child table in a database. A one-to-many relationship is created by adding a foreign key to the child table.
one-to-many-erd-generic.png

Diagram created on diagrams.net
PK = primary key
FK = foreign key

WHEN WOULD I USE A ONE-TO-MANY RELATIONSHIP?

Let me present to you a real-world scenario, let’s say you have an online store and you need to keep track of your customers and the orders they make. In your database you would create a table for customers and separate table for the orders. How would you know which order belongs to which customer? The way to do that is to set up a one-to-many relationship. This will allow you to take one customer and query all the information connected to the orders the customer made.

one-to-many-erd.png

I will show you an easy way to know when you need to set up a one-to-many database relationship as opposed to a one-to-one or a many-to-many database relationship. Going back to the example of the online store, you have two tables, a customer table and an order table. You would ask yourself two questions about the tables.

Example 1:

Q: Does a customer have many orders?
A: Yes, a customer can have many orders.

Q: Does an order have many customers?
A: No, only one person can create an order.

We have one customer and many orders but it does not work the other way around. One order does not have many customers. We can clearly see that example 1 is a great candidate for a one-to-many database relationship.

Let’s look at another example.

Example 2:

In another imaginary database we have a table for students and a table for classes. So we form the questions like this.

Q: Does a student have many classes?
A: Yes, a student can have many classes.

Q: Does a class have many students?
A: Yes, a class can have many students.

Here we have many students and many classes because of that, example 2 is not suitable for a one-to-many relationship.

How To Set Up The Project

Install PostgreSQL and Pip. We use Pip to install Pipenv.

$ sudo apt install postgresql libpq-dev python3-pip
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Enable and start PostgreSQL.

$ sudo systemctl enable postgresql

$ sudo systemctl start postgresql
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Check to see if PostgreSQL is running.

$ sudo systemctl status postgresql
● postgresql.service - PostgreSQL RDBMS
   Loaded: loaded (/lib/systemd/system/postgresql.service; enabled; vendor preset: enabled)
   Active: active (exited) since Mon 2021-02-01 10:09:46 EST; 2h 56min ago
   Process: 920 ExecStart=/bin/true (code=exited, status=0/SUCCESS)
   Main PID: 920 (code=exited, status=0/SUCCESS)
[...]
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Log in as the postgres user and then the interactive PostgreSQL terminal 'psql'.

$ sudo su – postgres

postgres@laptop:~$ psql

postgres=#
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Check the PostgreSQL version.

postgres=# SELECT version();
                                                     version                                                      
------------------------------------------------------------------
 PostgreSQL 14.1 (Debian 14.1-1.pgdg110+1) on x86_64-pc-linux-gnu [...]
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Set the postgres user’s password. Please use something stronger than 123456.

postgres=# ALTER USER postgres WITH PASSWORD '123456'; 
ALTER ROLE       
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Create a database called mystoredb.

postgres=# CREATE DATABASE mystoredb;
CREATE DATABASE
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

List the databases. You will see mystoredb on the list.

postgres=# \l
                                  List of databases
   Name    |  Owner   | Encoding |   Collate   |    Ctype    |   Access privileges   
-----------+----------+----------+-------------+-------------+-----------------------
 mystoredb | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | 
 postgres  | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | 
 template0 | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/postgres          +
[...]
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Exit psql interactive terminal and logout as the postgres user.

postgres=# \q

postgres@laptop:~$ exit
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Create a directory for your project.

$ mkdir my_project/

$ cd my_project/
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Install pipenv using pip3.

$ pip3 install pipenv
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Start the Python virtual environment.

$ pipenv shell
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Install the required libraries.

$ pipenv install flask flask-sqlalchemy psycopg2-binary
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Here is a simple example of a one-to-many relationship with Flask-SQLAlchemy.

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    comment_id = db.relationship('Comment', cascade='all, delete', backref='author' lazy=True)

class Comment(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    post_id = db.Column(db.Integer, db.ForeignKey('post.id'))
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

db.relationship()

This function looks at the child table and pulls up multiple instances of the child table.

backref

Backref creates a kind of invisible column within the child table.

lazy

Lazy defines when the data will be pulled from the database.

lazy=select
or
lazy=True

This is the default setting. SQLAlchemy will load the all data necessary at once.

db.ForiegnKey()

This function specifies the parent class (written in lowercase) and relates items back to the parent table.

Create file called application.py and add the following content.

$ vim application.py

# application.py

from datetime import datetime
from flask import Flask
from flask_sqlalchemy import SQLAlchemy


app = Flask(__name__)

app.config['DEBUG'] = True
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://postgres:123456@127.0.0.1:5432/mystoredb'

db = SQLAlchemy(app)


class Customer(db.Model):
    '''Customer table'''

    __tablename__ = 'customer'
    id = db.Column(db.Integer, primary_key=True)
    customer_name = db.Column(db.String(25))
    customer_email = db.Column(db.String(100), nullable=True)
    order_id = db.relationship('Order', cascade='all, delete', backref='customer', lazy=True)

    def __init__(self, customer_name, customer_email):
        self.customer_name = customer_name
        self. customer_email = customer_email

    def __repr__(self):
        return f'<Customer: {self.id} {self.customer_name}>'


class Order(db.Model):
    '''Order table'''

    __tablename__ = 'order'
    id = db.Column(db.Integer, primary_key=True)
    order_date = db.Column(db.DateTime, default=datetime.now)
    # Use datetime to generate a random 6 digit number from milliseconds.
    order_number = db.Column(db.Integer, default=datetime.now().strftime("%f"))
    customer_id = db.Column(db.Integer, db.ForeignKey('customer.id'))

    def __repr__(self):
        return f'<OrderID: {self.id}>'


if __name__ == '__main__':
    app.run(debug=True)
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

*NOTE:

The syntax for the SQLALCHEMY_DATABASE_URI variable is

'postgresql://<db_username>:<password>@<ip_address>:<port>/<database_name>'

Example of what I am using in this tutorial:

'postgresql://postgres:123456@127.0.0.1:5432/mystoredb'
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Change the values of the database URI to reflect your set up.

Start a Python interpreter shell on the command line.

$ python3
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Import the database + models and create the tables.

>>> from application import db, Customer, Order

>>> db.create_all()
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Add a few people to the customer table.

>>> brandon = Customer(customer_name='Brandon', customer_email='brandon@example.com')
>>> maria = Customer(customer_name='Maria', customer_email='maria@example.com')
>>> ahmed = Customer(customer_name='Ahmed', customer_email='ahmed@example.com')
>>> cheuk = Customer(customer_name='Cheuk', customer_email='cheuk@example.com')
>>> nikolaus = Customer(customer_name='Nikolaus', customer_email='nikolaus@example.com')
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Add all people to the database session.

>>> db.session.add_all([brandon, maria, ahmed, cheuk, nikolaus])

⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Commit all changes to the database to save the users.

>>> db.session.commit()
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Create orders and link them with customers in the database like this. Remember 'customer' is the backref value in the parent table.

>>> order1 = Order(customer=brandon)
>>> order2 = Order(customer=brandon)
>>> order3 = Order(customer=brandon)
>>> order4 = Order(customer=ahmed)
>>> order5 = Order(customer=ahmed)
>>> order6 = Order(customer=cheuk)
>>> order7 = Order(customer=cheuk)
>>> order8 = Order(customer=maria)
>>> order9 = Order(customer=nikolaus)
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Add all orders to the session.

>>> db.session.add_all([order1, order3, order3, order4, order5, order6, order7, order8, order9])
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Commit all changes to the database to save the orders.

>>> db.session.commit()
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

How To Perform Queries

At this point we can query the database.

Pull up all the customer records in the database and loop through the results.

>>> customers = Customer.query.all()

>>> for person in customers:
...     f'ID: {person.id} Name: {person.customer_name} Email: {person.customer_email}'
... 
'ID: 1 Name: Brandon Email: brandon@example.com'
'ID: 2 Name: Maria Email: maria@example.com'
'ID: 3 Name: Ahmed Email: ahmed@example.com'
'ID: 4 Name: Cheuk Email: cheuk@example.com'
'ID: 5 Name: Nikolaus Email: nikolaus@example.com'
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Extract the name like this.

>>> for customer in customers:
...     customer.customer_name
... 
'Brandon'
'Maria'
'Ahmed'
'Cheuk'
'Nikolaus'
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Get the total order count for Brandon.

>>> brandon_order_count = Order.query.filter_by(customer_id=1).count()

>>> f'Total orders for Brandon: {brandon_order_count}'
Total orders for Brandon: 3
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Get the order numbers for Brandon.

>>> brandon_all_orders = Order.query.filter(Order.customer_id == 1).all()

>>> for order in brandon_all_orders:
...     f'Order number: {order.order_number}'
... 
'Order number: 680018'
'Order number: 193492'
'Order number: 662450'
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Get the orders from Brandon sorted by most recent.

>>> latest_orders = Order.query.order_by(Order.order_date.desc()).all()

>>> for order in latest_orders:
...     order.order_date.strftime("%Y-%m-%d %H:%M")
... 
'2022-04-03 18:09'
'2022-02-02 20:22'
'2022-01-01 10:59'
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Get the orders from Brandon sorted by oldest first.

>>> oldest_orders = Order.query.order_by(Order.order_date.desc()).all()

>>> for order in oldest_orders:
...     order.order_date.strftime("%Y-%m-%d %H:%M")
... 
'2022-01-01 10:59'
'2022-02-02 20:22'
'2022-04-03 18:09'
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Get orders filtering by a before a specific date.

>>> from datetime import datetime
>>> from_date = datetime(year=2022, month=2, day=22)

>>> befor_feb_22 = Order.query.filter(Order.order_date <= from_date).all()

>>> for order in before_feb_22:
...     f'ID: {order.id} Date: {order.order_date} OrderNum: {order.order_number} CustID: {order.customer_id}'
... 
'ID: 2 Date: 2022-01-01 10:29:43.380762 OrderNum: 27227 CustID: 1'
'ID: 1 Date: 2022-02-02 10:29:43.380762 OrderNum: 27227 CustID: 1'
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Get orders filtering by a after a specific date.

>>> after_date = datetime(year=2022, month=2, day=22)
>>> after_feb_22 = Order.query.filter(Order.order_date >= after_date).all()

>>> for order in after_feb_22:
...     f'ID: {order.id} Date: {order.order_date} OrderNum: {order.order_number} CustID: {order.customer_id}'
... 
'ID: 3 Date: 2022-04-03 10:29:43.380762 OrderNum: 27227 CustID: 1'
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Get the date and time Cheuk placed the orders.

>>> cheuk_all_orders = Order.query.filter(Order.customer_id == 4).all()

>>> for order in cheuk_all_orders:
...     f'Order date: {order.order_date.strftime("%Y-%m-%d %H:%M")}'
... 
'Order date: 2022-02-07 19:52'
'Order date: 2022-02-07 21:38'

>>> exit()
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

How To Troubleshoot If You Have Issues

  1. Check carefully for syntax errors.

  2. If you ran db.create_all() with errors in the application.py file you might have to delete the database and recreate it.

$ sudo su – postgres

postgres=# DROP DATABASE mystoredb;
DROP DATABASE

postgres=# CREATE DATABASE mystoredb;
CREATE DATABASE
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Then run db.create_all() again to create the tables.

>>> python3

>>> from application import db, Customer, Order

>>> db.create_all()
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung

Feel free to leave comments, questions, and suggestions.

Top comments (4)

⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung
 
ekck profile image
Emmanuel Kiprono •

hey , am getting this error TypeError: init() missing 1 required positional argument: 'customer_email' when i try to enter customer name

⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung
 
brandonwallace profile image
brandon_wallace • • Edited

You can add the customer_email when you add the customer to the database.

brandon = Customer(customer_name='Brandon', customer_email='brandon@example.com')
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung ⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung
⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung
 
ekck profile image
Emmanuel Kiprono •

Thank you Brandon, it worked out

⭐ Football Update: brandonwallace/one to many relationships complete how to nbi - Jadwal Siaran Langsung
 
brandonwallace profile image
brandon_wallace •

Hello Emmanuel, please try to go over your code and look for any typos. I have tested the code a few times. It works.