CRUD using SQLAlchemy ORM
Last updated on July 27, 2020
Creating Session #
When using SQLAlchemy ORM, we interact with the database using the Session
object. The Session
object also wraps the database connection and transaction. The transaction implicitly starts as soon as the Session
starts communicating with the database and will remain open until the Session
is committed, rolled back or closed.
One way to create a Session
object is to use the Session
class from the sqlalchemy.orm
package.
1 2 3 | from sqlalchemy.orm import create_engine, Session
engine = create_engine("postgres+psycopg2://postgres:pass@localhost/mydb")
session = Session(bind=engine)
|
You will have to create the Session
object everytime you want to communicate with the database.
The Session
constructor accepts a number of argument to customize its working. If we choose to create Session using this method, we would have to call the Session
constructor with the same set of parameters over and over again throughout the application.
To make things easier, SQLAlchemy provides sessionmaker
class which creates Session
class with default arguments set for its constructor.
1 2 | from sqlalchemy.orm import sessionmaker, Session
Session = sessionmaker(bind=engine)
|
You should call call sessionmaker
once in your application at the global scope.
Once we have access to the custom Session
class you can instantiate it as many time as you need without passing any arguments to it.
session = Session()
Note that instantiating Session
object doesn't instantly establish connection to the database. The connection is established only when you start sending queries to the database.
Note: There is more to Session than this tutorial covers. For more details checkout the official documentation.
Inserting Data #
To create a new record using SQLAlchemy ORM, we follow these steps:
- Create an object.
- Add the object to the session.
- Commit the session.
Let's create two new Customer
objects as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | c1 = Customer(first_name = 'Toby',
last_name = 'Miller',
username = 'tmiller',
email = 'tmiller@example.com',
address = '1662 Kinney Street',
town = 'Wolfden'
)
c2 = Customer(first_name = 'Scott',
last_name = 'Harvey',
username = 'scottharvey',
email = 'scottharvey@example.com',
address = '424 Patterson Street',
town = 'Beckinsdale'
)
c1, c2
|
Expected Output:
(<Customer:None-johngreen>, <Customer:None-katwilson>)
Here we have created two Customer
objects. We can access the attributes of an object using the dot(.
) operator as follows:
1 2 | c1.first_name, c1.last_name
c2.first_name, c2.last_name
|
Expected Output:
1 2 | ('John', 'Green')
('Katherine', 'Wilson')
|
Next, we add the objects to the session.
1 2 | session.add(c1)
session.add(c2)
|
Adding objects to the session doesn't actually writes them to the database, it only prepares the objects to be saved in the next commit. We can verify this by checking the primary key of the objects.
c1.id, c2.id
Expected Output:
(None, None)
The value of id
attribute of both the objects is None
. That means our objects are not yet saved in the database.
Instead of adding one object to the session at a time, we can use add_all()
method. The add_all()
method accepts a list of objects to be added to the session.
session.add_all([c1, c2])
Adding an object to the session multiple times doesn't throw any errors. At any time, you can view the objects added to the session using session.new
.
session.new
Expected Output:
IdentitySet([<Customer:None-johngreen>, <Customer:None-katwilson>])
Finally, to save the objects to the database call commit()
method as follows:
session.commit()
Once you commit the transaction, the connection resources referenced by the Session
object is returned to the connection pool. Subsequent operations will occur in a new transaction.
Accessing the id
attribute of the Customer
object will now return the primary key instead of None
.
c1.id, c2.id
Expected Output:
(1, 2)
At this point, the Customer
table should look like this:
Our customers haven't ordered anything. So c1.orders
and c2.orders
would return an empty list.
c1.orders, c2.orders
Expected Output:
([], [])
Let's add some more customers to the customers
table:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | c3 = Customer(
first_name = "John",
last_name = "Lara",
username = "johnlara",
email = "johnlara@mail.com",
address = "3073 Derek Drive",
town = "Norfolk"
)
c4 = Customer(
first_name = "Sarah",
last_name = "Tomlin",
username = "sarahtomlin",
email = "sarahtomlin@mail.com",
address = "3572 Poplar Avenue",
town = "Norfolk"
)
c5 = Customer(first_name = 'Toby',
last_name = 'Miller',
username = 'tmiller',
email = 'tmiller@example.com',
address = '1662 Kinney Street',
town = 'Wolfden'
)
c6 = Customer(first_name = 'Scott',
last_name = 'Harvey',
username = 'scottharvey',
email = 'scottharvey@example.com',
address = '424 Patterson Street',
town = 'Beckinsdale'
)
session.add_all([c3, c4, c5, c6])
session.commit()
|
Before we can take orders, let's add some products to the items
table.
1 2 3 4 5 6 7 8 9 10 11 | i1 = Item(name = 'Chair', cost_price = 9.21, selling_price = 10.81, quantity = 5)
i2 = Item(name = 'Pen', cost_price = 3.45, selling_price = 4.51, quantity = 3)
i3 = Item(name = 'Headphone', cost_price = 15.52, selling_price = 16.81, quantity = 50)
i4 = Item(name = 'Travel Bag', cost_price = 20.1, selling_price = 24.21, quantity = 50)
i5 = Item(name = 'Keyboard', cost_price = 20.1, selling_price = 22.11, quantity = 50)
i6 = Item(name = 'Monitor', cost_price = 200.14, selling_price = 212.89, quantity = 50)
i7 = Item(name = 'Watch', cost_price = 100.58, selling_price = 104.41, quantity = 50)
i8 = Item(name = 'Water Bottle', cost_price = 20.89, selling_price = 25, quantity = 50)
session.add_all([i1, i2, i3, i4, i5, i6, i7, i8])
session.commit()
|
Create some orders now:
1 2 3 4 5 6 7 8 9 10 11 12 | o1 = Order(customer = c1)
o2 = Order(customer = c1)
line_item1 = OrderLine(order = o1, item = i1, quantity = 3)
line_item2 = OrderLine(order = o1, item = i2, quantity = 2)
line_item3 = OrderLine(order = o2, item = i1, quantity = 1)
line_item3 = OrderLine(order = o2, item = i2, quantity = 4)
session.add_all([o1, o2])
session.new
session.commit()
|
Notice that we are only adding Order
objects (i.e o1
and o2
) to the session. The Order
and OrderLine
object are associated with a one-to-many relationship. Adding an Order
object to the session implicitly adds related OrderLine
object to the session as well. However, even you still add the OrderLine
object manually to the session, you will not get any error.
Instead of passing Order
object at the time of creating OrderLine
instance, we can also do this:
1 2 3 4 5 6 7 8 9 10 | o3 = Order(customer = c1)
orderline1 = OrderLine(item = i1, quantity = 5)
orderline2 = OrderLine(item = i2, quantity = 10)
o3.order_lines.append(orderline1)
o3.order_lines.append(orderline2)
session.add_all([o3])
session.commit()
|
After this commit, the orders
and order_lines
table should now look like this:
[]
Access the orders
attribute of the Customer
object again, this time you will get a non-empty list like this:
c1.orders
Expected Output:
[<Order:8>, <Order:9>]
From the other side of the relationship, we can access the Customer
object to which the order belongs to using the customer
attribute on the Order
object.
o1.customer
Expected Output:
<Customer:1-tmiller>
The customer c1
now has two orders. To view the order lines in an order use the order_lines
attribute of the Order
object.
c1.orders[0].order_lines, c1.orders[1].order_lines
Expected Output:
([<OrderLine:1>, <OrderLine:2>], [<OrderLine:3>, <OrderLine:4>])
To access the item in an order line use item
attribute.
1 2 3 4 5 6 7 | for ol in c1.orders[0].order_lines:
ol.id, ol.item, ol.quantity
print('-------')
for ol in c1.orders[1].order_lines:
ol.id, ol.item, ol.quantity
|
Expected Output:
1 2 3 4 5 | (1, <Item:1-Chair>, 3)
(2, <Item:2-Pen>, 2)
-------
(3, <Item:1-Chair>, 1)
(4, <Item:2-Pen>, 4)
|
Remember that, all of this became possible because of the relationship()
we defined in our models.
Querying Data #
To query database we use the query()
method of the session
object. The query()
method returns an object of type sqlalchemy.orm.query.Query
, simply called Query
. The Query
object represents the SELECT
statement that will be used to query the database. The following table lists some common methods of the Query
class.
Method | Description |
---|---|
all() |
returns the result of the query (represented by Query ) as a list. |
count() |
returns the total number of records in the query. |
first() |
returns the first result of the query or None , if there are no rows in the result. |
scalar() |
returns the first column of the first row or None if the result set is empty. If multiple rows are encountered it throws MultipleResultsFound exception. |
one |
returns exactly only row. If it encounters multiple rows it throws MultipleResultsFound exception. If the result set is empty it throws NoResultFound exception. |
get(pk) |
returns an object that matches the given primary key (pk), or None , if no such object is found. |
filter(*criterion) |
returns a new Query instance after applying the WHERE clause to the query. |
limit(limit) |
return a new Query instance after applying the LIMIT clause to the query. |
offset(offset) |
return a new Query instance after applying the OFFSET clause to the query. |
order_by(*criterion) |
return a new Query instance after applying ORDER BY clause to the query. |
join(*props, **kwargs) |
return a new Query instance after creating SQL INNER JOIN on the query. |
outerjoin(*props, **kwargs) |
return a new Query instance after creating SQL LEFT OUTER JOIN on the query. |
group_by(*criterion) |
return a new Query instance after adding GROUP BY clause to the query. |
having(criterion) |
return a new Query instance after adding HAVING clause to the query. |
all() method #
In its simplest form, the query()
method can take one or more model class or columns as arguments. The following code returns all the records from the customers
table.
session.query(Customer).all()
Expected Output:
1 2 3 4 5 6 | [<Customer:1-jongreen>,
<Customer:2-katwilson>,
<Customer:3-johnlara>,
<Customer:4-sarahtomlin>,
<Customer:5-tmiller>,
<Customer:6-scottharvey>]
|
Similarly, the following code returns all the records from the items
and orders
table.
1 2 | session.query(Item).all()
session.query(Order).all()
|
Expected Output:
1 2 3 4 5 6 7 8 9 10 | [<Item:1-Chair>,
<Item:2-Pen>,
<Item:3-Headphone>,
<Item:4-Travel Bag>,
<Item:5-Keyboard>,
<Item:6-Monitor>,
<Item:7-Watch>,
<Item:8-Water Bottle>]
[<Order:1>, <Order:2>, <Order:3>]
|
To get the raw SQL used to query the database simply print the sqlalchemy.orm.query.Query
object as follows:
print(session.query(Customer))
Expected Output:
1 2 3 4 5 6 7 8 9 10 11 12 | SELECT
customers. ID AS customers_id,
customers.first_name AS customers_first_name,
customers.last_name AS customers_last_name,
customers.username AS customers_username,
customers.email AS customers_email,
customers.address AS customers_address,
customers.town AS customers_town,
customers.created_on AS customers_created_on,
customers.updated_on AS customers_updated_on
FROM
customers
|
Calling all()
method on a large result set is inefficient instead we can use a for loop to iterate over the Query
object as follows::
1 2 3 4 | q = session.query(Customer)
for c in q:
print(c.id, c.first_name)
|
Expected Output:
1 2 3 4 5 6 | 1 John
2 Katherine
3 John
4 Sarah
5 Toby
6 Scott
|
The preceding queries have returned data from all columns of the table. We can prevent this by passing the column names explicitly to the query()
method as follows:
session.query(Customer.id, Customer.first_name).all()
Expected Output:
1 2 3 4 5 6 | [(1, 'John'),
(2, 'Katherine'),
(3, 'John'),
(4, 'Sarah'),
(5, 'Toby'),
(6, 'Scott')]
|
Notice that now each item in the list is a tuple instead of a model instance.
count() method #
The count()
method returns the number of results returned by the query.
1 2 3 | session.query(Customer).count() # get the total number of records in the customers table
session.query(Item).count() # get the total number of records in the items table
session.query(Order).count() # get the total number of records in the orders table
|
Expected Output:
1 2 3 | 6
8
3
|
first() method #
The first()
method returns the first result of the query or None
if the query returns zero results.
1 2 3 | session.query(Customer).first()
session.query(Item).first()
session.query(Order).first()
|
Expected Output:
1 2 3 | <Customer:1-johngreen>
<Item:1-Chair>
<Order:1>
|
get() method #
The get()
method returns the instance which matches the primary key passed to it or None
if no such object found.
1 2 3 | session.query(Customer).get(1)
session.query(Item).get(1)
session.query(Order).get(100)
|
Expected Output:
1 2 | <Customer:1-johngreen>
<Item:1-Chair>
|
filter() method #
The filter()
method allows us to filter the result by adding WHERE
clause to the query. At the minimum, it accepts a column, an operator and a value. Here is an example:
session.query(Customer).filter(Customer.first_name == 'John').all()
Expected Output:
[<Customer:1-johngreen>, <Customer:3-johnlara>]
This query returns all the customers whose first name is John
. The SQL equivalent of the query is:
print(session.query(Customer).filter(Customer.first_name == 'John'))
Expected Output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | SELECT
customers.id AS customers_id,
customers.first_name AS customers_first_name,
customers.last_name AS customers_last_name,
customers.username AS customers_username,
customers.email AS customers_email,
customers.address AS customers_address,
customers.town AS customers_town,
customers.created_on AS customers_created_on,
customers.updated_on AS customers_updated_on
FROM
customers
WHERE
customers.first_name = %(first_name_1)s
|
The string %(first_name_1)s
in the WHERE
clause is a placeholder and will be replaced by the actual value (i.e John
) when the query is executed.
We can pass multiple filters to the filter()
method and they will be joined together using SQL AND
operator. For example:
session.query(Customer).filter(Customer.id <= 5, Customer.town == "Norfolk").all()
Expected Output:
[<Customer:1-johngreen>, <Customer:3-johnlara>, <Customer:4-sarahtomlin>]
This query returns all the customers whose primary key is less than or equal to 5 and town name starts with Nor
. Its SQL equivalent is:
print(session.query(Customer).filter(Customer.id <= 5, Customer.town.like("Nor%"))
Expected Output:
Another way to combine conditions is to use conjunctions (i.e. and_()
, or_()
and not_()
). Here are some examples:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | # find all customers who either live in Peterbrugh or Norfolk
session.query(Customer).filter(or_(
Customer.town == 'Peterbrugh',
Customer.town == 'Norfolk'
)).all()
# find all customers whose first name is John and live in Norfolk
session.query(Customer).filter(and_(
Customer.first_name == 'John',
Customer.town == 'Norfolk'
)).all()
# find all johns who don't live in Peterbrugh
session.query(Customer).filter(and_(
Customer.first_name == 'John',
not_(
Customer.town == 'Peterbrugh',
)
)).all()
|
Expected Output:
1 2 3 4 5 6 7 8 | [<Customer:1-johngreen>,
<Customer:2-katwilson>,
<Customer:3-johnlara>,
<Customer:4-sarahtomlin>]
[<Customer:1-johngreen>, <Customer:3-johnlara>]
[<Customer:1-johngreen>, <Customer:3-johnlara>]
|
The following listing shows how to use some common comparison operators with the filter()
method.
IS NULL #
session.query(Order).filter(Order.date_shipped == None).all()
Expected Output:
[<Order:1>, <Order:2>, <Order:3>]
IS NOT NULL #
session.query(Order).filter(Order.date_shipped != None).all()
Expected Output:
[]
IN #
session.query(Customer).filter(Customer.first_name.in_(['Toby', 'Sarah'])).all()
Expected Output:
[<Customer:4-sarahtomlin>, <Customer:5-tmiller>]
NOT IN #
session.query(Customer).filter(Customer.first_name.notin_(['Toby', 'Sarah'])).all()
Expected Output:
1 2 3 4 | [<Customer:1-johngreen>,
<Customer:2-katwilson>,
<Customer:3-johnlara>,
<Customer:6-scottharvey>]
|
BETWEEN #
session.query(Item).filter(Item.cost_price.between(10, 50)).all()
Expected Output:
1 2 3 4 | [<Item:3-Headphone>,
<Item:4-Travel Bag>,
<Item:5-Keyboard>,
<Item:8-Water Bottle>]
|
NOT BETWEEN #
session.query(Item).filter(not_(Item.cost_price.between(10, 50))).all()
Expected Output:
[<Item:1-Chair>, <Item:2-Pen>, <Item:6-Monitor>, <Item:7-Watch>]
LIKE #
session.query(Item).filter(Item.name.like("%r")).all()
Expected Output:
[<Item:1-Chair>, <Item:6-Monitor>]
The like()
method performs a case-sensitive match. For case-insensitive match use ilike()
.
session.query(Item).filter(Item.name.ilike("w%")).all()
Expected Output:
[<Item:7-Watch>, <Item:8-Water Bottle>]
NOT LIKE #
session.query(Item).filter(not_(Item.name.like("W%"))).all()
Expected Output:
1 2 3 4 5 6 | [<Item:1-Chair>,
<Item:2-Pen>,
<Item:3-Headphone>,
<Item:4-Travel Bag>,
<Item:5-Keyboard>,
<Item:6-Monitor>]
|
limit() method #
The limit()
method adds LIMIT
clause to the query. It accepts the number of rows you want to return from the query.
1 2 | session.query(Customer).limit(2).all()
session.query(Customer).filter(Customer.address.ilike("%avenue")).limit(2).all()
|
Expected Output:
1 2 3 | [<Customer:1-johngreen>, <Customer:2-katwilson>]
[<Customer:2-katwilson>, <Customer:4-sarahtomlin>]
|
The SQL equivalent of the above queries is as follows:
1 2 | print(session.query(Customer).limit(2))
print(session.query(Customer).filter(Customer.address.ilike("%avenue")).limit(2))
|
Expected Output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | SELECT
customers. id AS customers_id,
customers.first_name AS customers_first_name,
customers.last_name AS customers_last_name,
customers.username AS customers_username,
customers.email AS customers_email,
customers.address AS customers_address,
customers.town AS customers_town,
customers.created_on AS customers_created_on,
customers.updated_on AS customers_updated_on
FROM
customers
LIMIT %(param_1)s
SELECT
customers.id AS customers_id,
customers.first_name AS customers_first_name,
customers.last_name AS customers_last_name,
customers.username AS customers_username,
customers.email AS customers_email,
customers.address AS customers_address,
customers.town AS customers_town,
customers.created_on AS customers_created_on,
customers.updated_on AS customers_updated_on
FROM
customers
WHERE
customers.address ILIKE % (address_1)s
LIMIT %(param_1)s
|
offset() method #
The offset()
method adds the OFFSET
clause to the query. It accepts offset as an argument. It is commonly used with the limit()
clause.
session.query(Customer).limit(2).offset(2).all()
Expected Output:
[<Customer:3-johnlara>, <Customer:4-sarahtomlin>]
The SQL equivalent of the above query is as follows:
print(session.query(Customer).limit(2).offset(2))
Expected Output:
1 2 3 4 5 6 7 8 9 10 11 12 13 | SELECT
customers. ID AS customers_id,
customers.first_name AS customers_first_name,
customers.last_name AS customers_last_name,
customers.username AS customers_username,
customers.email AS customers_email,
customers.address AS customers_addrees,
customers.town AS customers_town,
customers.created_on AS customers_created_on,
customers.updated_on AS customers_updated_on
FROM
customers
LIMIT %(param_1)s OFFSET %(param_2)s
|
order_by() method #
The order_by()
method is used to order the result by adding ORDER BY
clause to the query. It accepts column names on which the order should be based. By default, it sorts in ascending order.
1 2 | session.query(Item).filter(Item.name.ilike("wa%")).all()
session.query(Item).filter(Item.name.ilike("wa%")).order_by(Item.cost_price).all()
|
Expected Output:
1 2 | [<Item:7-Watch>, <Item:8-Water Bottle>]
[<Item:8-Water Bottle>, <Item:7-Watch>]
|
To sort in descending order use desc()
function as follows:
1 2 | from sqlalchemy import desc
session.query(Item).filter(Item.name.ilike("wa%")).order_by(desc(Item.cost_price)).all()
|
Expected Output:
[<Item:7-Watch>, <Item:8-Water Bottle>]
join() method #
The join()
method is used to create SQL INNER JOIN. It accepts table name for which you want to create SQL JOIN.
Let's use join()
method to find all the customers who have one or more orders.
session.query(Customer).join(Order).all()
Expected Output:
[<Customer:1-johngreen>]
This query is equivalent to the following SQL:
print(session.query(Customer).join(Order))
Expected Output:
1 2 3 4 5 6 7 8 9 10 11 12 13 | SELECT
customers.id AS customers_id,
customers.first_name AS customers_first_name,
customers.last_name AS customers_last_name,
customers.username AS customers_username,
customers.email AS customers_email,
customers.address AS customers_address,
customers.town AS customers_town,
customers.created_on AS customers_created_on,
customers.updated_on AS customers_updated_on
FROM
customers
JOIN orders ON customers.id = orders.customer_id
|
The join()
method is commonly used to get the data from one or more table in a single query. For example:
session.query(Customer.id, Customer.username, Order.id).join(Order).all()
Expected Output:
[(1, 'johngreen', 1), (1, 'johngreen', 2), (1, 'johngreen', 3)]
We can create SQL JOIN for more than two table by chaining join()
method as follows:
session.query(Table1).join(Table2).join(Table3).join(Table4).all()
Here is another example, which uses 3 joins to find all the items in the John Green
's first order.
1 2 3 4 5 6 7 8 9 10 | session.query(
Customer.first_name,
Item.name,
Item.selling_price,
OrderLine.quantity
).join(Order).join(OrderLine).join(Item).filter(
Customer.first_name == 'John',
Customer.last_name == 'Green',
Order.id == 1,
).all()
|
Expected Output:
[('John', 'Chair', Decimal('10.81'), 3), ('John', 'Pen', Decimal('4.51'), 2)]
outerjoin() method #
The outerjoin()
method works just like join()
but creates LEFT OUTER JOIN
.
1 2 3 4 | session.query(
Customer.first_name,
Order.id,
).outerjoin(Order).all()
|
Expected Output:
1 2 3 4 5 6 7 8 | [('John', 1),
('John', 2),
('John', 3),
('Katherine', None),
('Toby', None),
('Scott', None),
('Sarah', None),
('John', None)]
|
In this query, the left table is the customers
table. Thus, it will return all the rows from customers
table (the left table), and only the rows that meet the join condition are returned from the orders
table (the right table).
You can also create a FULL OUTER JOIN
by passing full=True
to outerjoin()
method. For example:
1 2 3 4 | session.query(
Customer.first_name,
Order.id,
).outerjoin(Order, full=True).all()
|
Expected Output:
1 2 3 4 5 6 7 8 | [('John', 1),
('John', 2),
('John', 3),
('Katherine', None),
('Toby', None),
('Scott', None),
('Sarah', None),
('John', None)]
|
group_by() method #
We group results using the group_by()
method. It accepts one or more columns and groups the rows according to the values in the column.
The following query uses join()
and group_by()
to count the number of orders made by john green.
1 2 3 4 5 6 | from sqlalchemy import func
session.query(func.count(Customer.id)).join(Order).filter(
Customer.first_name == 'John',
Customer.last_name == 'Green',
).group_by(Customer.id).scalar()
|
Expected Output:
3
having() method #
To filter out the results based on the values returned by aggregate functions we use having()
method which adds the HAVING
clause to the SELECT
statement. Just like the where()
clause, it accepts a condition.
1 2 3 4 5 6 | # find the number of customers lives in each town
session.query(
func.count("*").label('town_count'),
Customer.town
).group_by(Customer.town).having(func.count("*") > 2).all()
|
Expected Output:
[(3, 'Norfolk')]
Dealing with Duplicates #
To deal with the duplicate rows in the result set we use the DISTINCT
option. We can add DISTINCT
option to the SELECT
statement using the distinct()
method. For example:
1 2 3 4 5 6 7 8 9 | from sqlalchemy import distinct
session.query(Customer.town).filter(Customer.id < 10).all()
session.query(Customer.town).filter(Customer.id < 10).distinct().all()
session.query(
func.count(distinct(Customer.town)),
func.count(Customer.town)
).all()
|
Expected Output:
1 2 3 4 5 6 7 8 9 10 | [('Norfolk'),
('Peterbrugh'),
('Norfolk'),
('Norfolk'),
('Wolfden'),
('Beckinsdale')]
[('Peterbrugh'), ('Beckinsdale'), ('Wolfden'), ('Norfolk')]
[(4, 6)]
|
Casting #
Casting (converting) data from one type to another is a common operation and is done via cast()
function from the sqlalchemy
package.
1 2 3 4 5 6 7 8 | from sqlalchemy import cast, Date, distinct, union
session.query(
cast(func.pi(), Integer),
cast(func.pi(), Numeric(10,2)),
cast("2010-12-01", DateTime),
cast("2010-12-01", Date),
).all()
|
Expected Output:
1 2 3 4 | [(3,
Decimal('3.14'),
datetime.datetime(2010, 12, 1, 0, 0),
datetime.date(2010, 12, 1))]
|
Unions #
To union queries we use the union()
method of the Query
object. It takes one or more queries. For example:
1 2 3 | s1 = session.query(Item.id, Item.name).filter(Item.name.like("Wa%"))
s2 = session.query(Item.id, Item.name).filter(Item.name.like("%e%"))
s1.union(s2).all()
|
Expected Output:
1 2 3 4 5 6 | [(2, 'Pen'),
(4, 'Travel Bag'),
(3, 'Headphone'),
(5, 'Keyboard'),
(7, 'Watch'),
(8, 'Water Bottle')]
|
By default, union()
removes all the duplicate rows from the result set. If you want to keep the duplicates use union_all()
.
s1.union_all(s2).all()
Expected Output:
1 2 3 4 5 6 7 | [(7, 'Watch'),
(8, 'Water Bottle'),
(2, 'Pen'),
(3, 'Headphone'),
(4, 'Travel Bag'),
(5, 'Keyboard'),
(8, 'Water Bottle')]
|
Updating Data #
To update an object simply set its attribute to a new value, add the object to the session and commit the changes.
1 2 3 4 | i = session.query(Item).get(8)
i.selling_price = 25.91
session.add(i)
session.commit()
|
This way we can only update a single object at a time. To update multiple rows at once use update()
method of the Query
object. It returns the total number of records updated. For example:
1 2 3 4 5 6 | # update quantity of all quantity of items to 60 whose name starts with 'W'
session.query(Item).filter(
Item.name.ilike("W%")
).update({"quantity": 60}, synchronize_session='fetch')
session.commit()
|
Expected Output:
2
Deleting Data #
To delete an object use the delete()
method of the session object. It accepts an object and marks it to be deleted in the next commit.
1 2 3 4 | i = session.query(Item).filter(Item.name == 'Monitor').one()
i
session.delete(i)
session.commit()
|
Expected Output:
<Item:6-Monitor>
This commit removes the Monitor
from the items
table.
To delete multiple records at once use the delete()
method of the Query
object.
1 2 3 4 | session.query(Item).filter(
Item.name.ilike("W%")
).delete(synchronize_session='fetch')
session.commit()
|
Expected Output:
2
This commit deletes all the items whose name start with W
.
Raw Queries #
ORM also give you the flexibility to directly to use directly use SQL using the text()
function. For example:
1 2 3 4 5 6 7 | from sqlalchemy import text
session.query(Customer).filter(text("first_name = 'John'")).all()
session.query(Customer).filter(text("town like 'Nor%'")).all()
session.query(Customer).filter(text("town like 'Nor%'")).order_by(text("first_name, id desc")).all()
|
Expected Output:
1 2 3 4 5 | [<Customer:1-johngreen>, <Customer:3-johnlara>]
[<Customer:1-johngreen>, <Customer:3-johnlara>, <Customer:4-sarahtomlin>]
[<Customer:3-johnlara>, <Customer:1-johngreen>, <Customer:4-sarahtomlin>]
|
Transactions #
A transaction is a way to execute a set of SQL statements such that either all of the statements are executed successfully or nothing at all. If any of the statement involved in the transaction fails then the database is returned to the state it was in before the transaction was initiated.
We currently have two orders in the database. The process of dispatching the order is as follows:
- Set the shipping date in the
date_shipped
column inorders
table - Subtract the quantity of ordered items from the
items
table
Both of these actions must be performed as a unit to ensure that the data in the tables are correct.
In the following listing, we define dispatch_order()
method which accepts order_id
as an argument, and performs the above-mentioned tasks in a transaction.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | from sqlalchemy import update
from sqlalchemy.exc import IntegrityError
from datetime import datetime
def dispatch_order(order_id):
# check whether order_id is valid or not
order = session.query(Order).get(order_id)
if not order:
raise ValueError("Invalid order id: {}.".format(order_id))
if order.date_shipped:
print("Order already shipped.")
return
try:
for i in order.order_lines:
i.item.quantity = i.item.quantity - i.quantity
order.date_shipped = datetime.now()
session.commit()
print("Transaction completed.")
except IntegrityError as e:
print(e)
print("Rolling back ...")
session.rollback()
print("Transaction failed.")
|
Our first order is for 3 chairs and 2 pens. Calling dispatch_order()
function with order id of 1
, will return the following output:
dispatch_order(1)
Expected Output:
Transaction completed.
At this point, items
and order_lines
tables should look like this:
[]
[]
Our next order is for 1 chair and 4 pens, but we now only have 3 pens in the stock!
Let's try running dispatch_order()
for second order and see what happens.
dispatch_order(2)
Expected Output:
1 2 3 4 5 | (psycopg2.IntegrityError) new row for relation "items" violates check constraint "quantity_check"
DETAIL: Failing row contains (2, Pen, 3.45, 4.51, -3).
[SQL: 'UPDATE items SET quantity=%(quantity)s WHERE items.id = %(items_id)s'] [parameters: ({'quantity': 1, 'items_id': 1}, {'quantity': -3, 'items_id': 2})] (Background on this error at: http://sqlalche.me/e/gkpj)
Rolling back ...
Transaction failed.
|
As expected, our shipment failed because we don't have enough pens in the stock and because we are using transaction our database is returned to the state it was in before the transaction was started.
I hope this tutorial would be informative for you. If you have encountered any typo or error. Get in touch using the contact page or comment box below.
Load Comments