最近要封装数据库接口,查询的关键是where条件。如何让不懂SQL的用户,通过某些接口,拼接出符合条件的SQL,成为了一个问题。

不论多复杂的where,应该只包括3种类型的元素,表达式与逻辑操作符及组合元素。

表达式,即形同:colname>=val,colname=val,colname like '%val%'等等的元素

逻辑操作符,即or,and

组合元素:即逻辑操作符+表达式,或逻辑操作符+表达式+组合元素组成的元素。这是一种递归的表达,不知道是否容易理解。

可以举个简单的例子:

colname1=val1 and colname2=val2是一种组合元素,它由表达式colname1=val1,colname2=val2,以及逻辑操作符and组成。

(colname1=val1 or colnam2=val2) and colname3=val3也是一种组合元素,它由组合元素(colname1=val1 or colnam2=val2) ,逻辑操作符and,和表达式colname3=val3组成。


这样写太复杂了,简化一下。因为组合元素如果探究其本质,最终还是由表达式和逻辑操作符组成。所以,where条件的最基本元素应该只有两个,表达式和逻辑操作符。

我们将它看做一个对象,node。

class Node(object):
	def __init__(self,key,val,logic,nodetype):
		self.key = key
		self.val = val
		self.logic = logic
		# 0 is logic node, 1 is normal node 
		self.type = nodetype
		self.links = list()

	def isLogicNode(self):
		return self.type == 1



它具有5个属性。其中type表明它是逻辑操作符(1),还是表达式(0)。

如果是逻辑操作符,那么它的logic会有值(or,and);如果是表达式,那么key,val会被赋值(name='1'被拆分为,key="name='?'",val="1";方便我们传入的参数进行过滤,避免SQL注入之类的)

python中没有指针的概念,所以我们用过list数组,存放该节点的子节点列表。

我心目中的树是这样的:

sql查询语句 转换成mysql查询语句 sql语句转化成查询树_操作符

树的描述如下:

1.叶子节点一定是表达式(因为不可能存在一个叶子节点是逻辑操作符的情况)

2.在一棵子树中,如果子树的高度>=2(那么子树的根节点一定是逻辑操作符)

那么,明显的。为了避免用户错误的建造没有子节点的逻辑节点,所以,我们拼接where的接口只对外提供两个方法:

a.创建表达式节点的方法

b.传入节点数组(节点可以是逻辑节点,也可以是表达式节点),逻辑操作符,返回逻辑节点的方法

即:逻辑节点的生成,仅能通过传入节点数组+逻辑操作符的形式生成


最后一步,就是得到树节点的根节点之后。如何还原成where语句呢?

这明显就是后序遍历了,如果对于二叉树而言,就是左子树,右子树,根(最后的根上的逻辑操作符,负责将左右子树的表达式连接起来);但是,因为我们的树是一个逻辑操作符节点,下面可能有多个表达式节点,即我们是N叉树。

后序也很简单,即:子树->根进行遍历。用递归实现即可。语言描述遍历方式如下:

方法 遍历节点方法(node):

如果节点是表达式节点:返回key,val拼接好的字符串

如果节点是逻辑操作符节点:

定义结果数组

取该节点的每一个子节点:

结果数组.append(遍历节点方法(node))

               逻辑操作符 连接 结果数组

我们如何判断该递归遍历方法一定能够结束?因为根据树的描述,我们的叶子节点一定是表达式节点,所以绝对能够触发返回key,val拼接好的字符串的方法。所以递归一定能够结束。


下面是对于我的思路的实现:

#coding:utf-8
import copy
class Node(object):
	def __init__(self,key,val,logic,nodetype):
		self.key = key
		self.val = val
		self.logic = logic
		# 0 is logic node, 1 is normal node 
		self.type = nodetype
		self.links = list()

	def isLogicNode(self):
		return self.type == 1

class NodeTree(object):
	def createNormalNode(self,key,val):
		node = Node(key,val,None,0)
		return node

	# private method ,user can only generate normal node
	def __createLogicNode(self,logic):
		node = Node(None,None,logic,1)
		return node

	def combineWheres(self,nodes,logic):
		logicnode = self.__createLogicNode(logic)
		for node in nodes:
			#浅拷贝,即是node对象的引用
			logicnode.links.append(copy.copy(node))
		return logicnode

	def transSQL(self,node):
		# invalid node tree
		if node.isLogicNode() and len(node.links) == 0:
			return -1
		if not node.isLogicNode():
			return node.key.replace('?',node.val)
		if node.isLogicNode():
			sql = list()
			for item in node.links:
				sql.append(self.transSQL(item))
			if -1 in sql:
				return -1
			if len(sql) == 1:
				return sql[0]
			else:
				logic = " "+node.logic+" "
				return '('+logic.join(sql)+')'

if __name__ == '__main__':
	#(name1='a' or name2='b' or name3='c') and name4 like '%u%'
	tree = NodeTree()
	node1 = tree.createNormalNode("name1 = '?'","a")
	node2 = tree.createNormalNode("name2 = '?'","b")
	node3 = tree.createNormalNode("name3 = '?'","c")
	node4 = tree.createNormalNode("name4 like '?'","%m%")

	orNode = tree.combineWheres([node1,node2,node3],'or')
	andNode = tree.combineWheres([orNode,node4],'and')

	#generate sql
	print '1:',tree.transSQL(andNode)

	#(name1='a' or name2='b' or name3='c') and name4 like '%u%' and (name5>='a' and name6>='b')
	node5 = tree.createNormalNode("name5>='?'","a")
	node6 = tree.createNormalNode("name6>='?","b")
	andNode2 = tree.combineWheres([node5,node6],'and')
	andNode3 = tree.combineWheres([andNode,andNode2],'and')
	print '2:',tree.transSQL(andNode3)




运行结果如下:

1: ((name1 = 'a' or name2 = 'b' or name3 = 'c') and name4 like '%m%')
2: (((name1 = 'a' or name2 = 'b' or name3 = 'c') and name4 like '%m%') and (name5>='a' and name6>='b))

虽然括号与人类相比,可能会多打。但是,整个where条件的优先级是不会受到影响的。