Skip to content

Commit 9f06316

Browse files
Waldemar BauerWaldemar Bauer
Waldemar Bauer
authored and
Waldemar Bauer
committed
[ADB2022] lab 4 add
1 parent 48c9d2d commit 9f06316

File tree

4 files changed

+357
-8
lines changed

4 files changed

+357
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
# Join table and relation in SQLAlchemy
2+
3+
The purpose of these laboratory classes is to familiarize participants with join table in SQLAlchemy.
4+
5+
The scope of this classes:
6+
- using join()
7+
- using outerjoin()
8+
-
9+
10+
## Introduction
11+
From the previous classes we know how create query to database in SQLAlchemy based on function [select](https://docs.sqlalchemy.org/en/13/core/metadata.html?highlight=select#sqlalchemy.schema.Table.select) or [query](https://docs.sqlalchemy.org/en/14/orm/query.html)
12+
13+
To work properly in class, we will need the following configuration:
14+
```python
15+
from sqlalchemy import create_engine, select, Column, Integer, String, Date, ForeignKey, PrimaryKeyConstraint
16+
from sqlalchemy.orm import sessionmaker, relationship
17+
from sqlalchemy.ext.declarative import declarative_base
18+
from sqlalchemy import create_engine, MetaData, Table
19+
20+
engine = create_engine(db_string)
21+
22+
metadata = MetaData()
23+
24+
dic_table = {}
25+
for table_name in engine.table_names():
26+
dic_table[table_name] = Table(table_name, metadata , autoload=True, autoload_with=engine)
27+
28+
session = (sessionmaker(bind=engine))()
29+
30+
Base = declarative_base()
31+
```
32+
33+
The first part of the laboratory will concern the case of working with a database whose structure is don't well known.
34+
35+
All the examples for this laboratory part will be for the country, city, and address tables that are mapped on the classes:
36+
37+
```python
38+
class Country(Base):
39+
__tablename__ = 'country'
40+
country_id = Column(Integer, primary_key=True)
41+
country = Column(String(50))
42+
last_update = Column(Date)
43+
def __str__(self):
44+
return 'Country id:{0}\n Country name: {1}\n Country last_update: {2}'.format(self.country_id,self.country,self.last_update)
45+
46+
47+
class City(Base):
48+
__tablename__ = 'city'
49+
city_id = Column(Integer, primary_key=True)
50+
city = Column(String(50))
51+
country_id = Column(Integer, ForeignKey('country.country_id'))
52+
last_update = Column(Date)
53+
54+
class Address(Base):
55+
__tablename__ = 'address'
56+
address_id = Column(Integer, primary_key=True)
57+
address = Column(String(50))
58+
address2 = Column(String(50))
59+
district = Column(String(50))
60+
city_id = Column(Integer, ForeignKey('city.city_id'))
61+
postal_code = Column(String(10))
62+
phone = Column(String(50))
63+
last_update = Column(Date)
64+
```
65+
66+
## Basic join
67+
68+
To make join we can use script:
69+
70+
```python
71+
from sqlalchemy import select
72+
73+
# select * from category
74+
75+
mapper_stmt = select([dic_table['city']]).select_from(dic_table['city'].join(dic_table['country'], dic_table['city'].c.country_id == dic_table['country'].c.country_id ))
76+
print('Mapper join: ')
77+
print(mapper_stmt)
78+
79+
session_stmt = session.query(Country).join(City)
80+
print('\nSession join: ')
81+
print(session_stmt)
82+
```
83+
84+
```sql
85+
Mapper join:
86+
SELECT city.city_id, city.city, city.country_id, city.last_update
87+
FROM city JOIN country ON city.country_id = country.country_id
88+
89+
Session join:
90+
SELECT country.country_id AS country_country_id, country.country AS country_country, country.last_update AS country_last_update
91+
FROM country JOIN city ON country.country_id = city.country_id
92+
```
93+
As you can see, the join function creates queries that connect tables in a natural way (PK - FK relationship). But the query results will only appear for the columns contained in the table specified in the select or query functions.
94+
95+
To download records for selected tables, modify the code as follows:
96+
```python
97+
mapper_stmt = select([dic_table['city'],dic_table['country']]).select_from(dic_table['city'].join(dic_table['country'], dic_table['city'].c.country_id == dic_table['country'].c.country_id ))
98+
print('Mapper join: ')
99+
print(mapper_stmt)
100+
101+
session_stmt = q =session.query(Country,City)
102+
print('\nSession join: ')
103+
print(session_stmt)
104+
```
105+
After execute mapper_stmt query, we get a list of tuples representing the values of joined table rows. Examples:
106+
107+
```python
108+
#select query:
109+
[(1, 'A Corua (La Corua)', 87, datetime.datetime(2006, 2, 15, 9, 45, 25), 87, 'Spain', datetime.datetime(2006, 2, 15, 9, 44)),
110+
(2, 'Abha', 82, datetime.datetime(2006, 2, 15, 9, 45, 25), 82, 'Saudi Arabia', datetime.datetime(2006, 2, 15, 9, 44)),
111+
(3, 'Abu Dhabi', 101, datetime.datetime(2006, 2, 15, 9, 45, 25), 101, 'United Arab Emirates', datetime.datetime(2006, 2, 15, 9, 44)),
112+
(4, 'Acua', 60, datetime.datetime(2006, 2, 15, 9, 45, 25), 60, 'Mexico', datetime.datetime(2006, 2, 15, 9, 44)),
113+
(5, 'Adana', 97, datetime.datetime(2006, 2, 15, 9, 45, 25), 97, 'Turkey', datetime.datetime(2006, 2, 15, 9, 44)),
114+
(6, 'Addis Abeba', 31, datetime.datetime(2006, 2, 15, 9, 45, 25), 31, 'Ethiopia', datetime.datetime(2006, 2, 15, 9, 44)),
115+
...]
116+
```
117+
118+
When session_stmt is used, the results are a list of tuples which consist of classes representing the relevant objects
119+
120+
```python
121+
#session query:
122+
[(<__main__.Country object at 0x000001CE78E50E88>, <__main__.City object at 0x000001CE78E50FC8>),
123+
(<__main__.Country object at 0x000001CE7A09C148>, <__main__.City object at 0x000001CE78E50FC8>),
124+
(<__main__.Country object at 0x000001CE7A09C208>, <__main__.City object at 0x000001CE78E50FC8>),
125+
(<__main__.Country object at 0x000001CE7A09C288>, <__main__.City object at 0x000001CE78E50FC8>),
126+
(<__main__.Country object at 0x000001CE7A09C308>, <__main__.City object at 0x000001CE78E50FC8>)]
127+
```
128+
129+
If we want to create a query for joined anather table we use the following pattern:
130+
131+
```python
132+
mapper_stmt = select([dic_table['city']]).\
133+
select_from(\
134+
dic_table['city'].join(\
135+
dic_table['country'], dic_table['city'].c.country_id == dic_table['country'].c.country_id\
136+
).join(\
137+
dic_table['address'], dic_table['city'].c.city_id == dic_table['address'].c.city_id)\
138+
)
139+
140+
session_stmt = session.query(Country,City,Address).\
141+
join(City, Country.country_id == City.country_id).\
142+
join(Address, Address.city_id == City.city_id)
143+
```
144+
Replacing with the join() function, the outerjoin() function can be used.
145+
146+
147+
## Join with conditions
148+
149+
To start filtering according to a given criterion:
150+
- mapper option:
151+
```python
152+
mapper_stmt = select([dic_table['category'].columns.category_id,dic_table['category'].columns.name]).where(dic_table['category'].columns.name == 'Games')
153+
154+
```
155+
- session option:
156+
```python
157+
session_stmt = session.query(Country).outerjoin(City).filter(Country.country_id > 10)
158+
159+
```
160+
161+
We can use ouer conditions, such as::
162+
- or_
163+
- and_
164+
- in_
165+
- order_by
166+
- limit
167+
- etc.
168+
169+
## Join a database with relationships
170+
171+
This section presents issues related to the use of relationships described in table mapping classes. For a better understanding of the topic, a simple database will be created containing two tables of users and their posts.
172+
173+
We can create this database by code:
174+
175+
```python
176+
from sqlalchemy import create_engine, ForeignKey, Column, Integer, String
177+
from sqlalchemy.ext.declarative import declarative_base
178+
from sqlalchemy.orm import relationship, sessionmaker
179+
180+
engine = create_engine(database_url`)
181+
Base = declarative_base()
182+
Session = sessionmaker(bind = engine)
183+
session = Session()
184+
185+
class User(Base):
186+
__tablename__ = 'users'
187+
188+
id = Column(Integer, primary_key = True)
189+
name = Column(String)
190+
address = Column(String)
191+
email = Column(String)
192+
def __str__(self):
193+
return 'User id {}\n name: {}\n address: {}\n email: {}\n'.format(self.id,self.name,self.address,self.email)
194+
```
195+
196+
Class User is the simple class that described the data of the user in the system. Class Post represents posts written by a user in the system:
197+
198+
```python
199+
class Post(Base):
200+
__tablename__ = 'posts'
201+
202+
id = Column(Integer, primary_key = True)
203+
user_id = Column(Integer, ForeignKey('users.id'))
204+
post_text = Column(String)
205+
users = relationship("User", back_populates = "posts")
206+
def __str__(self):
207+
return 'Post id {}\n user_id: {}\n Post: {}\n'.format(self.id,self.user_id,self.post_text)
208+
```
209+
This class also creates a relationship between the users and posts tables.
210+
211+
The last step is to define the relation between posts and user and create the database structure:
212+
```python
213+
User.posts = relationship("Post", order_by = Post.id, back_populates = "users")
214+
Base.metadata.create_all(engine)
215+
```
216+
217+
```python
218+
data_set = [
219+
User(
220+
name = "Anna Mała",
221+
address = "Small Place",
222+
email = "am@gmail.com",
223+
posts = [Post(post_text= 'Omnia vincit Amor'), Post(post_text = 'Cogito ergo sum')]
224+
),
225+
User(
226+
name = "Marta Kwas",
227+
address = "Acid avenue",
228+
email = "acidM@gmail.com",
229+
posts = [Post(post_text= 'You see, in this world there\'s two kinds of people, my friend: Those with loaded guns and those who dig. You dig.'), Post(post_text= 'I\'m gonna make him an offer he can\'t refuse.')]
230+
),
231+
User(
232+
name = "Zofia Pompa",
233+
address = "Water street",
234+
email = "zpws@gmail.com",
235+
posts = [Post(post_text= 'Not all those who wander are lost.'), Post(post_text= 'The Answer to the ultimate question of Life, The Universe and Everything is…42!')]
236+
)
237+
]
238+
239+
session.add_all(data_set)
240+
session.commit()
241+
```
242+
After executing the query:
243+
```python
244+
result = session.query(User).join(Post).all()
245+
print(result)
246+
```
247+
We get a list of users:
248+
```
249+
[<__main__.User at 0x1b965cce048>,
250+
<__main__.User at 0x1b96541fcc8>,
251+
<__main__.User at 0x1b96540c148>]
252+
```
253+
But in this case, by corresponding relationship mapping, each user has a posts field with a list of his posts. We can print all retrieved data with the following code:
254+
255+
```python
256+
for user in result:
257+
print(user)
258+
for post in user.posts:
259+
print(post)
260+
```
261+
Expected result:
262+
```
263+
User id 1
264+
name: Anna Mała
265+
address: Small Place
266+
email: am@gmail.com
267+
268+
Post id 1
269+
user_id: 1
270+
Post: Omnia vincit Amor
271+
272+
Post id 2
273+
user_id: 1
274+
Post: Cogito ergo sum
275+
276+
User id 2
277+
name: Marta Kwas
278+
address: Acid avenue
279+
email: acidM@gmail.com
280+
281+
Post id 3
282+
user_id: 2
283+
Post: You see, in this world there's two kinds of people, my friend: Those with loaded guns and those who dig. You dig.
284+
285+
Post id 4
286+
user_id: 2
287+
Post: I'm gonna make him an offer he can't refuse.
288+
289+
User id 3
290+
name: Zofia Pompa
291+
address: Water street
292+
email: zpws@gmail.com
293+
294+
Post id 5
295+
user_id: 3
296+
Post: Not all those who wander are lost.
297+
298+
Post id 6
299+
user_id: 3
300+
Post: The Answer to the ultimate question of Life, The Universe and Everything is42!
301+
```
302+
303+
## Exercise
304+
305+
Use all of these methods to create queries for the test database. Check their execution time using the [profiling and timing code methods](https://jakevdp.github.io/PythonDataScienceHandbook/01.07-timing-and-profiling.html).
306+
307+
For queries:
308+
1. View a list of the names and surnames of managers living in the same country and working in the same store.
309+
2. Find a list of all movies of the same length.
310+
3. Find all clients living in the same city.
311+
312+
313+

Advanced databases/Lecture 10(sequence, index, search in DB )/Lecture10.ipynb

+20-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,24 @@
1515
"### dr inż. Waldemar Bauer"
1616
]
1717
},
18+
{
19+
"cell_type": "code",
20+
"execution_count": 2,
21+
"metadata": {},
22+
"outputs": [
23+
{
24+
"name": "stdout",
25+
"output_type": "stream",
26+
"text": [
27+
"Requirement already satisfied: sqlalchemy in /Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages (1.4.31)\n",
28+
"Requirement already satisfied: greenlet!=0.4.17 in /Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages (from sqlalchemy) (1.1.2)\n"
29+
]
30+
}
31+
],
32+
"source": [
33+
"!pip install sqlalchemy"
34+
]
35+
},
1836
{
1937
"cell_type": "markdown",
2038
"metadata": {
@@ -272,7 +290,7 @@
272290
"formats": "ipynb"
273291
},
274292
"kernelspec": {
275-
"display_name": "Python 3",
293+
"display_name": "Python 3 (ipykernel)",
276294
"language": "python",
277295
"name": "python3"
278296
},
@@ -286,7 +304,7 @@
286304
"name": "python",
287305
"nbconvert_exporter": "python",
288306
"pygments_lexer": "ipython3",
289-
"version": "3.7.6"
307+
"version": "3.9.10"
290308
},
291309
"rise": {
292310
"autolaunch": true,

0 commit comments

Comments
 (0)