sqlalchemy ORM

ORM介紹

orm英文全稱object relational mapping,就是對象映射關係程序,簡單來講咱們相似python這種面向對象的程序來講一切皆對象,可是咱們使用的數據庫卻都是關係型的,爲了保證一致的使用習慣,經過orm將編程語言的對象模型和數據庫的關係模型創建映射關係,這樣咱們在使用編程語言對數據庫進行操做的時候能夠直接使用編程語言的對象模型進行操做就能夠了,而不用直接使用sql語言。html

orm的優勢:python

  1. 隱藏了數據訪問細節,「封閉」的通用數據庫交互,ORM的核心。他使得咱們的通用數據庫交互變得簡單易行,而且徹底不用考慮該死的SQL語句。快速開發,由此而來。
  2. ORM使咱們構造固化數據結構變得簡單易行。

缺點:mysql

  1. 無可避免的,自動化意味着映射和關聯管理,代價是犧牲性能(早期,這是全部不喜歡ORM人的共同點)。如今的各類ORM框架都在嘗試使用各類方法來減輕這塊(LazyLoad,Cache),效果仍是很顯著的。

在Python中,最有名的ORM框架是SQLAlchemy。用戶包括openstack\Dropbox等知名公司或應用,主要用戶列表http://www.sqlalchemy.org/organizations.html#openstacklinux

Dialect用於和數據API進行交流,根據配置文件的不一樣調用不一樣的數據庫API,從而實現對數據庫的操做,如:sql

MySQL-Python
    mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
   
pymysql
    mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
   
MySQL-Connector
    mysql+mysqlconnector://<user>:<password>@<host>[:<port>]/<dbname>
   
cx_Oracle
    oracle+cx_oracle://user:pass@host:port/dbname[?key=value&key=value...]
   
更多詳見:http://docs.sqlalchemy.org/en/latest/dialects/index.html
http://docs.sqlalchemy.org/en/latest/core/engines.html#sqlalchemy.create_engine

  安裝sqlalchemy數據庫

pip install SQLAlchemy
pip install pymysql  #因爲mysqldb依然不支持py3,因此這裏咱們用pymysql與sqlalchemy交互

 

3.sqlalchemy基本使用

  當咱們使用ORM時,首先咱們要處理數據庫表,而後定義咱們本身的類來映射到這些表,在sqlachemy中,這兩個任務一般一塊兒被執行,用一個系統知道的 Declarative(聲明),這個聲明能容許咱們建立類來描述實際的數據庫表而後映射到數據庫。編程

聲明基類session

from sqlalchemy.ext.declarative import declarative_base

base = declarative_base() #聲明基類

 如今假設咱們要在mysql建立一個這樣的user類 數據結構

CREATE TABLE user (
    id INTEGER NOT NULL AUTO_INCREMENT,
    name VARCHAR(32),
    password VARCHAR(64),
    PRIMARY KEY (id)
)

 用python是怎麼實現的呢?oracle

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy import Column,String,Integer

Base = declarative_base() #聲明基類
engine = create_engine("mysql+pymysql://root:123456@localhost:3306/testdb?charset=utf8",echo = True) #echo = True打印生成對象的全部過程,若是要插入中文,必定要加?charset=utf8

class User(Base):
    """
    定義表結構,映射到數據庫中
    """
    __tablename__ = 'user' #表名
    id = Column(Integer,primary_key=True)
    fullname = Column(String(32))
    password = Column(String(64))

Base.metadata.create_all(engine)  #建立表結構

#在打印出來的日誌中咱們看到已經有正常建立
CREATE TABLE user (
	id INTEGER NOT NULL AUTO_INCREMENT, 
	fullname VARCHAR(32), 
	password VARCHAR(64), 
	PRIMARY KEY (id)
)
登錄到mysql中,查看已經正常建立。  

當咱們表建立完成,再運行上面的代碼並不會重複建立。

當咱們與數據庫交互時,咱們要定義一個session做爲會話,咱們定義一個Session_class類來做爲新的session對象的工廠,下面咱們開始添加數據

from sqlalchemy.orm import sessionmaker


Session_class = sessionmaker(bind = engine)  #建立與數據庫的會話session class ,注意,這裏session_class是類,不是實例
Session = Session_class() #生成Session實例

  

插入數據

from sqlalchemy.orm import sessionmaker

Session_class = sessionmaker(bind = engine)  #建立與數據庫的會話session class ,注意,這裏session_class是類,不是實例
Session = Session_class() #生成Session實例

user_obj = User(fullname = 'lxj',password = '123456')  #生成你要建立的數據對象
Session.add(user_obj)   #添加數據

print(user_obj.fullname,user_obj.id)     #這裏id仍是爲None,說明數據還未添加成功

Session.commit()
print(user_obj.id)   #這裏id就有值了,說明正常添加  

插入多條數據

from sqlalchemy.orm import sessionmaker

Session_class = sessionmaker(bind = engine)  #建立與數據庫的會話session class ,注意,這裏session_class是類,不是實例
Session = Session_class() #生成Session實例

user_obj = User(fullname = 'sx',password = '123456')  #生成你要建立的數據對象
user_obj2 = User(fullname = 'sx1',password = '123456')  #生成你要建立的數據對象
Session.add_all([user_obj,user_obj2])  #添加多條數據

Session.commit()

  

查詢

class User(Base):
    """
    定義表結構,映射到數據庫中
    """
    __tablename__ = 'user' #表名
    id = Column(Integer,primary_key=True)
    fullname = Column(String(32))
    password = Column(String(64))

    def __repr__(self):   #對實例化的對象格式化輸出
        return "<User(fullname='%s', password='%s')>" % (
                                self.fullname, self.password)

Base.metadata.create_all(engine)  #建立表結構

from sqlalchemy.orm import sessionmaker

Session_class = sessionmaker(bind = engine)  #建立與數據庫的會話session class ,注意,這裏session_class是類,不是實例
Session = Session_class() #生成Session實例


user_objs = Session.query(User)  #查詢全部數據,類型select * from 表名,這是一個可迭代的對象
print(user_objs)       
for user_obj in user_objs:  #循環讀取每條數據
    print(user_obj)


Session.commit()
#輸出結果
SELECT user.id AS user_id, user.fullname AS user_fullname, user.password AS user_password 
FROM user
<User(fullname='lxj', password='123456')>
<User(fullname='sx', password='123456')>
<User(fullname='sx1', password='123456')>  

查詢多列

user_objs = Session.query(User.id,User.fullname) #查詢id和fullname列
for user_obj in user_objs:
    print(user_obj)

#結果:
(1, 'lxj')
(2, 'sx')
(3, 'sx1')
(4, 'lxj')

  查詢重複項

# del_user = Session.query(User.fullname).distinct().all()  #query的distinct用法
del_user = Session.query(distinct(User.fullname)).all()  #from sqlalchemy import distinct 外部導入distinct用法
print(del_user)
Session.commit()

  

過濾數據,爲了讓咱們例子更形象化,再添加一條數據,User(fullname = 'lxj',password = '123')

#對中間這部分進行修改
user_objs = Session.query(User).filter_by(fullname = 'lxj') #過濾數據,至關於select * from User where fullname = 'lxj',這是一個可迭代的對象

for user_obj in user_objs:  #循環讀取每條數據
    print(user_obj)

#結果
<User(fullname='lxj', password='123456')>
<User(fullname='lxj', password='123')>

 多條件過濾實現and和or

user_objs = Session.query(User).filter_by(fullname = 'lxj',password = '123') #過濾數據,至關於select * from User where fullname = 'lxj' and password = '123',這是一個可迭代的對象

#user_objs = Session.query(User).filter(User.fullname=='lxj',User.password == '123') #也能夠這麼寫

for user_obj in user_objs:  #循環讀取每條數據
    print(user_obj)

#結果:<User(fullname='lxj', password='123')>


#實現or
from sqlalchemy.orm import sessionmaker
from sqlalchemy import or_

Session_class = sessionmaker(bind = engine)  #建立與數據庫的會話session class ,注意,這裏session_class是類,不是實例
Session = Session_class() #生成Session實例


user_objs = Session.query(User).filter(or_(User.fullname=='lxj',User.fullname=='sx'))#過濾數據,至關於select * from User where fullname = 'lxj',這是一個可迭代的對象

for user_obj in user_objs:  #循環讀取每條數據
    print(user_obj)

# user_obj = User(fullname = 'lxj',password = '123')
# Session.add(user_obj)

Session.commit()

#結果:
<User(fullname='lxj', password='123456')>
<User(fullname='sx', password='123456')>
<User(fullname='lxj', password='123')>

  讀取第一條數據

user_obj = Session.query(User).filter(or_(User.fullname=='lxj',User.fullname=='sx')).first()
print(user_obj)
#結果:<User(fullname='lxj', password='123456')>

  後面query後面還能夠搭配好多種函數,如limit,offset等,可使用dir(user_obj)或則看源碼來查看對象下面能調用的方法。這裏調用函數的方法大體要和咱們編寫sql語句的順序一致。

修改  

user_obj = Session.query(User).filter(User.fullname=='sx').first()
print(user_obj)

user_obj.password = '12345'   #對查詢出來的數據修改
print(user_obj)

Session.commit()
#結果
<User(fullname='sx', password='123')>
<User(fullname='sx', password='12345')>

#Session.query(User).filter(User.fullname=='sx')查詢出來的結果是一個可迭代的對象,並不能直接修改,要麼就是經過for循環一條條取出數據來修改,要麼就像這個例子同樣取出一條數據來修改

刪除

刪除與添加十分相似

user_obj = Session.query(User).filter(User.fullname=='sjj').first() #找到要刪除的數據
Session.delete(user_obj)  #刪除
 

Session.commit()

 回溯 

Session_class = sessionmaker(bind = engine)  #建立與數據庫的會話session class ,注意,這裏session_class是類,不是實例
Session = Session_class() #生成Session實例


user_obj = Session.query(User).filter(User.fullname=='sx').first()
user_obj.fullname = "spp"

fake_user = User(fullname = 'pll',password = '123')
Session.add(fake_user)

print(Session.query(User).filter(User.fullname.in_(['spp','pll'])).all())  #in_同python中關鍵字in同樣的功能此時看Session裏有你添加和修改的數據

Session.rollback()  #回溯

print(Session.query(User).filter(User.fullname.in_(['spp','pll'])).all()) #已經沒有記錄了
# Session.commit()

 統計和分組

 

user_objs = Session.query(User).filter(User.fullname.like("%s%")).all()   #all()取出全部數據,並以列表的形式返回
count = Session.query(User).filter(User.fullname.like("%s%")).count()  #統計條目
print(user_objs,count)
Session.commit()
結果:
[<User(fullname='sx', password='12345')>, <User(fullname='sx', password='1234')>, <User(fullname='lss', password='1234')>, <User(fullname='lss', password='123')>]  4

  分組

from sqlalchemy import func

user = Session.query(User.fullname,func.count(User.fullname)).filter(User.fullname == 'lxj').group_by(User.fullname).all()
#上面這條語句至關於select fullname,count(fullname) from user where fullname = 'lxj' group by fullname;
print(user)
Session.commit()
#結果
[('lxj', 4)]
user = Session.query(User.fullname,func.count(User.fullname),func.max(User.id)).group_by(User.fullname).all()        #分組查詢
print(user)

#結果:
[('lss', 2, 10), ('lxj', 4, 7), ('sx', 2, 8)]

  

  

一些聚合函數的用法

user = Session.query(func.max(User.id)).one()  #one與first的區別是,one()返回且僅返回一個查詢結果。當結果的數量不足一個或者多於一個時會報錯
#上面這條語句至關於select fullname,count(fullname) from user where fullname = 'lxj' group by fullname;
print(user)
Session.commit()
#結果:
10

#還有min,sum聚合函數也是相似的用法。

Using Textual SQL 

文本字符串能夠靈活地用於查詢,經過指定它們與text()結構的使用,這被大多數適用的方法所接受。 例如,filter()和order_by():

from sqlalchemy import text
user_objs = Session.query(User).filter(text("id>5")).order_by(text("id desc")).all()   #支持字符串的形式查詢,查詢出來的結果按降序顯示
# user_objs = Session.query(User).filter(User.id>5).order_by(User.id.desc()).all()  #等同於上面這句
print(user_objs)

Session.commit()  

聯表查詢

首先咱們對數據庫的usertest作個表映射,方法同以前同樣

class UserTest(Base):
    __tablename__ = 'usertest'  # 表名
    id = Column(Integer, primary_key=True)
    name = Column(String(32))
    password = Column(String(64))

    def __repr__(self):  # 對實例化的對象格式化輸出
        return "<User(id='%s',fullname='%s', password='%s')>" % (
            self.id, self.fullname, self.password)

 

user_obj = Session.query(User,UserTest).filter(User.fullname == UserTest.name,User.password == UserTest.password).all() #聯表user和usertest一同查詢
#等同於執行select * from user,usertest where user.fullname = usertest.name and user.password = usertest.password;
print(user_obj,len(user_obj))
Session.commit()

#將結果
[(<User(id='1',fullname='lxj', password='123456')>, <User(id='1',fullname='lxj', password='123456')>), (<User(id='2',fullname='sx', password='12345')>, <User(id='5',fullname='sx', password='12345')>), (<User(id='7',fullname='lxj', password='1234')>, <User(id='3',fullname='lxj', password='1234')>)] 3

  

在mysql操做中,聯表查詢還用到join,咱們看看sqlalchemy中的join方法(left join, right join, inner join ,full join)

inner join
user = Session.query(User).join(UserTest).all()
print(user)

#結果直接報錯了
sqlalchemy.exc.InvalidRequestError: Could not find a FROM clause to join from.  Tried joining to <class '__main__.UserTest'>, but got: Can't find any foreign key relationships between 'user' and 'usertest'

緣由是沒有定義外鍵,在sqlalchemy中join用在定義外鍵聯表查詢使用,這個咱們下面會說,如今咱們來看下無關聯的查詢方式

inner join - 交集
result = Session.query(User).join(UserTest,UserTest.password == User.password)
print(result)
for record in result:
    print(record)
#結果:
SELECT user.id AS user_id, user.fullname AS user_fullname, user.password AS user_password 
FROM user INNER JOIN usertest ON usertest.password = user.password
<User(id='1',fullname='lxj', password='123456')>
<User(id='2',fullname='sx', password='12345')>
<User(id='7',fullname='lxj', password='1234')>
<User(id='8',fullname='sx', password='1234')>
<User(id='9',fullname='lss', password='1234')>

在結果中,咱們看到實際執行的就是inner join語句

 left join

result = Session.query(User,UserTest).outerjoin(UserTest,UserTest.id == User.id) 
print(result)
for record in result:
    print(record)

#以該例爲例子left join就是user顯示所有行,usertest中若是數據在user表不在usertest表中,就顯示NUll,python中就顯示None

第二種寫法:result = Session.query(User,UserTest).join(UserTest,UserTest.id == User.id,isouter=True) 


結果:
SELECT user.id AS user_id, user.fullname AS user_fullname, user.password AS user_password, usertest.id AS usertest_id, usertest.name AS usertest_name, usertest.password AS usertest_password 
FROM user LEFT OUTER JOIN usertest ON usertest.id = user.id
(<User(id='1',fullname='lxj', password='123456')>, <User(id='1',fullname='lxj', password='123456')>)
(<User(id='2',fullname='sx', password='12345')>, <User(id='2',fullname='lxj', password='12345')>)
(<User(id='4',fullname='lxj', password='123')>, <User(id='4',fullname='lxj', password='12345')>)
(<User(id='6',fullname='lxj', password='123')>, <User(id='6',fullname='sx', password='12346')>)
(<User(id='7',fullname='lxj', password='1234')>, <User(id='7',fullname='sxaa', password='12346')>)
(<User(id='8',fullname='sx', password='1234')>, <User(id='8',fullname='sxs', password='12346')>)
(<User(id='9',fullname='lss', password='1234')>, None)
(<User(id='10',fullname='lss', password='123')>, None)

  

 right join 與left join相似,把位置換下便可

result = Session.query(UserTest,User).outerjoin(User,UserTest.id == User.id) #若是數據在user表中,不在usertest表,
print(result)
for record in result:
    print(record)

#結果:
SELECT usertest.id AS usertest_id, usertest.name AS usertest_name, usertest.password AS usertest_password, user.id AS user_id, user.fullname AS user_fullname, user.password AS user_password 
FROM usertest LEFT OUTER JOIN user ON usertest.id = user.id
(<User(id='1',fullname='lxj', password='123456')>, <User(id='1',fullname='lxj', password='123456')>)
(<User(id='2',fullname='lxj', password='12345')>, <User(id='2',fullname='sx', password='12345')>)
(<User(id='3',fullname='lxj', password='1234')>, None)
(<User(id='4',fullname='lxj', password='12345')>, <User(id='4',fullname='lxj', password='123')>)
(<User(id='5',fullname='sx', password='12345')>, None)
(<User(id='6',fullname='sx', password='12346')>, <User(id='6',fullname='lxj', password='123')>)
(<User(id='7',fullname='sxaa', password='12346')>, <User(id='7',fullname='lxj', password='1234')>)
(<User(id='8',fullname='sxs', password='12346')>, <User(id='8',fullname='sx', password='1234')>)

  full join 取出全部在A和在B的的值,即除了交集的值

result1 = Session.query(User,UserTest).outerjoin(UserTest,UserTest.id == User.id)
result = Session.query(UserTest,User).outerjoin(User,UserTest.id == User.id)#若是數據在user表中,不在usertest表,
result_full = result1.union(result).all()
#參考mysql語句select * from user left join usertest on user.id = usertest.id union select * from user right join usertest on user.id = usertest.id;
for record in result_full:
    print(record)

結果:
(<User(id='1',fullname='lxj', password='123456')>, <User(id='1',fullname='lxj', password='123456')>)
(<User(id='2',fullname='sx', password='12345')>, <User(id='2',fullname='lxj', password='12345')>)
(<User(id='4',fullname='lxj', password='123')>, <User(id='4',fullname='lxj', password='12345')>)
(<User(id='6',fullname='lxj', password='123')>, <User(id='6',fullname='sx', password='12346')>)
(<User(id='7',fullname='lxj', password='1234')>, <User(id='7',fullname='sxaa', password='12346')>)
(<User(id='8',fullname='sx', password='1234')>, <User(id='8',fullname='sxs', password='12346')>)
(<User(id='9',fullname='lss', password='1234')>, None)
(<User(id='10',fullname='lss', password='123')>, None)
(<User(id='3',fullname='lxj', password='1234')>, None)
(<User(id='5',fullname='sx', password='12345')>, None)

Process finished with exit code 0

  下面來學習下外鍵使用,等外鍵學習完咱們再來使用join方法。 

外鍵關聯

爲了使例子清晰,咱們如今先刪除以前建立的user表,建立一個addresses表,跟user表關聯

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy import Column,String,Integer,ForeignKey,distinct
Base = declarative_base() #聲明基類
engine = create_engine("mysql+pymysql://root:123456@localhost:3306/testdb?charset=utf8") #echo = True打印生成對象的全部過程,若是要插入中文,必定要加?charset=utf8

class User(Base):
    """
    定義表結構,映射到數據庫中
    """
    __tablename__ = 'user' #表名
    id = Column(Integer,primary_key=True)
    fullname = Column(String(32))
    password = Column(String(64))

    def __repr__(self):   #對實例化的對象格式化輸出
        return "<User(id='%s',fullname='%s', password='%s')>" % (
                               self.id, self.fullname, self.password)

class Address(Base):
    __tablename__ = "addresses"
    id = Column(Integer,primary_key=True)
    email_address = Column(String(32),nullable=False)
    user_id = Column(Integer,ForeignKey("user.id"))

    def __repr__(self):
        return "<Address(email_address='%s')>"%self.email_address

Base.metadata.create_all(engine)  #建立表結構

  

此時咱們已經與user表id有外鍵關聯了,在實際的模型中,一個用戶一般有多個email地址,一個email地址對應一個用戶,這就是數據庫設計中一對多關係, 下面咱們添加幾條數據 

 

user_obj1 = User(fullname = "lxj",password = "123456")
user_obj2 = User(fullname = "sx",password = "123456")
Session.add_all([user_obj1,user_obj2])
Session.commit()
addr_obj1 = Address(email_address = "aa@qq.com",user_id = 1)
addr_obj2 = Address(email_address = "bb@qq.com",user_id = 2)
addr_obj3 = Address(email_address = "cc@qq.com",user_id = 1)

Session.add_all([addr_obj1,addr_obj2,addr_obj3])

Session.commit()

  若是咱們在address表中添加的user_id字段在user表中找不到,那麼會報錯,同mysql外鍵同樣,會檢查一致性

此時咱們來實現下,以前join報錯的那個例子

user = Session.query(User).join(Address)
print(user,user.all())

#SELECT user.id AS user_id, user.fullname AS user_fullname, user.password AS user_password 
FROM user INNER JOIN addresses ON user.id = addresses.user_id [<User(id='1',fullname='lxj', password='123456')>, <User(id='2',fullname='sx', password='123456')>]

  當有外鍵關聯的時候,join的時候就不須要指定列

接下來咱們講下另一種場景,若是咱們要查user表中lxj全部的郵箱地址,能夠用這種方法

user = Session.query(Address).join(User).filter(User.fullname == 'lxj')
print(user,user.all())

#結果:
SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address, addresses.user_id AS addresses_user_id 
FROM addresses INNER JOIN user ON user.id = addresses.user_id 
WHERE user.fullname = %(fullname_1)s

 [<Address(email_address='aa@qq.com')>, <Address(email_address='cc@qq.com')>]

 可是上面的例子是經過address表來查詢,可是在一般狀況下咱們都是經過user來搜索,這時咱們就要在address表中定義一個relationship字段

class Address(Base):
    __tablename__ = "addresses"
    id = Column(Integer,primary_key=True)
    email_address = Column(String(32),nullable=False)
    user_id = Column(Integer,ForeignKey("user.id"))

    user = relationship("User",backref = "addresses")  #這個實際上是在內存中,並不在mysql表的關係

    def __repr__(self):
        return "<Address(email_address='%s')>"%self.email_address

  

user1 = Session.query(User).filter(User.fullname == 'lxj').first() #當咱們在address表中定義relationship時,此時咱們就能經過user表address查找對應地址信息
print(user1.addresses)

address = Session.query(Address).first() #還能夠經過address表反查user
print(address,address.user.fullname)

#結果
[<Address(email_address='aa@qq.com')>, <Address(email_address='cc@qq.com')>]
<Address(email_address='aa@qq.com')> lxj
  
多外鍵關聯

One of the most common situations to deal with is when there are more than one foreign key path between two tables.

Consider a Customer class that contains two foreign keys to an Address class:

下表中,Customer表有2個字段都關聯了Address表 

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy import Column,String,Integer,ForeignKey,distinct
from sqlalchemy.orm import relationship,sessionmaker
Base = declarative_base() #聲明基類
engine = create_engine("mysql+pymysql://root:123456@localhost:3306/testdb?charset=utf8") #echo = True打印生成對象的全部過程,若是要插入中文,必定要加?charset=utf8

class Customer(Base):
    """
    定義表結構,映射到數據庫中
    """
    __tablename__ = 'customer' #表名
    id = Column(Integer,primary_key=True)
    name = Column(String(64))
    billing_address_id = Column(Integer, ForeignKey("address.id"))
    shipping_address_id = Column(Integer, ForeignKey("address.id"))

    billing_address = relationship("Address")
    shipping_address = relationship("Address")

    def __repr__(self):   #對實例化的對象格式化輸出
        return "<Customer(id='%s',name='%s')>" % (
                               self.id, self.name)

class Address(Base):
    __tablename__ = 'address'
    id = Column(Integer, primary_key=True)
    street = Column(String(64))
    city = Column(String(64))
    state = Column(String(64))

    def __repr__(self):
        return "<Address(street='%s',city='%s',state='%s')>"%(self.state,self.city ,self.street)

Base.metadata.create_all(engine)  #建立表結構

Session_class = sessionmaker(bind = engine)  #建立與數據庫的會話session class ,注意,這裏session_class是類,不是實例
Session = Session_class() #生成Session實例

address_obj = Address(street = 'jiuhelu',city = 'hangzhou',state = 'zhejiang')

Session.add(address_obj)
Session.commit()

建立表沒有問題,可是插入address數據會報錯
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship Customer.billing_address - there are multiple foreign key paths linking the tables.  Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.

 修改方法:

class Customer(Base):
    """
    定義表結構,映射到數據庫中
    """
    __tablename__ = 'customer' #表名
    id = Column(Integer,primary_key=True)
    name = Column(String(64))
    billing_address_id = Column(Integer, ForeignKey("address.id"))
    shipping_address_id = Column(Integer, ForeignKey("address.id"))

    billing_address = relationship("Address",foreign_keys = [billing_address_id] )
    shipping_address = relationship("Address",foreign_keys = [shipping_address_id])

    def __repr__(self):   #對實例化的對象格式化輸出
        return "<Customer(id='%s',name='%s')>" % (
                               self.id, self.name)

  這樣sqlachemy就能分清哪一個外鍵是對應哪一個字段了

插入數據

address_obj = Address(street = 'chengfa',city = 'hangzhou',state = 'zhejiang')
address_obj1 = Address(street = 'shimen',city = 'jiangshan',state = 'zhejiang')
address_obj2 = Address(street = 'dongyuelu',city = 'jiangshan',state = 'zhejiang')

Session.add_all([address_obj,address_obj1,address_obj2])

c1 = Customer(name = 'lxj',billing_address = address_obj,shipping_address = address_obj2)
c2 = Customer(name = 'sx',billing_address = address_obj1,shipping_address = address_obj1)
Session.add_all([c1,c2])
Session.commit()

 

查詢

obj = Session.query(Customer).filter(Customer.name=='lxj').first()

print(obj,obj.billing_address,obj.shipping_address)
Session.commit()

#結果:
<Customer(id='1',name='lxj')> <Address(street='zhejiang',city='hangzhou',state='chengfa')> <Address(street='zhejiang',city='jiangshan',state='dongyuelu')>

  

  

 

多對多關聯

咱們思考一種狀況,一個學生對應多個班級,一個班級又有好多學生,這種多對多的關係表結構該如何設計呢?假設咱們的班級信息和學生信息是這樣

 

 那應該如何定義外鍵呢?總不能在一個字段裏存好幾個班級的id吧!更不可能建立多條學員記錄來關聯班級,那這樣就更low了,改個學生信息,全部都要改,並且太冗餘了,最好的辦法是建立第三張表來關聯2這兩張表。

下面咱們來看看sqlalchemy的具體實現

 

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy import Column,String,Integer,ForeignKey,distinct,Date,Table
from sqlalchemy.orm import relationship
Base = declarative_base() #聲明基類
engine = create_engine("mysql+pymysql://root:123456@localhost:3306/testdb?charset=utf8") #echo = True打印生成對象的全部過程,若是要插入中文,必定要加?charset=utf8

stu_m2m_cls = Table("stu_m2m_cls",Base.metadata,
                    Column('stu_id',Integer, ForeignKey("students.id")),
                    Column("cls_id",Integer, ForeignKey("classes.id"))
)  #建立第三張表來關聯students表和classes表

class Student(Base):
    __tablename__ = 'students'

    id = Column(Integer,primary_key=True)
    name = Column(String(32),nullable=False)
    age = Column(Integer,nullable=False)
    register_date = Column(Date)

    def __repr__(self):
        return "<name:%s,age:%s,register_date:%s>"%(self.name,self.age,self.register_date)

class Class(Base):
    __tablename__ = 'classes'


    id = Column(Integer, primary_key=True)
    cls = Column(String(32),nullable=False)

    students = relationship("Student",secondary = stu_m2m_cls,backref = "classes")  #經過relationship來創建第三張表和class、student創建關係,這條語句也可寫在Student種

    def __repr__(self):
        return "<培訓班:%s>"%self.cls

Base.metadata.create_all(engine)  #建立表結構

  插入數據

stu_obj1 = Student(name = "lxj",age = 21,register_date ="2017-05-12" )
stu_obj2 = Student(name = "sx",age = 21,register_date ="2017-05-10" )
stu_obj3 = Student(name = "lss",age = 3,register_date ="2017-05-12" )

cls_obj1 = Class(cls = "python培訓" )
cls_obj2 = Class(cls = "linux培訓" )
cls_obj3 = Class(cls = "mysql培訓" )

stu_obj1.classes = [cls_obj1,cls_obj2]  #創建學生表與班級表創建關係,這裏最好直接用.一、單個要添加用stu_obj1.classes.append(cls_obj)二、多個添加用stu_obj1.classes.extend([stu_obj1,stu_obj2])
                                         #踩過的坑,否則會覆蓋以前的記錄,在這裏記錄下。
stu_obj2.classes = [cls_obj3] Session.add_all([stu_obj2,stu_obj1,stu_obj3,cls_obj3,cls_obj2,cls_obj1]) Session.commit()

 登錄mysql查看錶的記錄

 

 

查詢

stu_obj = Session.query(Student).filter(Student.name == "lxj").first()
print(stu_obj.classes)

cls_obj = Session.query(Class).filter(Class.cls =='python培訓').first()  #反查
print(cls_obj.students)

結果:
[<培訓班:python培訓>, <培訓班:linux培訓>]
[<name:lxj,age:21,register_date:2017-05-12>]

  更多詳見:http://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html#one-to-many

相關文章
相關標籤/搜索