For the record, I'm using Python and SQLlite. I have a working function that generates the SQL I need, but it does not seem right.
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
I will probably need to add multiple columns and criteria later on.
Any better ideas?
Edit: What does not look right is that I am constructing the SQL dynamically from Strings. This is generally not the best approach. SQL injections attacs, need to properly escape strings. I cannot use placeholders because some of the values are None and do not need to be in the WHERE clause condition.
Native dynamic SQL only supports a RETURNING clause if a single row is returned. See Also: "Performing DML with RETURNING Clause Using Dynamic SQL: Example" for examples of DBMS_SQL package code and native dynamic SQL code that uses a RETURNING clause.
Dynamic SQL is a programming technique that enables you to build SQL statements dynamically at runtime. You can create more general purpose, flexible applications by using dynamic SQL because the full text of a SQL statement may be unknown at compilation.
Using CTEs, for instance, you can use SELECT from <subquery> in Open SQL. In my case I needed to execute dynamic SELECT count( DISTINCT col1, col2, …) which is not possible in the regular OpenSQL.
You really do not want to use string formatting to include values. Leave that to the database API via SQL parameters.
Using parameters you:
Since SQLLite supports named SQL parameters, I'd return both a statement and a dictionary with parameters:
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
then pass both to cursor.execute()
:
cursor.execute(*daily(host, day))
SQL generation becomes complex fast, you may want to look at SQLAlchemy core to do the generation instead.
For your example, you can generate:
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
The query
object can have additional filters applied to it, ordered, grouped, used as a subselect to other queries, joined and finally sent to be executed at which point SQLAlchemy will turn this into SQL fit for the specific database you are connecting to.
Just for completeness. I found the pypika library quite handy (if libraries are allowed):
https://pypika.readthedocs.io/en/latest/index.html
It allows to construct sql queries like this:
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With