Introduction to sqlalchemy
Object Relationship Mapping:(Object Relational Mapping) is a kind ofProgramming technology
, used to implementObject-oriented
Different in programming languagesType system
ofdata
Conversion between
From the perspective of effect, it creates aprogramming language
"Virtual Object Database" used in
Plain language: Mapping of object models and database tables
Why do you need sqlalchemy
As the project becomes larger and larger, a large number of repeated SQL statements will appear in the code by writing native SQL, so the problem will arise.
- The reuse rate of SQL statements is not high. The more complex the SQL statements have more conditions and the longer the code, many similar SQL statements will appear.
- Many SQL statements are spliced in the business. If the database needs to be changed, these logics need to be modified, and it is easy to miss some SQL modifications.
- When writing SQL, it is easy to ignore web security issues, causing hidden dangers
ORM can passkindTo operate the database in a way, without writing native database statements,Table mapping into classes,BundleOKAsExample,BundleFieldsAspropertyWhen ORM executes an object, it will eventually convert the object's operations into native statements in the database. There are many advantages to using ORM.
- Ease of use: Using ORM for database development can reduce the probability of repeating SQL statements, and the model written is more intuitive and clear
- Low performance consumption: ORM conversion into underlying database operation instructions does have some overhead, but from the actual situation, this loss is very small (less than 5%). As long as there are not strict requirements for performance, and comprehensively considering development efficiency and code reading, the benefits are much greater than the performance loss, and the larger the project, the obvious effect is.
- Flexible design: it can easily write complex queries
- Portability: SQLAlchemy encapsulates the database implementation of the stratigraphics and supports multiple relational database engines, including the popular MySQL, postgreSQL, etc., which can easily switch databases.
SQLAlchemy connection to database
from sqlalchemy import create_engine
# Database variables
HOST = "localhost"
PORT = 3306
DATA_BASE = "flask_db"
USER = "root"
PWD = "root"
# mysql driver name://username:password@address:port/database name
DB_URI = f"mysql+pymysql://{USER}:{PWD}@{HOST}:{PORT}/{DATA_BASE}"
engine = create_engine(DB_URI)
# Execute a SQL (virtual)
sql = "select 1"
# Connect to the database
conn = engine.connect()engine = create_engine(DB_URI)
sql = "create table t_user(id int primary key auto_increment,name varchar(32));"
conn = engine.connect()
conn.execute(sql)
rs = conn.execute(sql)
print(rs.fetchone())
Create a table
engine = create_engine(DB_URI)
sql = "create table t_user(id int primary key auto_increment,name varchar(32));"
conn = engine.connect()
conn.execute(sql)
For data security, you can use the with statement
engine = create_engine(DB_URI)
sql = "create table t_user1(id int primary key auto_increment,name varchar(32));"
with engine.connect() as conn:
conn.execute(sql)
Model map table structure
- use
declarative_base
Create an ORM base class BASE - Use this base class to write your own class. To define
__tablename__
Class attributes to specify the table name of this model map - use
.create_all()
Map the model to the database
from sqlalchemy import create_engine,Column,Integer,String
from sqlalchemy.ext.declarative import declarative_base
HOST = "localhost"
PORT = 3306
DATA_BASE = "flask_db"
USER = "root"
PWD = "root"
DB_URI = f"mysql+pymysql://{USER}:{PWD}@{HOST}:{PORT}/{DATA_BASE}"
engine = create_engine(DB_URI)
# Create a basic class
Base = declarative_base(engine)
class Person(Base):
__tablename__ = "t_person"
id = Column(Integer,primary_key=True,autoincrement=True)
name = Column(String(32))
age = Column(Integer)
country = Column(String(32))
# Map table structure
Base.metadata.create_all()
Notice
Once the model is mapped to the database using .create_all(), even if the model's fields are changed, it will not be remapping.
sqlalchemy's data addition, deletion, modification and search
Build session object
All ORM operations on the database must be passed
session
Implementation of session objects
from sqlalchemy import create_engine,Column,Integer,String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
HOST = "localhost"
PORT = 3306
DATA_BASE = "flask_db"
USER = "root"
PWD = "root"
DB_URI = f"mysql+pymysql://{USER}:{PWD}@{HOST}:{PORT}/{DATA_BASE}"
engine = create_engine(DB_URI)
# Create a basic class
Base = declarative_base(engine)
class Person(Base):
__tablename__ = "t_person"
id = Column(Integer,primary_key=True,autoincrement=True)
name = Column(String(32))
age = Column(Integer)
country = Column(String(32))
from sqlalchemy.orm import sessionmaker
# Create a session object
Session = sessionmaker(engine)
def create_data_one():# Add a data
with Session() as session: # If an error is reported here, please follow the new version of sqlalchemy
p1 = Person(name="Zhang San",age=18,country="China")
session.add(p1)
session.commit()
def create_data_all(): # Add multiple data
p1 = Person(name="Li Si",age=19,country="America")
p2 = Person(name="Wang Wu",age=23,country="England")
with Session() as session:
session.add_all([p1,p2])
session.commit()
def query_data(): # Query multiple data and return the name
with Session() as session:
all_person = session.query(Person).all()
for person in all_person:
print(person.name)
def query_data_one(): # Query the first data (age in the first data)
with Session() as session:
person = session.query(Person).first()
print(person.age)
def query_data_by_params(): # Add filter conditions for query
with Session() as session:
# If one uses first, multiple use all
# p1 = (Person).filter_by(name="Li Si").first() or
p1 = session.query(Person).filter(Person.name=="Li Si").first() # is a property, so you need to use a judgment operator instead of assignment
print(p1.country)
def update_data(): #Modify data
with Session() as session:
p1 = session.query(Person).filter_by(name="Zhang San").first() # Query statement first
p1.age = 30
session.commit() # Make sure to submit after modification
def delete_data(): # Delete data
with Session() as session:
p1 = session.query(Person).filter(Person.name=="Li Si").first()
session.delete(p1)
session.commit()
def delete_data_all():
with Session() as session:
all_datas = session.query(Person).all()
for data in all_datas:
session.delete(data)
session.commit()
if __name__ == '__main__':
# create_data_one()
# create_data_all()
# query_data()
# query_data_one()
# query_data_by_params()
# update_data()
# delete_data()
delete_data_all()
Common Column parameters
Set parameters in the field to complete special functions
- primary_key:True Set a field as the primary key
- autocrement:True set field to increase automatically
- default sets a field to the default value
- unllable specifies whether a field is empty
- unique specifies whether a field is unique, default False
- Onupdate will call the value or function specified by this parameter when the data is updated.
from datetime import datetime
from db_utils import * # Configured mysql file, call it at the moment
from sqlalchemy import Column,Integer,String,DateTime
class News(Base):
__tablename__ = "t_news"
id = Column(Integer,primary_key=True,autoincrement=True)
title = Column(String(32),nullable=False) # Not allowed to be empty
read_count = Column(Integer,default=1) # Reading volume
create_time = Column(DateTime,default=datetime.now) # Create time
update_time = Column(DateTime,default=datetime.now,onupdate=datetime.now) # Update time
phone = Column(String(11),unique=True) # Only account
def create_date():
new1 = News(phone="18411111111",title="Test column parameters")
with Session() as session: # If an error is reported here, upgrade sqlalchemy
session.add(new1)
session.commit()
if __name__ == '__main__':
# .create_all()# Map table structure
create_date()
Use of query functions
Filter data
There are three types of data that can be passed in the query function.
- Model name: Specify to find this modelallAttributes (already mentioned above)
- Properties in a model: You can only find several properties specified in a model
- Aggregation function:,,,, etc.
func
On the top, there is actually no aggregate function, but because it does some magic, as long as there are aggregate functions in mysql, they can be called through func
from db_utils import *
from random import randint
from sqlalchemy import Integer,String,Column,func
class Item(Base):
__tablename__ = "t_item"
id = Column(Integer,primary_key=True,autoincrement=True)
title = Column(String(32))
price = Column(Integer)
def create_data():
with Session() as sessin:
for i in range(10): # Randomly generate 10 pieces of data
item = Item(title = f"Product{i+1}",price=randint(1,100))
sessin.add(item)
sessin.commit()
def query_model_name():# Query by class name
with Session() as session:
rs = session.query(Item).all()
for r in rs:
print(r.price)
def query_model_attr():# Get the specified field query through attributes
with Session() as session:
rs = session.query(Item.title,Item.price,Item.id).all()
for r in rs:
print(f"Product ID:{r.id}-Product name:{r.title}-Product price:{r.price}")
def query_by_func():
with Session() as session:
count = session.query(func.count(Item.id)).first() #Quantity of data
price_max = session.query(func.max(Item.price)).first()
print(f" Yes{count}The highest price is{price_max}")
if __name__ == "__main__":
# .create_all()
# create_data()
# query_model_name()
# query_model_attr()
query_by_func()
Use of filter filter function
Filtering data is a very important function. The following are some commonly used filter conditions, and these filter conditions can only be implemented through filters.
- equals:== Precise matching
- Not equals:! = Complement match
- like/ilike: case insensitive
- in: within a certain range
- Not in: not within a certain range
- is null: empty
- is not null: not empty
- and: Conditional splicing
- or: one of the conditions that meet one
from sqlalchemy import Column,String,Integer,Float,Text
from random import randint
from db_utils import *
from uuid import uuid4
class Article(Base):
__tablename__ = "t_article"
id = Column(Integer,primary_key=True,autoincrement=True)
title = Column(String(32),nullable=False)
price = Column(Float,nullable=False)
content = Column(Text)
def __repr__(self):
# A magic method that can print strings instead of addresses directly
return f"<Articla(title:{self.title} price:{self.price} content:{self.content})>"
# Create a batch of data first
def create_data():
with Session() as session:
for i in range(20):
if i%2 == 0:
art = Article(title = f"title{i+1}",price=randint(1,100),content=uuid4())
else:
art = Article(title=f"TITLE{i+1}",price = randint(1,100))
session.add(art)
session.commit()
def query_data():
with Session() as session:
# rs = (Article).filter_by(id=1).first() or
rs = session.query(Article).filter(Article.id==1).first() # Recommended
print(rs)
def query_data_equal():
with Session() as session:
rs = session.query(Article).filter(Article.title=="title2").first() # Recommended
print(rs)
def query_data_equal_not():
with Session() as session:
rs = session.query(Article).filter(Article.title!="title2").all() # Recommended
print(rs)
def query_data_like():
# Fuzzy Match
with Session() as session:
# select * from t_article where title like "titl%"
rs = session.query(Article).filter(Article.title.like("%title%")).all() # Recommended
for r in rs:
print(r)
def query_data_in():
# Fuzzy Match
with Session() as session:
# The in_ here is a fixed usage to avoid keyword conflicts in Python
rs = session.query(Article).filter(Article.title.in_(["title1","title2","title3","title5","title6"])).all() # Recommended
for r in rs:
print(r)
if __name__ == "__main__":
# .create_all()
# create_data()
# query_data()
# query_data_like()
query_data_in()
Table of relationships
There are three relationships between tables: one-to-one, one-to-many, and many-to-many
ORM in sqlalchemy can simulate these three relationships
Because the one-to-one and then SQLAlchemy is simulated through a one-to-many method in the underlying layer, let’s first look at the one-to-many relationship:
Foreign keys
Creating a foreign key using sqlalchemy is very simple. Add a field from the table and specify which field of the foreign key of this field is. The fields of foreign keys from the table must be consistent with the primary keys of the primary table. useForeignkey
Keywords.
The constraints of foreign keys are as follows
- RESTRICT: If there is associated data corresponding to the parent table in the table, deleting the corresponding data corresponding to the parent table will prevent deletion.
- No ACTION: In MySQL, same as RESTRICT
- CASCADE: Cascading Deletion
- SET NULL: The corresponding data of the parent table is deleted, and the corresponding data items of the child table will be set to null
ORM relationship-one to many
sqlalchemy provides arelationship
, This class can define attributes, and in the future, when accessing the association table, it can be accessed directly through attribute access.
In addition, it can be done throughbackref
to specify the name of the property for reverse access.
from db_utils import Session,Base
from sqlalchemy import String,Column,Integer,Text,ForeignKey
from sqlalchemy.orm import relationship
class User(Base):
__tablename__ = "t_user"
id = Column(Integer,primary_key=True,autoincrement =True)
uname = Column(String(50),nullable=False,name="name")
def __repr__(self):
return f"id={self.id} uname={self.uname}"
# One-to-many ForeignKey puts the more
class News(Base):
__tablename__ = "t_news"
id = Column(Integer,primary_key=True,autoincrement =True)
title = Column(String(32),nullable=False)
content = Column(Text,nullable=False)
uid = Column(Integer,ForeignKey("t_user.id"))
user = relationship("User") # Connect two tables, here is the class name instead of the table name
def __repr__(self):
return f"News:id={self.id} content={self.content} uid={self.uid} title={self.title}"
def create_data():
user = User(uname="zs")
news1 = News(title="Python",content="Flask",uid=1)
news2 = News(title="Java",content="Spring",uid=1)
with Session() as session:
session.add(user)
session.commit()
with Session() as session:
session.add_all([news1,news2])
session.commit()
def query_data():
with Session() as session:
# select from t_news left join t_user u = where =1;
news1 = session.query(News).first()
print(news1.user) # The user in the News table connects to the User through relationship.
if __name__ == "__main__":
# .create_all()
# create_data()
query_data()
ORM relationship-one to one
In sqlalchemy, if you want to map two models into a one-to-one relationship, you should pass one when specifying a reference in the parent model.uselist=False
This parameter is entered. It tells the parent model that when referring to this slave model in the future, it is no longer a list but an object.
from db_utils import Session,Base
from sqlalchemy import Column, ForeignKey,String,Integer
from sqlalchemy.orm import relationship
class LoginUser(Base):
__tablename__ = "t_user_login"
id = Column(Integer,primary_key=True,autoincrement=True)
uname=Column(String(32),nullable=False)
passwd = Column(String(32),nullable=False)
user = relationship("User",uselist=False) # To pass uselist=False in parent table
def __repr__(self):
return f"LoginUser:id={self.id} uname={self.uname} passwd={self.passwd}"
# Create a one-to-one relationship, and create a field as a representation of the table (foreign key)
class User(Base):
__tablename__ = "t_user"
id = Column(Integer,primary_key = True,autoincrement=True)
name = Column(String(32),nullable=False,name="name")
gender = Column(String(1))
address = Column(String(64))
login_id = Column(Integer,ForeignKey("t_user_login.id"))
def __repr__(self):
return f"User id={self.id} name={self.name} gender={self.gender} address={self.address}"
def create_data():
login = LoginUser(uname="zs",passwd="123")
user = User(name="Zhang San",gender="male",address="Confidential")
login.user = user
with Session() as session:
session.add(login)
session.commit()
def query_data():
with Session() as session:
login = session.query(LoginUser).first()
print(login.user)
if __name__ == "__main__":
# .create_all()
# create_data()
query_data()
ORM Relationship - Many to Many
Many people buy each product, and one person can choose from a variety of products.
- A many-to-many relationship requires an intermediate table to bind their relationships
- First define the two models, then use Table to define an intermediate table. The intermediate table generally includes the foreign key fields of the two models, and let them serve as one
Joint primary key
- Choose one of the two many-to-many models and define one
relationship
Attributes to bind the relationship between these three. When using relationship, you need to pass insecondary=Intermediate table object name
from db_utils import Session,Base
from sqlalchemy.orm import relationship
from sqlalchemy import Column,String,Integer,ForeignKey,Table
"""
One news can have multiple tags.
One tag can correspond to multiple news.
"""
# The third table should be placed on top of two models to correlate the two models
news_tag = Table(
"t_news_tag",
Base.metadata,
Column("news_id",Integer,ForeignKey("t_news.id"),primary_key=True),
Column("tag_id",Integer,ForeignKey("t_tag.id"),primary_key=True)
)
class New(Base):
__tablename__ = "t_news"
id = Column(Integer,primary_key=True,autoincrement=True)
title = Column(String(32),nullable=False)
tags = relationship("Tag",backref="newss",secondary=news_tag) # The third table model name instead of the table name, you can also write another table
def __repr__(self):
return f"New: id={self.id} title={self.title}"
class Tag(Base):
__tablename__ = "t_tag"
id = Column(Integer,primary_key=True,autoincrement=True)
name = Column(String(32),nullable=False)
def __repr__(self):
return f"Tag id={self.id} name={self.name}"
def create_data():
new1 = New(title="This is title 1")
new2 = New(title="This is Title 2")
tag1 = Tag(name="Tag 1")
tag2 = Tag(name="Tag 2")
new1.tags.append(tag1)
new1.tags.append(tag2)
new2.tags.append(tag1)
new2.tags.append(tag2)
with Session() as session:
session.add(new1)
session.add(new2)
session.commit()
def query_data():
with Session() as session:
new = session.query(New).first()
print(new.tags)
if __name__ == "__main__":
# .create_all()
# create_data()
query_data()
SQLAlchemy cascaded data operation parameters
cascade in relation method at the ORM level
In sqlalchemy, as long as one piece of data is added to the session, the data associated with it can be stored in the database together.
How are these set up? In fact, when using relationship, there is a keyword parameter cascade that can set these properties.
The value of the cascade attribute is:
- save-updatae: default option. When adding a piece of data, all other related data will be added to the database. This behavior is affected by save-update.
- delete: indicates whether the data associated with the relationship is also deleted when deleting data in a certain model.
- delete-orphan: means that when an ORM removes the relational object in the parent table, it will be deleted.
- merge: default option, when using
When merging an object, the object associated with relationship is also merged.
- expunge: When removing the operation, the associated object will be removed. This operation can only be removed from the session and will not be deleted from the database.
- all: It is abbreviation for save-update, merge, refresh-expire, expunge, delete.
from unicodedata import name
from db_utils import Session,Base
from sqlalchemy import Column,String,Integer,ForeignKey
from sqlalchemy.orm import relationship
class User(Base):
__tablename__ = "t_user"
id = Column(Integer,primary_key=True,autoincrement=True)
name = Column(String(32))
def __repr__(self):
return f"User:id={self.id} name={self.name}"
class Article(Base):
__tablename__ = "t_article"
id = Column(Integer,primary_key=True,autoincrement=True)
title = Column(String(32))
uid = Column(Integer,ForeignKey("t_user.id"))
user = relationship("User",backref="articles",cascade="save-update")
def create_data():
Base.metadata.drop_all() # Delete existing tables
Base.metadata.create_all()
# Initialize data
user = User(name="Zhang San")
art1 = Article(title="This is title one",uid=1)
art2 = Article(title="This is title two",uid=1)
user.articles.append(art1)
user.articles.append(art2)
with Session() as session:
session.add(user)
session.commit()
if __name__ == "__main__":
create_data()
Flask-SQLAlchemy usage
flask-sqlalchemy is a plug-in that makes it simple to encapsulate sqlalchemy in flask easier.
pip install flask-sqlalchemy
Key points of using Flask-sqlalchemy
Database connection
Create_engine is no longer used in database initialization
-
Like sqlalchemy, define the database connection string DB_URI
-
Pass this defined database through
SQLALCHEMY_DATABASE_URI
Configure tomiddle
["SQLALCHEMY_DATABASE_URI"]=DB_URI
-
use
flask_sqlalchemy.SQLAlchemy
This class defines an object and passes the app intodb=SQLAlchemy(app)
Create an ORM model class
Previously, a base class was initialized through Base=declarative_base() and then inherited. And it's easier in flask-sqlalchemy
- It is still the same as using sqlalchemy, defining the model class. Now there is no need to create it with declarative_base(). Instead use
db_Model
As the base class. - In the model class, Column, String, Integer, and relationship no longer need to be imported. Just use the attribute name under db (
)
- When defining a model, you don't need to write
__tablename__
, flask will use the name of the current model by default and convert it to lowercase as the name of the table.And if this model uses multiple words and uses camel nomenclature, it will add underscores between multiple words to connect (but is not recommended)
Map the ORM model to the database table
After writing the model, to map the model to the database table, use the following code:
- Delete database tables
db.drop_all()
- Create a database table
db.create_all()
Use of session
session is no longer used in flask-sqlalchemysessionmaker
Created, use it directlyThat's right, the operation of this session is the same as before
Add data
There is no difference before adding data, just the session becomes a db property
Query data
-
Single table query: Data query is no longer the previous one
()
, instead put the query attribute on it, so the query is passedModel name.query
The method has been queried,query
Same as before -
Multi-table query
If the search data involves multiple models, you can only use the method (model name).all()
Modify data
There is no difference from before, just session has become a property of db
Delete data
There is no difference from before, just session has become a property of db
Using flask-sqlalchemy
from operator import ne
from flask import Flask, session
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
# Database variables
HOST = "localhost"
PORT = 3306
DATA_BASE = "flask_db"
USER = "root"
PWD = "root"
# mysql driver name://username:password@address:port/database name
DB_URI = f"mysql+pymysql://{USER}:{PWD}@{HOST}:{PORT}/{DATA_BASE}"
app.config["SQLALCHEMY_DATABASE_URI"] = DB_URI
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False # If you do not add this, a warning will be reported (not an error)
# Connect to the database
db = SQLAlchemy(app)
# Create a model
class User(db.Model):
__tablename__ = "t_user" # This is not available, but it is recommended to use it
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
name = db.Column(db.String(32))
def __repr__(self):
return f"User:id={self.id},name={self.name}"
class News(db.Model):
__tablename__ = "t_news" # This is not available, but it is recommended to use it
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
content = db.Column(db.String(256))
uid = db.Column(db.Integer,db.ForeignKey("t_user.id"))
user = db.relationship("User",backref="news")
def __repr__(self):
return f"News:id={self.id},content={self.content},uid={self.uid}"
# Delete table
# db.drop_all()
# Create a table
# db.create_all()
# Add data
def create_data():
user = User(name="Zhang San")
news=News(content="This is what Zhang San wrote")
# Create an association
user.news.append(news)
# Add to session
db.session.add(user)
db.session.commit()
# Query single table data
def query_data_one():
users = User.query.all()
print(users)
# Query multi-table data
def query_data_many():
rs = db.session.query(User,News.content).join(News,News.uid == User.id).all()
print(rs)
# Modify data
def update_data():
user = User.query.first()
user.name = "Li Si"
db.session.commit()
# Delete data
def delete_data():
news = News.query.first()
db.session.delete(news)
db.session.commit()
if __name__ == "__main__":
# create_data()
# query_data_one()
# query_data_many()
# update_data()
delete_data()
Use of alembic
alembic is a database migration tool that is used to migrate and map ORM models and databases.
Alembic uses a bit similar to git
All alembic commands start with almbic
pip install alembic
#
from sqlalchemy import create_engine,Column,String,Integer
from sqlalchemy.ext.declarative import declarative_base
# Database information
HOST = "localhost"
PORT = 3306
DATA_BASE = "flask_db"
USER = "root"
PWD = "root"
# mysql driver name://username:password@address:port/database name
DB_URI = f"mysql+pymysql://{USER}:{PWD}@{HOST}:{PORT}/{DATA_BASE}"
engine = create_engine(DB_URI)
Base = declarative_base(engine)
class User(Base):
__tablename__ = "t_user"
id = Column(Integer,primary_key=True,autoincrement=True)
name = Column(String(32))
initialization
cdIn the current folder
alembic init Take a folder name
After the command is input, a folder will be generated, and the name of the folder is the name after init
Configuration information
- Modify in .ini file
Information (in more than fifty lines), change it to your own database information
- Modify the content to be mapped in the file
import os,sys
sys.path.append(os.path.dirname(os.path.dirname(__name__)))
import alembic_demo
target_metadata = alembic_demo.Base.metadata
Generate migration files
Enter the command line
alembic revision --autogenerate -m "model_file"
--autogenerate
Automatic update-m "model_file"
Add prompt information
After executing the command, theversionsFolder generates a migration filedf935ec6b6c2_model_file.py
, the alphanumeric preceding is the version numberdf935ec6b6c2
Mapping table structure
Again, although a version information table would appear in the database, the table structure has not been mapped yet and there is no information in the table. Only after the table structure has been mapped, the version number will be
Appears in the information table.
On the command line
alembic upgrade head
Flask-SQLAlchemy and alembic use
Most operations are the same
target_metadata = app_demo.