动态SQL WHERE子句生成
我在用Python和SQLite。现在我有一个可以生成我需要的SQL的函数,但感觉不太对劲。
def daily(self, host=None, day=None):
sql = "SELECT * FROM daily WHERE 1"
if host:
sql += " AND host = '%s'" % (host,)
if day:
sql += " AND day = '%s'" % (day,)
return sql
我可能以后还需要添加多个列和条件。
有没有更好的想法呢?
补充:我觉得不对劲的地方在于,我是通过字符串动态构建SQL的。这通常不是最好的做法。因为这样容易受到SQL注入攻击,还需要正确处理字符串。由于有些值是None,所以我不能使用占位符,因为这些值在WHERE条件中是不需要的。
3 个回答
0
psycopg2 有一个非常棒的 sql 模块,可以让你做很多事情,比如:
SQL("SELECT * FROM tbl WHERE {} = {}").format(Identifier(field_name), Literal(field_value))
这在 sqlite 中也可以做到,但你需要把输入转换成数据库使用的编码格式,这样才能避免一些更隐蔽的 SQL 注入攻击,就像 psycopg2 在后台处理的那样。
3
为了完整性,我发现pypika这个库非常方便(如果允许使用库的话):
https://pypika.readthedocs.io/en/latest/index.html
它可以像这样构建SQL查询:
from pypika import Query
q = Query._from('daily').select('*')
if host:
q = q.where('host' == host)
if day:
q = q.where('day' == day)
sql = str(q)
16
你真的不想用字符串格式化来包含值。把这个工作交给数据库的API,通过SQL参数来处理。
使用参数的好处有:
- 让数据库有机会准备好语句,并重用查询计划,这样可以提高性能。
- 省去你处理值时需要正确转义的麻烦(包括避免SQL转义和SQL注入攻击的问题)。
因为SQLLite 支持命名的SQL参数,我建议你返回一个语句和一个包含参数的字典:
def daily(self, host=None, day=None):
sql = "SELECT * FROM daily"
where = []
params = {}
if host is not None:
where.append("host = :host")
params['host'] = host
if day is not None:
where.append("day = :day")
params['day'] = day
if where:
sql = '{} WHERE {}'.format(sql, ' AND '.join(where))
return sql, params
然后把这两个都传给cursor.execute()
:
cursor.execute(*daily(host, day))
生成SQL的过程会很快变得复杂,你可能想看看SQLAlchemy核心,用它来生成SQL。
对于你的例子,你可以生成:
from sqlalchemy import Table, Column, Integer, String, Date, MetaData
metadata = MetaData()
daily = Table('daily', metadata,
Column('id', Integer, primary_key=True),
Column('host', String),
Column('day', Date),
)
from sqlalchemy.sql import select
def daily(self, host=None, day=None):
query = select([daily])
if host is not None:
query = query.where(daily.c.host == host)
if day is not None:
query = query.where(daily.c.day == day)
return query
这个query
对象可以应用额外的过滤条件,可以排序、分组,可以作为其他查询的子查询,连接在一起,最后发送去执行,这时SQLAlchemy会把它转换成适合你连接的特定数据库的SQL。