大声的告诉我是不是被我标题中的两个“之”给带进来的??手动滑稽
需求
自己的一款软件 GitHub 地址,关于PostgreSQL 已经设置成运行后自动创建序列,表和函数,但是数据库还是要手动去创建,很不方便,想使用创建序列和表同样的方法,去自动创建数据库
过程
DDL 语句如下:
DB_NAME = """ CREATE DATABASE if not exists {}; ALTER DATABASE {} OWNER TO postgres; """.format(DATABASE_NAME, DATABASE_NAME)
psycopg2.ProgrammingError: syntax error at or near "not" LINE 2: CREATE DATABASE if not exists test_classs;
创建序列和表的时候都没问题啊(函数我使用的是先删除,后创建),这怎么报错了?本着先运行成功,再实现功能的原则,我把“if not exists”给去掉,结果还是报错:
psycopg2.InternalError: CREATE DATABASE cannot run inside a transaction block
不能在事务块中创建数据库,大概意思就是这样不安全吧,百度加谷歌,有两种方法:
1.在 psycopg2 extensions 里使用 ISOLATION_LEVEL_AUTOCOMMIT,原理就是让连接发出命令时不启动任何事务,看常量名字,字面意思也是自动提交,并且不需要commit()或rollback()如:
import psycopg2 from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT # <-- ADD THIS LINE con = psycopg2.connect(...) con.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) # <-- ADD THIS LINE cur = con.cursor() cur.execute("CREATE DATABASE %s ;" % self.db_name) cur.close() con.close()
2.让连接处于自动提交模式,使用连接对象的autocommit 属性,如:
import psycopg2 con = psycopg2.connect(...) con.autocommit = True cur = con.cursor() cur.execute('CREATE DATABASE {};'.format(db_name)) cur.close() # 如果连接不关闭,则需要改回连接对象的autocommit 属性 con.autocommit = False # 如果连接关闭,则不需要执行上面这行代码 # con.close()
但是当我运行的时候,还是遇到了另外一个错误:
psycopg2.InternalError: CREATE DATABASE cannot be executed from a function or multi-command string
从字面意思上看,是不能在函数中创建数据库,且不能在多命令的字符串中创建数据库,函数我这里没用,只能是多行命令了,看了一下自己的DDL 语句,发现自己复制创建表的DDL 语句的部分有点多了。。从最简单的做起吧,毕竟上面的这两个例子都没有问题,将DDL 语句改为:
DB_NAME = """ CREATE DATABASE {}; """.format(DATABASE_NAME)
再次运行,虽然有一丢丢慢,但是还是创建成功了,但是在这里,我建议使用第二种方法,因为简单,不用再导入这个很长的常量,而且控制还很方便,比如下面这个需求,创建第一个数据库,创建第二个数据库(怎么会有这么奇葩的需求,哈哈,都是为了举例方便)
import psycopg2 from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT,ISOLATION_LEVEL_DEFAULT DATABASE_NAME = 'test_classs' DATABASE_NAME2 = 'test_classssssss' DB_NAME = """ CREATE DATABASE {}; """.format(DATABASE_NAME) DB_NAME2 = """ CREATE DATABASE {}; """.format(DATABASE_NAME2) conn = psycopg2.connect(...) conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) cur = conn.cursor() cur.execute(DB_NAME) conn.set_isolation_level(ISOLATION_LEVEL_DEFAULT) cur.execute(DB_NAME2) cur.close() conn.close()
看到了吧,你需要导入两个很长很长的变量名,当然,在IDE 的提示下,这样其实也不费事,但是我总感觉这样不怎么优雅,毕竟是Python,不优雅怎么行?所以我们可以使用另外一种方式:
import psycopg2 DATABASE_NAME = 'test_classs' DATABASE_NAME2 = 'test_classssssss' DB_NAME = """ CREATE DATABASE {}; """.format(DATABASE_NAME) DB_NAME2 = """ CREATE DATABASE {}; """.format(DATABASE_NAME2) conn = psycopg2.connect(...) conn.autocommit = True cur = conn.cursor() cur.execute(DB_NAME) conn.autocommit = False cur.execute(DB_NAME2) cur.close() conn.close()
效果是一样的,看着是不是舒服很多了?
好吧,现在我们的数据库是可以创建了,那么怎么样实现如果不存在就创建,存在就不处理呢?
既然不能像‘MySQL’那样用‘if not exists’来创建数据库,那么只能换另外一种思路了,先查询看看这个数据库是否存在,如果不存在就设置自动提交然后创建,如果存在,就不做处理,excuse me?这是换思路了吗?咳咳。。好吧,思路没变,只是过程用代码实现了而已。。具体代码如下:
import psycopg2 DATABASE_NAME = 'test_classss' DB_NAME_EXIST = """ select * from pg_database where datname='{}'; """.format(DATABASE_NAME) DB_NAME = """ CREATE DATABASE {}; """.format(DATABASE_NAME) conn = psycopg2.connect(...) cur = conn.cursor() cur.execute(DB_NAME_EXIST) result = cur.fetchall() if result == []: conn.autocommit = True cur.execute(DB_NAME) conn.autocommit = False else: print('{} is exist'.format(DATABASE_NAME)) cur.close() conn.close()
注意,ALL_DB_NAME 这条SQL 语句的条件后面占位符{}必须用引号引起来,否则会报 column "test_class" does not exist 的错误
测试了一下,貌似没有问题,那么改用一个不存在的数据库看看,果然,没有我想的那么简单。。
conn.autocommit = True
psycopg2.ProgrammingError: set_session cannot be used inside a transaction
百度了一下,也没找到答案,不过根据字面意思,肯定是和事务有关,先尝试了一个最笨的方法,就是查询后记录查询结果,然后把游标和连接都关闭,然后再创建新的连接,新的游标,根据结果去创建数据库,或者不处理,发现没有问题,虽然感觉怪怪的,但是也蛮开心的,就在要往博客园上记下解决方法的时候,忽然想到了,把上次的查询后的给提交一下不就OK 了?快被自己蠢哭了,这也是为什么我打了那么多字,而不直接上代码的原因,这是经验的不足,也是数据库知识的不足,脸红,直接上代码吧,其实就加一行代码就解决了。
结果
import psycopg2 DATABASE_NAME = 'test_classss' DB_NAME_EXIST = """ select * from pg_database where datname='{}'; """.format(DATABASE_NAME) DB_NAME = """ CREATE DATABASE {}; """.format(DATABASE_NAME) conn = psycopg2.connect(...) cur = conn.cursor() cur.execute(DB_NAME_EXIST) conn.commit() # <-- ADD THIS LINE result = cur.fetchall() if result == []: conn.autocommit = True cur.execute(DB_NAME) conn.autocommit = False else: print('{} is exist'.format(DATABASE_NAME)) cur.close() conn.close()
行吧,到此位置,功能实现~我们下次再见,如果你觉得对你有帮助,麻烦点个“推荐”,谢谢~
当然,如果你有更优雅的办法,请在下面留言,谢谢
参考
使用python创建Postgres数据库:https://www.e-learn.cn/content/wangluowenzhang/448331