在最近的工作中遇到一個問題,要將兩個字段相似的表里的數據統一起來展示在一個統計頁面中。如果是單純的展示數據那很簡單,兩個表查出來之后組合一下就完事了,但是有坑的地方就是分頁和按照時間搜索,這兩個功能決定了不可能單獨查詢兩張表。在同事的建議下,使用了union的聯合查詢,最終完成這個功能。做一個簡單的demo,記錄下這個功能。
數據庫和sqlalchemy安裝請參考另一篇文章 sqlalchemy數據庫查詢小集合
定義數據表
定義 兩張表,字段類型相同,但名稱不同。
#coding:utf-8 from sqlalchemy import Column,CHAR,INTEGER from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker Base = declarative_base() class User(Base): __tablename__ = "user" id = Column(CHAR(20),primary_key = True) name = Column(CHAR(20)) age = Column(INTEGER) class Teacher(Base): __tablename__ = "teacher" id = Column(CHAR(20),primary_key = True) tec_name = Column(CHAR(20)) tec_age = Column(INTEGER) engine = create_engine('mysql+mysqldb://root:12345678@localhost:3306/test') def create_table(table_name): table_name.metadata.create_all(engine) print "創建成功" def insert_data(): DBSession = sessionmaker(bind=engine) session = DBSession() for x in range(10): temp = {} temp['id'] = x temp['name'] = 'user_' + str(x) temp['age'] = x user = User(**temp) session.add(user) for x in range(15): temp = {} temp['id'] = x temp['tec_name'] = 'tec_' + str(x) temp['tec_age'] = x * 2 tec = Teacher(**temp) session.add(tec) session.commit() session.close() print 'success' if __name__ = '__main__': create_table(User) create_table(Teacher) insert_data()
User表字段:
mysql> desc user; +-------+----------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+----------+------+-----+---------+-------+ | id | char(20) | NO | PRI | NULL | | | name | char(20) | YES | | NULL | | | age | int(11) | YES | | NULL | | +-------+----------+------+-----+---------+-------+ 3 rows in set (0.00 sec)
User表數據:
mysql> select * from user; +----+--------+------+ | id | name | age | +----+--------+------+ | 0 | user_0 | 0 | | 1 | user_1 | 1 | | 2 | user_2 | 2 | | 3 | user_3 | 3 | | 4 | user_4 | 4 | | 5 | user_5 | 5 | | 6 | user_6 | 6 | | 7 | user_7 | 7 | | 8 | user_8 | 8 | | 9 | user_9 | 9 | +----+--------+------+ 10 rows in set (0.00 sec)
Teacher表字段:
mysql> desc teacher; +----------+----------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +----------+----------+------+-----+---------+-------+ | id | char(20) | NO | PRI | NULL | | | tec_name | char(20) | YES | | NULL | | | tec_age | int(11) | YES | | NULL | | +----------+----------+------+-----+---------+-------+ 3 rows in set (0.00 sec) mysql>
teacher表數據:
mysql> select * from teacher; +----+----------+---------+ | id | tec_name | tec_age | +----+----------+---------+ | 0 | tec_0 | 0 | | 1 | tec_1 | 2 | | 10 | tec_10 | 20 | | 11 | tec_11 | 22 | | 12 | tec_12 | 24 | | 13 | tec_13 | 26 | | 14 | tec_14 | 28 | | 2 | tec_2 | 4 | | 3 | tec_3 | 6 | | 4 | tec_4 | 8 | | 5 | tec_5 | 10 | | 6 | tec_6 | 12 | | 7 | tec_7 | 14 | | 8 | tec_8 | 16 | | 9 | tec_9 | 18 | +----+----------+---------+ 15 rows in set (0.00 sec)
查詢
首先做一個簡單的查詢,將兩個表的數據分別查出來
def select(): DBSession = sessionmaker(bind=engine) session = DBSession() table_data = session.query(User).all() session.close() for x in table_data: print x.name,'------>',x.age table_data = session.query(Teacher).all() session.close() for x in table_data: print x.tec_name,'------>',x.tec_age
查詢結果:
Desktop python union_one.py /home/ljk/.local/lib/python2.7/site-packages/sqlalchemy/dialects/mysql/base.py:2514: Warning: '@@tx_isolation' is deprecated and will be removed in a future release. Please use '@@transaction_isolation' instead cursor.execute('SELECT @@tx_isolation') user_0 ------> 0 user_1 ------> 1 user_2 ------> 2 user_3 ------> 3 user_4 ------> 4 user_5 ------> 5 user_6 ------> 6 user_7 ------> 7 user_8 ------> 8 user_9 ------> 9 ----------------------------------- tec_0 ------> 0 tec_1 ------> 2 tec_10 ------> 20 tec_11 ------> 22 tec_12 ------> 24 tec_13 ------> 26 tec_14 ------> 28 tec_2 ------> 4 tec_3 ------> 6 tec_4 ------> 8 tec_5 ------> 10 tec_6 ------> 12 tec_7 ------> 14 tec_8 ------> 16 tec_9 ------> 18
union查詢
union 查詢的關鍵字是 union ,首先將第一張表的數據全部查詢出來,然后將第二張表的數據全部查詢出來,最后將兩個數據使用union聯合成一張新表,這張新表可以再次被篩選過濾,分頁等。
def select(): DBSession = sessionmaker(bind=engine) session = DBSession() table_data = session.query(User).all() session.close() # for x in table_data: # print x.name,'------>',x.age # table_data = session.query(Teacher).all() # session.close() # for x in table_data: # print x.tec_name,'------>',x.tec_age user_data = session.query(User.name,User.age) tec_data = session.query(Teacher.tec_name.label('name'), Teacher.tec_age.label('age')) result = user_data.union_all(tec_data) for x in result: print x.name,'------>',x.age
在上面的查詢中需要有一個注意點就是label,可以看到tec_data的查詢語句中使用了label這個屬性,該屬性的作用是將Teacher這張表查詢出來的tec_name 字段名稱變成name,已達到和User表字段的統一,只有兩張表的字段名稱一致,類型一致的情況下才能聯合查詢。
另外還使用了一個union_all 字段,該字段的意思是如果兩張表存在相同的記錄也要全部展示出來,想要讓相同的記錄合並起來使用union即可
查詢結果如下:
Desktop python union_one.py /home/ljk/.local/lib/python2.7/site-packages/sqlalchemy/dialects/mysql/base.py:2514: Warning: '@@tx_isolation' is deprecated and will be removed in a future release. Please use '@@transaction_isolation' instead cursor.execute('SELECT @@tx_isolation') user_0 ------> 0 user_1 ------> 1 user_2 ------> 2 user_3 ------> 3 user_4 ------> 4 user_5 ------> 5 user_6 ------> 6 user_7 ------> 7 user_8 ------> 8 user_9 ------> 9 tec_0 ------> 0 tec_1 ------> 2 tec_10 ------> 20 tec_11 ------> 22 tec_12 ------> 24 tec_13 ------> 26 tec_14 ------> 28 tec_2 ------> 4 tec_3 ------> 6 tec_4 ------> 8 tec_5 ------> 10 tec_6 ------> 12 tec_7 ------> 14 tec_8 ------> 16 tec_9 ------> 18
往往查詢出來還不是最終目的,還需要對查詢出來的數據過濾。查詢出來的數據不是一張正真的表,如果使用字段去匹配過濾條件呢?以查詢出age 大於 5為例 ,有兩種過濾方式:
1.使用User.age 作為篩選條件
2.使用Teacher.age 作為篩選條件
規則就是使用兩張表里任意一張表的原始字段過濾即可,該過濾條件會在聯合查詢出來的結果起上作用。
使用 User 表字段
def select(): DBSession = sessionmaker(bind=engine) session = DBSession() table_data = session.query(User).all() session.close() # for x in table_data: # print x.name,'------>',x.age # table_data = session.query(Teacher).all() # session.close() # for x in table_data: # print x.tec_name,'------>',x.tec_age user_data = session.query(User.name,User.age) tec_data = session.query(Teacher.tec_name.label('name'), Teacher.tec_age.label('age')) # result = user_data.union_all(tec_data) # for x in result: # print x.name,'------>',x.age result = user_data.union_all(tec_data).filter(User.age > 5) for x in result: print x.name,'------>',x.age
Desktop python union_one.py /home/ljk/.local/lib/python2.7/site-packages/sqlalchemy/dialects/mysql/base.py:2514: Warning: '@@tx_isolation' is deprecated and will be removed in a future release. Please use '@@transaction_isolation' instead cursor.execute('SELECT @@tx_isolation') user_6 ------> 6 user_7 ------> 7 user_8 ------> 8 user_9 ------> 9 tec_10 ------> 20 tec_11 ------> 22 tec_12 ------> 24 tec_13 ------> 26 tec_14 ------> 28 tec_3 ------> 6 tec_4 ------> 8 tec_5 ------> 10 tec_6 ------> 12 tec_7 ------> 14 tec_8 ------> 16 tec_9 ------> 18
使用 Teacher 表字段
def select(): DBSession = sessionmaker(bind=engine) session = DBSession() table_data = session.query(User).all() session.close() # for x in table_data: # print x.name,'------>',x.age # table_data = session.query(Teacher).all() # session.close() # for x in table_data: # print x.tec_name,'------>',x.tec_age user_data = session.query(User.name,User.age) tec_data = session.query(Teacher.tec_name.label('name'), Teacher.tec_age.label('age')) result = user_data.union_all(tec_data).filter(Teacher.tec_age>5) for x in result: print x.name,'------>',x.age
Desktop python union_one.py /home/ljk/.local/lib/python2.7/site-packages/sqlalchemy/dialects/mysql/base.py:2514: Warning: '@@tx_isolation' is deprecated and will be removed in a future release. Please use '@@transaction_isolation' instead cursor.execute('SELECT @@tx_isolation') user_6 ------> 6 user_7 ------> 7 user_8 ------> 8 user_9 ------> 9 tec_10 ------> 20 tec_11 ------> 22 tec_12 ------> 24 tec_13 ------> 26 tec_14 ------> 28 tec_3 ------> 6 tec_4 ------> 8 tec_5 ------> 10 tec_6 ------> 12 tec_7 ------> 14 tec_8 ------> 16 tec_9 ------> 18
關於union聯合查詢有一個說法很形象:join查詢就像是橫向擴展,將多張表的數據橫向組合在一起,而union像是縱向擴展,將多張表數據縱向排列起來
