Object Relational Mapping Python
What is ORM?
ORM stands for Object-Relational Mapping, which is a programming technique that allows developers to work with a relational database using an object-oriented paradigm. It maps the data stored in a database to objects in a programming language, which makes it easier for developers to work with data in their application.
Traditionally, when working with a relational database, developers use SQL to write queries and retrieve data. SQL is a powerful language, but it can be difficult to work with and maintain, especially for large and complex systems. ORM provides a layer of abstraction between the database and the application, allowing developers to work with objects instead of raw SQL.
ORM benefits for developers
- Simplification of code: With ORM, developers can work with objects and classes instead of complex SQL queries. This makes the code more readable and maintainable.
- Portability: ORM allows the same code to work with different databases, as it provides a common interface to access the data.
- Database abstraction: ORM shields developers from the complexities of different databases and database-specific SQL syntax, allowing them to focus on the application logic.
- Security: ORM provides built-in protection against SQL injection attacks, as it automatically sanitizes input and uses parameterized queries.
- Increased productivity: With ORM, developers can write code faster, as they don't have to write complex SQL queries manually.
Why SQLAlchemy
SQLalchemy ORM (Object-Relational Mapping) is another way of working with databases using SQLalchemy. It supports a wide range of relational databases, including MySQL, PostgreSQL, SQLite, and Oracle. In ORM, database tables are represented as classes and rows in tables are represented as objects of those classes.
SQLAlchemy provides several key features:
- Declarative base classes: SQLAlchemy allows developers to define their database schema using Python classes. This makes it easy to map tables to classes, and provides a clear separation between the database and application logic.
- Session management: SQLAlchemy provides a session management system that tracks changes made to objects and commits them to the database.
- Query API: SQLAlchemy provides a powerful and flexible query API that allows developers to construct complex queries using Python syntax. Like the
filter()
function we have seen in the previous course. - Support for transactions: SQLAlchemy provides support for transactions, allowing developers to roll back changes if necessary.
- Built-in caching: SQLAlchemy provides built-in caching to improve performance. Support for migrations: SQLAlchemy provides support for database migrations, allowing developers to change the schema of their database over time.
How to use SQLAlchemy for ORM
Using SQLAlchemy for ORM involves several steps:
- Define the database schema using Python classes.
- Create an engine that connects to the database.
- Define a session factory that will be used to create sessions.
- Use the session factory to create a session.
- Use the session to perform CRUD (Create, Read, Update, Delete) operations on the database.
Here is an example of how to use SQLAlchemy for ORM and perform a query with the SQLAlchemy Query API :
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# create an engine to connect to the database
engine = create_engine('mysql://user:password@localhost:3306/employees')
# create a session to work with the database
Session = sessionmaker(bind=engine)
session = Session()
# import the Employee model
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Date, Enum
Base = declarative_base()
class Employee(Base):
__tablename__ = 'employees'
emp_no = Column(Integer, primary_key=True)
birth_date = Column(Date)
first_name = Column(String(14))
last_name = Column(String(16))
gender = Column(Enum('M','F'))
hire_date = Column(Date)
# query the database using the ORM
employees = session.query(Employee).filter(Employee.gender == 'M').limit(10).all()
# print the results
for employee in employees:
print(employee.emp_no, employee.first_name, employee.last_name)
# close the session
session.close()
To query the database, we create a session using sessionmaker and use the query method to perform a query. We filter the query to only include employees with gender 'M' and limit the results to 10 rows. We then iterate over the results and print them.
The main advantage of ORM is that it allows developers to work with databases in a more object-oriented way. They can use the familiar syntax of Python classes and objects to interact with databases. It also allows for better organization and abstraction of database code.
SQLalchemy provides a powerful ORM that allows developers to work with databases in a high-level, Pythonic way. The ORM provides an abstraction layer between the Python code and the database, making it easier to work with databases without needing to know SQL.
Mapping the employees database
from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData, ForeignKey
# create an engine to connect to the database
engine = create_engine('mysql://user:password@localhost:3306/employees')
# create a metadata object to reflect the database schema
metadata = MetaData()
# define the departments table
departments = Table('departments', metadata,
Column('dept_no', String(4), primary_key=True),
Column('dept_name', String(40))
)
# define the dept_emp table
dept_emp = Table('dept_emp', metadata,
Column('emp_no', Integer, ForeignKey('employees.emp_no')),
Column('dept_no', String(4), ForeignKey('departments.dept_no')),
Column('from_date', String(10)),
Column('to_date', String(10))
)
# define the dept_manager table
dept_manager = Table('dept_manager', metadata,
Column('emp_no', Integer, ForeignKey('employees.emp_no')),
Column('dept_no', String(4), ForeignKey('departments.dept_no')),
Column('from_date', String(10)),
Column('to_date', String(10))
)
# define the employees table
employees = Table('employees', metadata,
Column('emp_no', Integer, primary_key=True),
Column('birth_date', String(10)),
Column('first_name', String(14)),
Column('last_name', String(16)),
Column('gender', String(1)),
Column('hire_date', String(10))
)
# define the salaries table
salaries = Table('salaries', metadata,
Column('emp_no', Integer, ForeignKey('employees.emp_no')),
Column('salary', Integer),
Column('from_date', String(10)),
Column('to_date', String(10))
)
# define the titles table
titles = Table('titles', metadata,
Column('emp_no', Integer, ForeignKey('employees.emp_no')),
Column('title', String(50)),
Column('from_date', String(10)),
Column('to_date', String(10))
)
# create a session to interact with the database
from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine)
session = Session()
# example query to select all employees
result = session.query(employees).all()
for row in result:
print(row.emp_no, row.first_name, row.last_name)
# close the session
session.close()
In this example, we first create an engine
to connect to the database using the create_engine
function. Then we define the tables in the database using the Table
and Column
objects from the sqlalchemy module, and create a metadata object to reflect the database schema.
We then create a session to interact with the database using the sessionmaker
function, and use the query
method of the session object to execute a SELECT
statement on the employees table, selecting all rows and printing : the employee number, first name, and last name.
Finally, we close the session using the close method. We can refactor our code like this :
from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData, ForeignKey
# create an engine to connect to the database
engine = create_engine('mysql://user:password@localhost:3306/employees')
# create a metadata object to reflect the database schema
metadata = MetaData()
# create a session to interact with the database
from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine)
session = Session()
#
# define tables like in orm_1.py script
#
# example query
try:
result = session.query(employees, salaries).filter(employees.c.emp_no == salaries.c.emp_no).limit(10)
for row in result:
print(row)
finally:
session.close()
- First, we import the necessary modules, including create_engine, Table, Column, Integer, String, MetaData, ForeignKey, and sessionmaker.
- Next, we create an engine to connect to our MySQL database, using the create_engine function.
- We then create a session to work with the database using sessionmaker.
- We define metadata using MetaData.
- We define the employees and salaries tables using Table and Column.
- We join the two tables using the filter function, specifying the emp_no columns to join on.
- We limit the results to 10 using the limit function.
- Finally, we print the results of the query.
This code joins the employees
and salaries
tables on their respective emp_no
columns and returns the first 10 results. The result is a list of tuples, with each tuple containing the corresponding rows from the two tables.
Perform queries
Let's take a look at the top 5 highest paid employees :
from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData, ForeignKey
# create an engine to connect to the database
engine = create_engine('mysql://user:password@localhost:3306/employees')
# create a metadata object to reflect the database schema
metadata = MetaData()
# create a session to interact with the database
from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine)
session = Session()
#
# define tables like in orm_1.py script
#
# example query
try:
result = (
session.query(employees, salaries)\
.filter(employees.c.emp_no == salaries.c.emp_no)\
.order_by(desc(salaries.c.salary))\
.limit(5)
)
for row in result:
print(f'Employee {row.first_name}-{row.last_name} earn {row.salary}$/year')
#print(f"{employee.first_name} earns {salary.salary} dollars")
finally:
session.close()
Here we've added an order_by()
method to sort the results in descending order by the salary column. The desc()
function is used to specify a descending sort. Finally, we use the limit()
method to limit the results to the top 10 highest paid employees.
Let's take a look to an other query, this time without using session()
and ORM mapping.
Using SQLAlchemy Table
object to join data
Let's take a look at SQLAlchemy join and why this method has several benefits over manual SQL joins:
- Abstraction: SQLAlchemy provides a high-level object-oriented abstraction layer over SQL, making it easier to write queries and perform joins without needing to write low-level SQL code.
- Portability: Because SQLAlchemy provides a layer of abstraction, it makes it easier to switch databases without having to re-write queries. This is especially useful for larger projects where databases may need to be switched due to scaling or other requirements.
- Security: SQLAlchemy's query system provides a safe and secure way to build complex queries, helping to prevent SQL injection attacks.
- Easier to read and maintain: SQLAlchemy queries are often easier to read and maintain than raw SQL queries. This is because they are written in Python, which is a more expressive and easier to read language than SQL.
- Object-Relational Mapping (ORM): SQLAlchemy provides an ORM that maps database tables to Python classes, making it easy to work with data in an object-oriented manner. This can simplify code and make it easier to reason about the data model.
Overall, using SQLAlchemy to join data provides a more efficient and effective way to interact with databases, reducing the likelihood of errors and making it easier to maintain code in the long-term.
Using Table()
The primary difference between using Table
and the ORM approach in SQLAlchemy is how the data is represented and accessed.
When using Table
, the data is represented as tables and columns in the database. Queries are constructed using SQL-like syntax, and the results are returned as tuples or dictionaries. This approach requires a good understanding of SQL and database structure, as well as the ability to write complex queries.
from sqlalchemy import create_engine, MetaData, Table, select
# Define the engine
engine = create_engine('mysql://user:password@localhost:3306/employees')
# Define the metadata
metadata = MetaData()
# Define the tables
employees = Table('employees', metadata, autoload=True, autoload_with=engine)
dept_emp = Table('dept_emp', metadata, autoload=True, autoload_with=engine)
departments = Table('departments', metadata, autoload=True, autoload_with=engine)
# Define the query
query = select([employees.c.emp_no, employees.c.first_name, employees.c.last_name, departments.c.dept_name]).\
select_from(employees.join(dept_emp).join(departments, dept_emp.c.dept_no == departments.c.dept_no))
# Execute the query
result = engine.execute(query)
# Print the results
for row in result:
print(row)
This query selects the emp_no
, first_name
, last_name
, and dept_name
columns from the employees
, dept_emp
, and departments
tables, and joins them together based on the dept_no
column in dept_emp
and departments
. It then executes the query and prints the results.
ORM or Table
On the other hand, the ORM approach in SQLAlchemy allows developers to work with Python classes that represent database tables, and instances of those classes represent rows in the database. Queries are constructed using Python methods and attributes, which are then translated to SQL statements by SQLAlchemy. The results are returned as instances of the corresponding Python classes, making it easier to work with the data in a more object-oriented way.
Overall, the ORM approach is more intuitive for developers who are more familiar with object-oriented programming and less familiar with SQL. It also provides better abstraction from the underlying database structure, making it easier to make changes to the schema without affecting the application code. However, the Table approach may be more appropriate in cases where more fine-grained control over the SQL queries is required.
Wrap-up
Whether you prefer to work with SQL directly or use an ORM, SQLalchemy has you covered. With its intuitive API, SQLalchemy makes it easy to connect to databases, perform queries, and manipulate data. Understanding how to use SQLalchemy is an essential skill for any Python developer working with databases.