HiveBrain v1.2.0
Get Started
← Back to all entries
snippetpythonMinor

Generate SQL query by loop

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
sqlloopgeneratequery

Problem

I need to perform SQL query which will get products from database based on few dimensions which I will pass in URL.

import itertools

sql_where = ''
dimensions = ['a','b', 'c', 'd', 'e', 'f', 'f2', 'g']

for dimension in dimensions:
    for onetwo, direction in zip( range(1, 3), itertools.cycle('><') ):
        globals()[dimension+str(onetwo)] = str( request.GET.get( dimension+str(onetwo) ) )
        sql_where += " AND ( `{dimension}` {direction}= '{get_dimension}' OR '{get_dimension}' = ('None' OR '') )".format(dimension=dimension, direction=direction, get_dimension=globals()[dimension+str(onetwo)])

sql = """SELECT * FROM t_wishbone_dimensions WHERE 1=1""" + sql_where
print sql


Example output for /search?a1=34&a2=37&c1=50&c2=75

SELECT * FROM t_wishbone_dimensions 
WHERE 1=1 
AND ( `a` >= '34' OR '34' = ('None' OR '') ) 
AND ( `a` = 'None' OR 'None' = ('None' OR '') ) 
AND ( `b` = '50' OR '50' = ('None' OR '') ) 
AND ( `c` = 'None' OR 'None' = ('None' OR '') ) 
AND ( `d` = 'None' OR 'None' = ('None' OR '') ) 
AND ( `e` = 'None' OR 'None' = ('None' OR '') ) 
AND ( `f` = 'None' OR 'None' = ('None' OR '') ) 
AND ( `f2` = 'None' OR 'None' = ('None' OR '') ) 
AND ( `g` <= 'None' OR 'None' = ('None' OR '') )


What do you think about my solution?

Solution

Since I didn't want to setup a webserver for this, I added a bare class, which should behave in the same way as your request.GET:

class request:
    GET = dict(a1=34, a2=37, c1=50, c2=75)


You should avoid setting keys in globals(), it clutters the namespace too much. Instead just use the default keyword of the get method of a dictionary, which will give you the specified value if the key is not present.

get_dimension = request.GET.get(dim, 'None')


You can make your string formatting a bit easier to understand if you split it up a bit. I made the string itself a constant (avoiding having to allocate it multiple times) and used positional arguments, instead of keywords (which, admittedly, hurts the readability slightly).

Instead of doing a string addition every loop, it is better to append to a list and do one join at the end, because string addition means creating an intermediate new string, since strings are immutable.

sql = "SELECT * FROM t_wishbone_dimensions WHERE 1=1" + "\n".join(sql_where)


I would also use itertools.product to replace your double loop, so something like:

directions = {'1': '>', '2': '<'}
dimensions = ['a', 'b', 'c', 'd', 'e', 'f', 'f2', 'g']
for dimension, i in itertools.product(dimensions, ('1', '2')):
    direction = directions[i]
    ...


To improve readability you should separate the ANDs with a newline each (like you did in your example output).

Lastly, python has an official style guide, PEP8, which recommends having a blank after a comma in an argument list (in dimensions after 'a', one was missing), as well as not adding unnecessary whitespace (so usually no str( something ), but just str(something)).

Final code:

import itertools

class request:
    # should handle the same same as your `request.GET`
    GET = dict(a1=34, a2=37, c1=50, c2=75)

dimensions = ['a', 'b', 'c', 'd', 'e', 'f', 'f2', 'g'] 
DIRS = {'1': '>', '2': '<'}
AND_STR = "AND ( `{0}` {1}= '{2}' OR '{2}' = ('None' OR '') )"

sql_where = ['']
for dimension, i in itertools.product(dimensions, ('1', '2')):
    val = request.GET.get(dimension+i, 'None')
    sql_where.append(AND_STR.format(dimension, DIRS[i], val))
sql = "SELECT * FROM t_wishbone_dimensions WHERE 1=1" + "\n".join(sql_where)
print sql


Output:

SELECT * FROM t_wishbone_dimensions WHERE 1=1
AND ( `a` >= '34' OR '34' = ('None' OR '') )
AND ( `a` = 'None' OR 'None' = ('None' OR '') )
AND ( `b` = '50' OR '50' = ('None' OR '') )
AND ( `c` = 'None' OR 'None' = ('None' OR '') )
AND ( `d` = 'None' OR 'None' = ('None' OR '') )
AND ( `e` = 'None' OR 'None' = ('None' OR '') )
AND ( `f` = 'None' OR 'None' = ('None' OR '') )
AND ( `f2` = 'None' OR 'None' = ('None' OR '') )
AND ( `g` <= 'None' OR 'None' = ('None' OR '') )

Code Snippets

class request:
    GET = dict(a1=34, a2=37, c1=50, c2=75)
get_dimension = request.GET.get(dim, 'None')
sql = "SELECT * FROM t_wishbone_dimensions WHERE 1=1" + "\n".join(sql_where)
directions = {'1': '>', '2': '<'}
dimensions = ['a', 'b', 'c', 'd', 'e', 'f', 'f2', 'g']
for dimension, i in itertools.product(dimensions, ('1', '2')):
    direction = directions[i]
    ...
import itertools

class request:
    # should handle the same same as your `request.GET`
    GET = dict(a1=34, a2=37, c1=50, c2=75)

dimensions = ['a', 'b', 'c', 'd', 'e', 'f', 'f2', 'g'] 
DIRS = {'1': '>', '2': '<'}
AND_STR = "AND ( `{0}` {1}= '{2}' OR '{2}' = ('None' OR '') )"

sql_where = ['']
for dimension, i in itertools.product(dimensions, ('1', '2')):
    val = request.GET.get(dimension+i, 'None')
    sql_where.append(AND_STR.format(dimension, DIRS[i], val))
sql = "SELECT * FROM t_wishbone_dimensions WHERE 1=1" + "\n".join(sql_where)
print sql

Context

StackExchange Code Review Q#139931, answer score: 4

Revisions (0)

No revisions yet.