PolarSPARC |
Introduction to SQLAlchemy :: Part - 3
Bhaskar S | 04/25/2020 |
Overview
In Part - 2 of this series, we began exploring the basics of the the Object Relational Mapping (ORM) layer for interacting with PostgreSQL database, as well as introduced the one-to-many relationship between the customer and account entities.
In this article, we will explore the remaining two entity relationships - one-to-one and many-to-many.
Hands-on with SQLAlchemy ORM Relationships
In Part - 1, we created a simple database table called customer and in Part - 2, we created another simple database table called account with a one-to-many relationship between them. In this demonstration, we will create yet another database table called kyc with a one-to-one relationship with both the account and customer tables. The following diagram illustrates this relationship:
The class Kyc defined in the following Python program (ex_sa_09.py) correspond to the database table kyc:
The following are brief descriptions for some of the Python classes and methods:
onupdate :: Sets the value of the column kyc_update_dt with the current timestamp on row update
uselist=False :: Keyword creates a one-to-one relationship with the specified domain model object name
In following Python program (ex_sa_10.py), the method create_kyc_table creates the kyc database table and the method insert_kyc_recs inserts *3* sample rows for the associated account and customer:
To run the Python program ex_sa_10.py, execute the following command:
$ python ex_sa_10.py
The following would be a typical output:
2020-04-24 21:31:35,372 - Postgres database url: postgres://polarsparc:polarsparc$123@localhost:5432/my_test_db 2020-04-24 21:31:35,459 - Connected to the Postgres database !!! 2020-04-24 21:31:35,489 - Created the kyc table !!! 2020-04-24 21:31:35,506 - Inserted kyc for Alice 2020-04-24 21:31:35,509 - Inserted kyc for Bob 2020-04-24 21:31:35,511 - Inserted kyc for Charlie
The following Python program (ex_sa_11.py) demonstrates the query and update operations on the kyc database table.
The method query_kyc queries all the records, the method query_kyc_order queries all the records and sorts them in a descending order by the update date, and finally the method update_kyc updates a row by the last name of the associated customer.
The following are brief descriptions for some of the Python classes and methods:
Query.all() :: Method that selects all the rows from the specified database table
Query.order_by() :: Method that orders the selected rows in ascending order (by default) on the specified column
desc() :: Method on a database column to indicate the default order needs to be reversed (descending order)
has() :: Method that selects row(s) that match the specified criteria on the specified database column
To run the Python program ex_sa_11.py, execute the following command:
$ python ex_sa_11.py
The following would be a typical output:
2020-04-24 21:36:16,810 - Postgres database url: postgres://polarsparc:polarsparc$123@localhost:5432/my_test_db 2020-04-24 21:36:16,866 - Connected to the Postgres database !!! 2020-04-24 21:36:16,879 - [Kyc: kyc_flag=True, kyc_update_dt=2020-04-24 20:11:35.501991, customer=Doctor, account=Alice Trade Account] 2020-04-24 21:36:16,883 - [Kyc: kyc_flag=False, kyc_update_dt=2020-04-24 20:11:35.507191, customer=Builder, account=Bob Credit Account] 2020-04-24 21:36:16,884 - [Kyc: kyc_flag=False, kyc_update_dt=2020-04-24 20:11:35.509393, customer=Driver, account=Charlie Trade Account] 2020-04-24 21:36:16,893 - Updated record for Customer 'Driver' 2020-04-24 21:36:16,897 - [Kyc: kyc_flag=True, kyc_update_dt=2020-04-24 20:11:35.501991, customer=Doctor, account=Alice Trade Account] 2020-04-24 21:36:16,898 - [Kyc: kyc_flag=False, kyc_update_dt=2020-04-24 20:11:35.507191, customer=Builder, account=Bob Credit Account] 2020-04-24 21:36:16,899 - [Kyc: kyc_flag=True, kyc_update_dt=2020-04-24 21:36:16.891167, customer=Driver, account=Charlie Trade Account] 2020-04-24 21:36:16,905 - Updated record for Customer 'Driver' 2020-04-24 21:36:16,910 - Updated record for Customer 'Builder' 2020-04-24 21:36:17,920 - Updated record for Customer 'Builder' 2020-04-24 21:36:17,929 - [Kyc: kyc_flag=False, kyc_update_dt=2020-04-24 21:36:17.917521, customer=Builder, account=Bob Credit Account] 2020-04-24 21:36:17,930 - [Kyc: kyc_flag=False, kyc_update_dt=2020-04-24 21:36:16.903741, customer=Driver, account=Charlie Trade Account] 2020-04-24 21:36:17,931 - [Kyc: kyc_flag=True, kyc_update_dt=2020-04-24 20:11:35.501991, customer=Doctor, account=Alice Trade Account]
In Part - 1, we created a simple database table called securities. In this demonstration, we will create another database table called trades with a many-to-many relationship with both the account and securities tables. In other words, the database table trades acts as a join table between the account and the securities tables. The following diagram illustrates this relationship:
The classes Account, Securities, and Trades defined in the following Python program (ex_sa_12.py) correspond to the database tables account, securities, and trades respectively:
The following are brief descriptions for some of the Python keyword(s):
secondary :: Keyword that associates the specified name as the join table in forming the many-to-many relationship between the related tables
In following Python program (ex_sa_13.py), the method create_trades_table creates the trades database table, the method insert_trades inserts *6* sample trades, and the method query_trades performs a query of the trades:
The following are brief descriptions for some of the Python classes and methods:
add_all() :: Method that takes a list of domain model objects to add to the Session for persistence
Query.join() :: Method to perform a join on the mapped domain model objects using the foreign key relationships between them
Query.select_from() :: Method to control the 'left' side of the join
Ensure the method select_from() is used to indicate the table on the left to perform the joins. Else will encounter the following error:
Can't determine which FROM clause to join from, there are multiple FROMS which can join to this entity. Please use the .select_from() method to establish an explicit left side, as well as providing an explcit ON clause if not present already to help resolve the ambiguity
To run the Python program ex_sa_13.py, execute the following command:
$ python ex_sa_13.py
The following would be a typical output:
2020-04-24 21:47:29,353 - Postgres database url: postgres://polarsparc:polarsparc$123@localhost:5432/my_test_db 2020-04-24 21:47:29,435 - Connected to the Postgres database !!! 2020-04-24 21:47:29,462 - Created the trades table !!! 2020-04-24 21:47:29,487 - Inserted record(s) for 6 trades: 2020-04-24 21:47:29,497 - ==> [Trades: customer=Alice Doctor, trade_dt=2020-04-24 21:47:29.478902, trade_type=B, quantity=100, security=BULL.ST] 2020-04-24 21:47:29,498 - ==> [Trades: customer=Alice Doctor, trade_dt=2020-04-24 21:47:29.482047, trade_type=B, quantity=300, security=BARK.ST] 2020-04-24 21:47:29,500 - ==> [Trades: customer=Charlie Driver, trade_dt=2020-04-24 21:47:29.482694, trade_type=B, quantity=50, security=BULL.ST] 2020-04-24 21:47:29,502 - ==> [Trades: customer=Charlie Driver, trade_dt=2020-04-24 21:47:29.483480, trade_type=B, quantity=150, security=DOG.ST] 2020-04-24 21:47:29,503 - ==> [Trades: customer=Alice Doctor, trade_dt=2020-04-24 21:47:29.484275, trade_type=S, quantity=100, security=BARK.ST] 2020-04-24 21:47:29,504 - ==> [Trades: customer=Charlie Driver, trade_dt=2020-04-24 21:47:29.485013, trade_type=S, quantity=50, security=DOG.ST] 2020-04-24 21:47:29,513 - SQL => SELECT customer.first_name AS customer_first_name, customer.last_name AS customer_last_name, account.acct_name AS account_acct_name, trades.trade_dt AS trades_trade_dt, trades.trade_type AS trades_trade_type, trades.quantity AS trades_quantity, securities.symbol AS securities_symbol, securities.price AS securities_price FROM trades JOIN account ON account.acct_no = trades.aid JOIN securities ON securities.id = trades.sid JOIN customer ON customer.id = account.cust_id 2020-04-24 21:47:29,519 - < ------------------------- 2020-04-24 21:47:29,520 - ('Alice', 'Doctor', 'Alice Trade Account', datetime.datetime(2020, 4, 25, 13, 47, 29, 478902), 'B', 100, 'BULL.ST', Decimal('25.75')) 2020-04-24 21:47:29,520 - ('Alice', 'Doctor', 'Alice Trade Account', datetime.datetime(2020, 4, 25, 13, 47, 29, 482047), 'B', 300, 'BARK.ST', Decimal('144.90')) 2020-04-24 21:47:29,520 - ('Charlie', 'Driver', 'Charlie Trade Account', datetime.datetime(2020, 4, 25, 13, 47, 29, 482694), 'B', 50, 'BULL.ST', Decimal('25.75')) 2020-04-24 21:47:29,520 - ('Charlie', 'Driver', 'Charlie Trade Account', datetime.datetime(2020, 4, 25, 13, 47, 29, 483480), 'B', 150, 'DOG.ST', Decimal('54.15')) 2020-04-24 21:47:29,521 - ('Alice', 'Doctor', 'Alice Trade Account', datetime.datetime(2020, 4, 25, 13, 47, 29, 484275), 'S', 100, 'BARK.ST', Decimal('144.90')) 2020-04-24 21:47:29,521 - ('Charlie', 'Driver', 'Charlie Trade Account', datetime.datetime(2020, 4, 25, 13, 47, 29, 485013), 'S', 50, 'DOG.ST', Decimal('54.15')) 2020-04-24 21:47:29,521 - ------------------------- >
This concludes the exploration of the basic capabilities in the SQLAlchemy ORM layer.
References
Introduction to SQLAlchemy :: Part - 1
Introduction to SQLAlchemy :: Part - 2