a2.sqlalchemy-操作

一、sqlalchemy中的查詢分析

1、基本查詢

filter和filter_by的區別:

  • filter可以在後邊加判斷條件,但是每次只能過濾一個條件
  • filter_by可以過濾多個,與Django的orm的方式類似,但是隻能用“=”

如何查看sqlalchemey的sql語句

在不添加結尾的all()``first()等,得到的具體查詢對象,直接利用str方法,便可將其轉換爲sql語句

row = session.query(User).filter(User.username!='aaa')
print(row)

結果

SELECT user.id AS user_id, user.username AS user_username, user.password AS user_password, user.createtime AS user_createtime, user._locked AS user__locked 
FROM user 
WHERE user.username != %(username_1)s

表中單個字段查詢

print(session.query(User.username).filter(User.username!='dandan').all())

first()

僅查詢顯示第一個

print(session.query(User.username).filter(User.username!='dandan').first())

one()

只查詢出來第一個,有且只有一個
如果有兩個符合條件的,會報錯

print(session.query(User).filter(User.username=='choupi').one())

結果

<User(id='12' ,username='choupi',password='q1',createtime='2018-03-09 20:27:54',_locked ='False',)>

get 根據主鍵查詢

主鍵在表中只有一個
例如ID爲主鍵,查詢id=3的元素

print(session.query(User).get(3))
# 結果
<User(id='3' ,username='tree',password='zzz111',createtime='2018-03-09 14:44:21',_locked ='False',)>

limit 限制查詢結果

limit(3) 僅查出3條結果

print(session.query(User).filter(User.username!='dandan').limit(3).all())
# 結果
[<User(id='2' ,username='tobee',password='234qwe',createtime='2018-03-07 16:16:05',_locked ='False',)>, <User(id='3' ,username='tree',password='zzz111',createtime='2018-03-09 14:44:21',_locked ='False',)>, <User(id='4' ,username='aaa',password='111',createtime='2018-03-09 17:57:15',_locked ='False',)>]

offset() 限制前面n個,顯示後面n+1個,向後便宜Nge

顯示第N個以後

print(session.query(User.username).filter(User.username!='dandan').all())
print(session.query(User.username).filter(User.username!='dandan').limit(3).all())

print(session.query(User.username).filter(User.username!='dandan').offset(3).all())

結果:

[('tobee',), ('tree',), ('aaa',), ('coding',), ('choupi',), ('111',), ('choupidan',), ('youku',)]
[('tobee',), ('tree',), ('aaa',)]
[('coding',), ('choupi',), ('111',), ('choupidan',), ('youku',)]

slice() 切片,也可以直接使用[1,9]

slice(1,3) 與python的slice一致,從0開始 左閉右開,顯示1,2兩個元素

print(session.query(User.username).filter(User.username!='dandan').slice(1,3).all())
# 結果
[('tree',), ('aaa',)]

order_by() 元素排序 順序

print(session.query(User.username).filter(User.username!='dandan').order_by(User.username).all())

# 按數字字符順序排序

[('111',), ('aaa',), ('choupi',), ('choupidan',), ('coding',), ('tobee',), ('tree',), ('youku',)]

desc() 逆序排序

from sqlalchemy import desc
print(session.query(User.username).filter(User.username!='dandan').order_by(desc(User.username)).all())

# 逆序排序

[('youku',), ('tree',), ('tobee',), ('coding',), ('choupidan',), ('choupi',), ('aaa',), ('111',)]

like() 模糊搜索,結合佔位符%使用 與原生sql一致

print(session.query(User.username).filter(User.username.like('%e')).all())
# 結果
[('tobee',), ('tree',)]

ilike() 模糊搜索,不區分大小寫

# 內部實現如下類似操作:
lower(a) LIKE lower(other)
# 同理還有 notilike() 方法,
# 跟like()用法一致,只是不區分大小寫。
# 這個是sqlalchemy ORM 層做的強化,不是數據庫層的用法

notlike()

print(session.query(User.username).filter(User.username.notlike('%e')).all())
# 結果
[('dandan',), ('aaa',), ('coding',), ('choupi',), ('111',), ('choupidan',), ('youku',)]

in_() 判斷在xx裏邊

print(session.query(User.username).filter(User.username.in_(['dandan','aaa'])).all())
# 結果
[('dandan',), ('aaa',)]

notin_

print(session.query(User.username).filter(User.username.notin_(['dandan','aaa'])).all())
# 結果
[('tobee',), ('tree',), ('coding',), ('choupi',), ('111',), ('choupidan',), ('youku',)]

is_ 是xxx

兩種表達方式 None

print(session.query(User.username).filter(User.username==None).all())
print(session.query(User.username).filter(User.username.is_(None)).all())

isnot

filter支持多條件查詢

print(session.query(User.username).filter(User.username.isnot(None),User.password=='111').all())
# 結果
[('aaa',), ('111',)]

or_

from sqlalchemy import or_
print(session.query(User.username).filter(or_(User.username.isnot(None),User.password=='111')).all())

# 結果
[('dandan',), ('tobee',), ('tree',), ('aaa',), ('coding',), ('choupi',), ('111',), ('choupidan',), ('youku',)]

2、聚合函數(在sqlalchemy.func中)

count / group_by

from sqlalchemy import func
print(session.query(User.password,func.count(User.id)).group_by(User.password).all())

查詢原生sql

SELECT user.password AS user_password, count(user.id) AS count_1 
FROM user GROUP BY user.password
[('111', 2), ('123asd', 1), ('234qwe', 1), ('333', 1), ('choupidan', 1), ('q1', 1), ('qwer', 1), ('zzz111', 1)]

having

having字句可以讓我們篩選成組後的各種數據,where字句在聚合前先篩選記錄,也就是說作用在group by和having字句前。
having子句在聚合後對組記錄進行篩選。真實表中沒有此數據,這些數據是通過一些函數生存。

print(session.query(User.password,func.count(User.id)).group_by(User.password).\
      having(func.count(User.id)>1).all())
# 結果
[('111', 2)]

sum

print(session.query(User.password,func.sum(User.id)).group_by(User.password).all())
# 結果
[(111, Decimal(18)), (‘123asd’, Decimal(1)), (‘234qwe’, Decimal(2)), (333, Decimal(16)), (‘choupidan’, Decimal(15)), (‘q1’, Decimal(12)), (‘qwer’, Decimal(5)), (‘zzz111’, Decimal(3))]

max

print(session.query(User.password,func.max(User.id)).group_by(User.password).all())

# 結果
[(111, 14), (‘123asd’, 1), (‘234qwe’, 2), (333, 16), (‘choupidan’, 15), (‘q1’, 12), (‘qwer’, 5), (‘zzz111’, 3)]

min

print(session.query(User.password,func.min(User.id)).group_by(User.password).all())

# 結果
[(111, 4), (‘123asd’, 1), (‘234qwe’, 2), (333, 16), (‘choupidan’, 15), (‘q1’, 12), (‘qwer’, 5), (‘zzz111’, 3)]

lable 別名

lable別名不能用在having中

extract 提取

提取時間元素

from sqlalchemy import extract
print(session.query(extract('minute',User.createtime).label('minute'),func.count(User.id)).group_by('minute').all())

基於分鐘排序

[(16, 1), (27, 1), (29, 3), (44, 1), (52, 1), (57, 2)]

基於天數排序

print(session.query(extract('day',User.createtime).label('day'),func.count(User.id)).group_by('day').all())

[(6, 1), (7, 1), (9, 7)]

二、進階操作

1、反向查詢

一對多反向查詢

  • sqlalchemy的數據庫層
class TaskTemplateTable(db.Model, SessionMixin):
    """
    任務模板綁定的表單模板
    """
    __tablename__ = 'patrol_inspect_tasktemplatetable'
    # True: 顯示點位信息
    # False 不顯示點位信息
    id = db.Column(db.Integer(), primary_key=True, autoincrement=True)

    table_template_id = db.Column(db.Integer(), db.ForeignKey('patrol_inspect_tabletemplate.id'))

    task_template = db.relationship('TaskTemplate')

  • 查詢層
table_obj = db.session.query(TaskTemplateTable).filter_by(id=1).first()
# 要先拿到查詢表下的對象,再根據對象,進行關聯表的反向查詢
print(table_obj.tamplate_table.name)
>>> 'ups巡檢記錄表'

2、數據批量操作

數據庫批量數據的插入:

  • 一般的操作都爲,每一條數據提交一次的方式,這樣相當影響性能,可以通過執行原生sql或者sqlalchemy支持的批量導入數據的方式。進行批量插入
  • 轉化爲sql:insert into 表名 values(...),(...)...;

1) 批量插入10000條數據:

db.session.execute(
    User.__table__.insert(),
    [{'name': `randint(1, 100)`,'age': randint(1, 100)} for i in xrange(10000)]
)
session.commit()

2)如何讓執行的 SQL 語句增加前綴?

使用 query 對象的 prefix_with() 方法:

session.query(User.name).prefix_with('HIGH_PRIORITY').all()
session.execute(User.__table__.insert().prefix_with('IGNORE'), {'id': 1, 'name': '1'})

3)如何替換一個已有主鍵的記錄?

使用 session.merge() 方法替代 session.add(),其實就是 SELECT + UPDATE:

user = User(id=1, name='ooxx')
session.merge(user)
session.commit()

或者使用 MySQL 的 INSERT … ON DUPLICATE KEY UPDATE,需要用到 @compiles 裝飾器,有點難懂,自己看吧:《SQLAlchemy ON DUPLICATE KEY UPDATE》 和 sqlalchemy_mysql_ext。

4)如何使用無符號整數?

可以使用 MySQL 的方言:

from sqlalchemy.dialects.mysql import INTEGER

id = Column(INTEGER(unsigned=True), primary_key=True)

5)模型的屬性名需要和表的字段名不一樣怎麼辦?

開發時遇到過一個奇怪的需求,有個其他系統的表裏包含了一個“from”字段,這在 Python 裏是關鍵字,於是只能這樣處理了:
from_ = Column('from', CHAR(10))

6)如何獲取字段的長度?

Column 會生成一個很複雜的對象,想獲取長度比較麻煩,這裏以 User.name 爲例:
User.name.property.columns[0].type.length

7)如何指定使用 InnoDB,以及使用 UTF-8 編碼?

最簡單的方式就是修改數據庫的默認配置。如果非要在代碼裏指定的話,可以這樣:

class User(BaseModel):
    __table_args__ = {
        'mysql_engine': 'InnoDB',
        'mysql_charset': 'utf8'
    }

數據庫批量數據刪除DELETE

  • 當查詢結果只有一個對象時,可直接調用改對象的delete方法即可
    • dbm.AccessToken.query.filter_by(id=1).delete()
  • 但當結果不止一個時,直接這麼操作,會報錯。
  • 刪除記錄時,默認會嘗試刪除 session 中符合條件的對象,而這裏還不支持,所以報錯

1) 批量刪除數據:

# 方式一:  
token_obj_list = dbm.AccessToken.query.filter(text(sql_text)).params(**sql_params).all()

for obj in token_obj_list:
    db.session.delete(obj)
db.session.commit()

# 方式二:
     # 這裏不讓session同步
delte_count = AccessToken.query.filter(text(sql_text)).params(**sql_params).delete(synchronize_session=False)

print(delete_count)
session.commit()

3、複製copy對象,並新建數據到db

consume_obj = dbm.ConsumeOrderHistory.query.filter_by(id=raw_index).first()


# 直接copy對象,會造成session會話衝突,這個不能直接用copy的方式來操作
# consume_record = copy.deepcopy(consume_obj)

# 通過make_transient來實現
from sqlalchemy.orm.session import make_transient

logger.info(consume_obj)  # <pay_proxy.dbmodels.ConsumeOrderHistory object at 0x7f3ff62ac350>
logger.info(type(consume_obj))   # <class 'pay_proxy.dbmodels.ConsumeOrderHistory'>

logger.info("開始處理對象 ... ")
make_transient(consume_obj)

logger.info(consume_obj)   # <pay_proxy.dbmodels.ConsumeOrderHistory object at 0x7f3ff62ac350> ,對象的地址都沒變,只是去掉了session鏈接
logger.info(type(consume_obj))   # <class 'pay_proxy.dbmodels.ConsumeOrderHistory'>
logger.info(dir(consume_obj))  # 仍然包含源對象的所有方法

"""
- 處理前後的兩個對象是完全相等的!
- 經過其處理之後的對象,不再跟session綁定,此時只需要將其主鍵作相應替換即可實現複製一份數據到db
"""

# 兩種方式:
# consume_obj.id = None  # 1、 主鍵設null,主鍵設置了autoincrement,則會自動生成
delattr(consume_obj, 'id')  # 2、刪除其主鍵屬性,commit到數據庫時會自動賦值

db.session.add(consume_obj)
db.session.commit()

# 最終會在數據庫添加除了主鍵不同之外,其他所有字段都跟查出來的consume_obj字段相一致的一條數據。

三、其他sql知識點

1、sql語句相關

數據庫字段判空:

  • 1.爲null
  • 2.爲字符串的空’’
    判斷是否爲空(包括上邊兩種情況):
select * from table where column is null or trim(column)=''

這樣就可以排除字段內容爲null、’'的。

判斷某個字段不爲空,可直接用!=來做判斷,便可以排除掉null""的判斷

select * from table where trim(column) != ''
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章